Add strict mode and ability to generate exports for normal recompilation (for patches)

This commit is contained in:
Mr-Wiseguy 2024-08-11 14:45:07 -04:00
parent 8fcf73de4d
commit 623013a371
8 changed files with 103 additions and 5 deletions

View file

@ -139,9 +139,11 @@ int main(int argc, const char** argv) {
RabbitizerConfig_Cfg.pseudos.pseudoNot = false; RabbitizerConfig_Cfg.pseudos.pseudoNot = false;
RabbitizerConfig_Cfg.pseudos.pseudoBal = false; RabbitizerConfig_Cfg.pseudos.pseudoBal = false;
std::string recomp_include = "#include \"librecomp/recomp.h\"";
bool should_write_header = true; bool should_write_header = true;
for (const auto& func : mod_context.base_context.functions) { 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; should_write_header = false;
} }

View file

@ -307,9 +307,9 @@ N64Recomp::ModContext build_mod_context(const N64Recomp::Context& input_context,
} }
// Check for special section names. // Check for special section names.
bool patch_section = cur_section.name == ".recomp_patch"; bool patch_section = cur_section.name == N64Recomp::PatchSectionName;
bool force_patch_section = cur_section.name == ".recomp_force_patch"; bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName;
bool export_section = cur_section.name == ".recomp_export"; bool export_section = cur_section.name == N64Recomp::ExportSectionName;
// Add the functions from the current input section to the current output section. // Add the functions from the current input section to the current output section.
auto& section_out = ret.base_context.sections[output_section_index]; auto& section_out = ret.base_context.sections[output_section_index];

View file

@ -60,6 +60,9 @@ namespace N64Recomp {
constexpr uint16_t SectionSelf = (uint16_t)-1; constexpr uint16_t SectionSelf = (uint16_t)-1;
constexpr uint16_t SectionAbsolute = (uint16_t)-2; constexpr uint16_t SectionAbsolute = (uint16_t)-2;
constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods 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 { struct Section {
uint32_t rom_addr = 0; uint32_t rom_addr = 0;
uint32_t ram_addr = 0; uint32_t ram_addr = 0;

View file

@ -408,6 +408,25 @@ N64Recomp::Config::Config(const char* path) {
const toml::array* array = data_reference_syms_file_data.as_array(); const toml::array* array = data_reference_syms_file_data.as_array();
data_reference_syms_file_paths = get_data_syms_paths(array, basedir); data_reference_syms_file_paths = get_data_syms_paths(array, basedir);
} }
// Control whether the recompiler emits exported symbol data.
std::optional<bool> allow_exports_opt = input_data["allow_exports"].value<bool>();
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<bool> strict_patch_mode_opt = input_data["strict_patch_mode"].value<bool>();
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) { catch (const toml::parse_error& err) {
std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl; std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl;

View file

@ -42,6 +42,8 @@ namespace N64Recomp {
bool single_file_output; bool single_file_output;
bool use_absolute_symbols; bool use_absolute_symbols;
bool unpaired_lo16_warnings; bool unpaired_lo16_warnings;
bool allow_exports;
bool strict_patch_mode;
std::filesystem::path elf_path; std::filesystem::path elf_path;
std::filesystem::path symbols_file_path; std::filesystem::path symbols_file_path;
std::filesystem::path func_reference_syms_file_path; std::filesystem::path func_reference_syms_file_path;

View file

@ -431,7 +431,21 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
else { else {
reloc_out.reference_symbol = false; reloc_out.reference_symbol = false;
reloc_out.target_section = rel_symbol_section_index; 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) // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf)

View file

@ -568,6 +568,10 @@ int main(int argc, char** argv) {
open_new_output_file(); open_new_output_file();
} }
std::vector<size_t> export_function_indices{};
bool failed_strict_mode = false;
//#pragma omp parallel for //#pragma omp parallel for
for (size_t i = 0; i < context.functions.size(); i++) { for (size_t i = 0; i < context.functions.size(); i++) {
const auto& func = context.functions[i]; const auto& func = context.functions[i];
@ -576,6 +580,33 @@ int main(int argc, char** argv) {
fmt::print(func_header_file, fmt::print(func_header_file,
"void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name);
bool result; 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) { 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); result = N64Recomp::recompile_function(context, func, config.recomp_include, current_output_file, static_funcs_by_section, false);
if (!config.single_file_output) { 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++) { for (size_t section_index = 0; section_index < context.sections.size(); section_index++) {
auto& section = context.sections[section_index]; auto& section = context.sections[section_index];
auto& section_funcs = section.function_addrs; auto& section_funcs = section.function_addrs;
@ -789,6 +829,23 @@ int main(int argc, char** argv) {
} }
} }
fmt::print(overlay_file, "}};\n"); 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()) { if (!config.output_binary_path.empty()) {

View file

@ -15,6 +15,7 @@ struct FileSubHeaderV1 {
}; };
struct SectionHeaderV1 { struct SectionHeaderV1 {
uint32_t flags;
uint32_t file_offset; uint32_t file_offset;
uint32_t vram; uint32_t vram;
uint32_t rom_size; uint32_t rom_size;