mirror of
				https://github.com/N64Recomp/N64Recomp.git
				synced 2025-10-30 08:02:11 +00:00 
			
		
		
		
	Symbol file toml update (#52)
* Symbol input file mechanism * Migration to new toml lib --------- Co-authored-by: dcvz <david@dcvz.io>
This commit is contained in:
		
							parent
							
								
									26c5c2cbb8
								
							
						
					
					
						commit
						e0e52d1fc3
					
				
					 10 changed files with 748 additions and 367 deletions
				
			
		
							
								
								
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -7,6 +7,6 @@ | ||||||
| [submodule "lib/fmt"] | [submodule "lib/fmt"] | ||||||
| 	path = lib/fmt | 	path = lib/fmt | ||||||
| 	url = https://github.com/fmtlib/fmt | 	url = https://github.com/fmtlib/fmt | ||||||
| [submodule "lib/toml11"] | [submodule "lib/tomlplusplus"] | ||||||
| 	path = lib/toml11 | 	path = lib/tomlplusplus | ||||||
| 	url = https://github.com/ToruNiina/toml11 | 	url = https://github.com/marzer/tomlplusplus | ||||||
|  |  | ||||||
|  | @ -5,9 +5,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||||
| set(CMAKE_CXX_EXTENSIONS OFF) | set(CMAKE_CXX_EXTENSIONS OFF) | ||||||
| # set(CMAKE_CXX_VISIBILITY_PRESET hidden) | # set(CMAKE_CXX_VISIBILITY_PRESET hidden) | ||||||
| 
 | 
 | ||||||
| # fmtlib |  | ||||||
| add_subdirectory(lib/fmt) |  | ||||||
| 
 |  | ||||||
| # Rabbitizer | # Rabbitizer | ||||||
| project(rabbitizer) | project(rabbitizer) | ||||||
| add_library(rabbitizer STATIC) | add_library(rabbitizer STATIC) | ||||||
|  | @ -51,11 +48,20 @@ target_sources(rabbitizer PRIVATE | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c" |     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c" | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c") |     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c") | ||||||
| 
 | 
 | ||||||
| target_include_directories(rabbitizer PRIVATE | target_include_directories(rabbitizer PUBLIC | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/include" |     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/include" | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include" |     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include") | ||||||
|  | 
 | ||||||
|  | target_include_directories(rabbitizer PRIVATE | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables") |     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables") | ||||||
| 
 | 
 | ||||||
|  | # fmtlib | ||||||
|  | add_subdirectory(lib/fmt) | ||||||
|  | 
 | ||||||
|  | # tomlplusplus | ||||||
|  | set(TOML_ENABLE_FORMATTERS OFF) | ||||||
|  | add_subdirectory(lib/tomlplusplus) | ||||||
|  | 
 | ||||||
| # N64 recompiler | # N64 recompiler | ||||||
| project(N64Recomp) | project(N64Recomp) | ||||||
| add_executable(N64Recomp) | add_executable(N64Recomp) | ||||||
|  | @ -67,27 +73,18 @@ target_sources(N64Recomp PRIVATE | ||||||
|     ${CMAKE_SOURCE_DIR}/src/recompilation.cpp) |     ${CMAKE_SOURCE_DIR}/src/recompilation.cpp) | ||||||
| 
 | 
 | ||||||
| target_include_directories(N64Recomp PRIVATE | target_include_directories(N64Recomp PRIVATE | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/include" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/ELFIO" |     "${CMAKE_SOURCE_DIR}/lib/ELFIO" | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/fmt/include" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/toml11" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/include") |     "${CMAKE_SOURCE_DIR}/include") | ||||||
| 
 | 
 | ||||||
|     target_link_libraries(N64Recomp fmt rabbitizer) | target_link_libraries(N64Recomp fmt rabbitizer tomlplusplus::tomlplusplus) | ||||||
| 
 | 
 | ||||||
| # RSP recompiler | # RSP recompiler | ||||||
| project(RSPRecomp) | project(RSPRecomp) | ||||||
| add_executable(RSPRecomp) | add_executable(RSPRecomp) | ||||||
| 
 | 
 | ||||||
| target_include_directories(RSPRecomp PRIVATE | target_include_directories(RSPRecomp PRIVATE "${CMAKE_SOURCE_DIR}/include") | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/include" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/fmt/include" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/lib/toml11" |  | ||||||
|     "${CMAKE_SOURCE_DIR}/include") |  | ||||||
| 
 | 
 | ||||||
| target_link_libraries(RSPRecomp fmt rabbitizer) | target_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus) | ||||||
| 
 | 
 | ||||||
| target_sources(RSPRecomp PRIVATE | target_sources(RSPRecomp PRIVATE | ||||||
|     ${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) |     ${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) | ||||||
|  |  | ||||||
|  | @ -5,11 +5,12 @@ | ||||||
| #include <unordered_set> | #include <unordered_set> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| #include <cassert> | #include <cassert> | ||||||
|  | #include <iostream> | ||||||
| #include <filesystem> | #include <filesystem> | ||||||
| #include "rabbitizer.hpp" | #include "rabbitizer.hpp" | ||||||
| #include "fmt/format.h" | #include "fmt/format.h" | ||||||
| #include "fmt/ostream.h" | #include "fmt/ostream.h" | ||||||
| #include "toml.hpp" | #include <toml++/toml.hpp> | ||||||
| 
 | 
 | ||||||
| using InstrId = rabbitizer::InstrId::UniqueId; | using InstrId = rabbitizer::InstrId::UniqueId; | ||||||
| using Cop0Reg = rabbitizer::Registers::Rsp::Cop0; | using Cop0Reg = rabbitizer::Registers::Rsp::Cop0; | ||||||
|  | @ -541,39 +542,6 @@ void write_indirect_jumps(std::ofstream& output_file, const BranchTargets& branc | ||||||
|         "    return RspExitReason::UnhandledJumpTarget;\n", output_function_name); |         "    return RspExitReason::UnhandledJumpTarget;\n", output_function_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO de-hardcode these
 |  | ||||||
| // OoT njpgdspMain
 |  | ||||||
| //constexpr size_t rsp_text_offset = 0xB8BAD0;
 |  | ||||||
| //constexpr size_t rsp_text_size = 0xAF0;
 |  | ||||||
| //constexpr size_t rsp_text_address = 0x04001080;
 |  | ||||||
| //std::string rom_file_path = "../test/oot_mq_debug.z64";
 |  | ||||||
| //std::string output_file_path = "../test/rsp/njpgdspMain.cpp";
 |  | ||||||
| //std::string output_function_name = "njpgdspMain";
 |  | ||||||
| //const std::vector<uint32_t> extra_indirect_branch_targets{};
 |  | ||||||
| //const std::unordered_set<uint32_t> unsupported_instructions{};
 |  | ||||||
| 
 |  | ||||||
| // OoT aspMain
 |  | ||||||
| //constexpr size_t rsp_text_offset = 0xB89260;
 |  | ||||||
| //constexpr size_t rsp_text_size = 0xFB0;
 |  | ||||||
| //constexpr size_t rsp_text_address = 0x04001000;
 |  | ||||||
| //std::string rom_file_path = "../test/oot_mq_debug.z64";
 |  | ||||||
| //std::string output_file_path = "../test/rsp/aspMain.cpp";
 |  | ||||||
| //std::string output_function_name = "aspMain";
 |  | ||||||
| //const std::vector<uint32_t> extra_indirect_branch_targets{ 0x1F68, 0x1230, 0x114C, 0x1F18, 0x1E2C, 0x14F4, 0x1E9C, 0x1CB0, 0x117C, 0x17CC, 0x11E8, 0x1AA4, 0x1B34, 0x1190, 0x1C5C, 0x1220, 0x1784, 0x1830, 0x1A20, 0x1884, 0x1A84, 0x1A94, 0x1A48, 0x1BA0 };
 |  | ||||||
| //const std::unordered_set<uint32_t> unsupported_instructions{};
 |  | ||||||
| 
 |  | ||||||
| // MM's njpgdspMain is identical to OoT's
 |  | ||||||
| 
 |  | ||||||
| //// MM aspMain
 |  | ||||||
| //constexpr size_t rsp_text_offset = 0xC40FF0;
 |  | ||||||
| //constexpr size_t rsp_text_size = 0x1000;
 |  | ||||||
| //constexpr size_t rsp_text_address = 0x04001000;
 |  | ||||||
| //std::string rom_file_path = "../../MMRecomp/mm.us.rev1.z64"; // uncompressed rom!
 |  | ||||||
| //std::string output_file_path = "../../MMRecomp/rsp/aspMain.cpp";
 |  | ||||||
| //std::string output_function_name = "aspMain";
 |  | ||||||
| //const std::vector<uint32_t> extra_indirect_branch_targets{ 0x1F80, 0x1250, 0x1154, 0x1094, 0x1E0C, 0x1514, 0x1E7C, 0x1C90, 0x1180, 0x1808, 0x11E8, 0x1ADC, 0x1B6C, 0x1194, 0x1EF8, 0x1240, 0x17C0, 0x186C, 0x1A58, 0x18BC, 0x1ABC, 0x1ACC, 0x1A80, 0x1BD4 };
 |  | ||||||
| //const std::unordered_set<uint32_t> unsupported_instructions{};
 |  | ||||||
| 
 |  | ||||||
| #ifdef _MSC_VER | #ifdef _MSC_VER | ||||||
| inline uint32_t byteswap(uint32_t val) { | inline uint32_t byteswap(uint32_t val) { | ||||||
|     return _byteswap_ulong(val); |     return _byteswap_ulong(val); | ||||||
|  | @ -603,65 +571,106 @@ std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, c | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template <typename T> | template <typename T> | ||||||
| std::vector<T> toml_to_vec(const toml::value& branch_targets_data) { | std::vector<T> toml_to_vec(const toml::array* array) { | ||||||
| 	std::vector<T> ret; | 	std::vector<T> ret; | ||||||
| 
 | 
 | ||||||
| 	if (branch_targets_data.type() != toml::value_t::array) { |  | ||||||
| 		return ret; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Get the funcs array as an array type.
 |  | ||||||
| 	const std::vector<toml::value>& branch_targets_array = branch_targets_data.as_array(); |  | ||||||
| 
 |  | ||||||
| 	// Reserve room for all the funcs in the map.
 | 	// Reserve room for all the funcs in the map.
 | ||||||
| 	ret.reserve(branch_targets_array.size()); | 	ret.reserve(array->size()); | ||||||
| 	for (const toml::value& cur_target_val : branch_targets_array) { |     array->for_each([&ret](auto&& el) { | ||||||
| 		ret.push_back(cur_target_val.as_integer()); |         if constexpr (toml::is_integer<decltype(el)>) { | ||||||
| 	} |             ret.push_back(*el); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template <typename T> | ||||||
|  | std::unordered_set<T> toml_to_set(const toml::array* array) { | ||||||
|  |     std::unordered_set<T> ret; | ||||||
|  | 
 | ||||||
|  |     array->for_each([&ret](auto&& el) { | ||||||
|  |         if constexpr (toml::is_integer<decltype(el)>) { | ||||||
|  |             ret.insert(*el); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig& out) { | bool read_config(const std::filesystem::path& config_path, RSPRecompilerConfig& out) { | ||||||
|     std::ifstream config_file {config_path}; |  | ||||||
|     RSPRecompilerConfig ret{}; |     RSPRecompilerConfig ret{}; | ||||||
| 
 | 
 | ||||||
| 	try { | 	try { | ||||||
| 		const toml::value config_data = toml::parse(config_path); |         const toml::table config_data = toml::parse_file(config_path.u8string()); | ||||||
| 		std::filesystem::path basedir = std::filesystem::path{ config_path }.parent_path(); | 		std::filesystem::path basedir = std::filesystem::path{ config_path }.parent_path(); | ||||||
| 
 | 
 | ||||||
| 		ret.text_offset           = toml::find<uint32_t>(config_data, "text_offset"); |         std::optional<uint32_t> text_offset = config_data["text_offset"].value<uint32_t>(); | ||||||
| 		ret.text_size             = toml::find<uint32_t>(config_data, "text_size"); |         if (text_offset.has_value()) { | ||||||
| 		ret.text_address          = toml::find<uint32_t>(config_data, "text_address"); |             ret.text_offset = text_offset.value(); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing text_offset in config file", config_data.source()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 		ret.rom_file_path         = concat_if_not_empty(basedir, toml::find<std::string>(config_data, "rom_file_path")); |         std::optional<uint32_t> text_size = config_data["text_size"].value<uint32_t>(); | ||||||
| 		ret.output_file_path      = concat_if_not_empty(basedir, toml::find<std::string>(config_data, "output_file_path")); |         if (text_size.has_value()) { | ||||||
| 		ret.output_function_name  = toml::find<std::string>(config_data, "output_function_name"); |             ret.text_size = text_size.value(); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing text_size in config file", config_data.source()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::optional<uint32_t> text_address = config_data["text_address"].value<uint32_t>(); | ||||||
|  |         if (text_address.has_value()) { | ||||||
|  |             ret.text_address = text_address.value(); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing text_address in config file", config_data.source()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::optional<std::string> rom_file_path = config_data["rom_file_path"].value<std::string>(); | ||||||
|  |         if (rom_file_path.has_value()) { | ||||||
|  |             ret.rom_file_path = concat_if_not_empty(basedir, rom_file_path.value()); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing rom_file_path in config file", config_data.source()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::optional<std::string> output_file_path = config_data["output_file_path"].value<std::string>(); | ||||||
|  |         if (output_file_path.has_value()) { | ||||||
|  |             ret.output_file_path = concat_if_not_empty(basedir, output_file_path.value()); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing output_file_path in config file", config_data.source()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::optional<std::string> output_function_name = config_data["output_function_name"].value<std::string>(); | ||||||
|  |         if (output_function_name.has_value()) { | ||||||
|  |             ret.output_function_name = output_function_name.value(); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing output_function_name in config file", config_data.source()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 		// Extra indirect branch targets (optional)
 | 		// Extra indirect branch targets (optional)
 | ||||||
| 		const toml::value& branch_targets_data = toml::find_or<toml::value>(config_data, "extra_indirect_branch_targets", toml::value{}); |         const toml::node_view branch_targets_data = config_data["extra_indirect_branch_targets"]; | ||||||
| 		if (branch_targets_data.type() != toml::value_t::empty) { |         if (branch_targets_data.is_array()) { | ||||||
| 			ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(branch_targets_data); |             const toml::array* branch_targets_array = branch_targets_data.as_array(); | ||||||
| 		} |             ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(branch_targets_array); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 		// Unsupported_instructions (optional)
 | 		// Unsupported_instructions (optional)
 | ||||||
| 		const toml::value& unsupported_instructions_data = toml::find_or<toml::value>(config_data, "unsupported_instructions_data", toml::value{}); |         const toml::node_view unsupported_instructions_data = config_data["unsupported_instructions"]; | ||||||
| 		if (unsupported_instructions_data.type() != toml::value_t::empty) { |         if (unsupported_instructions_data.is_array()) { | ||||||
| 			ret.extra_indirect_branch_targets = toml_to_vec<uint32_t>(unsupported_instructions_data); |             const toml::array* unsupported_instructions_array = unsupported_instructions_data.as_array(); | ||||||
| 		} |             ret.unsupported_instructions = toml_to_set<uint32_t>(unsupported_instructions_array); | ||||||
| 	} |         } | ||||||
| 	catch (const toml::syntax_error& err) { |  | ||||||
| 		fmt::print(stderr, "Syntax error in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 	catch (const toml::type_error& err) { |  | ||||||
| 		fmt::print(stderr, "Incorrect type in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 	catch (const std::out_of_range& err) { |  | ||||||
| 		fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what()); |  | ||||||
| 		return false; |  | ||||||
| 	} | 	} | ||||||
|  |     catch (const toml::parse_error& err) { | ||||||
|  |         std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin <<  "):\n" << err.description() << std::endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     out = ret; |     out = ret; | ||||||
|     return true; |     return true; | ||||||
|  |  | ||||||
|  | @ -44,7 +44,7 @@ namespace RecompPort { | ||||||
|         std::string func_name; |         std::string func_name; | ||||||
|         uint32_t size_bytes; |         uint32_t size_bytes; | ||||||
| 
 | 
 | ||||||
|         FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(func_name), size_bytes(size_bytes) {} |         FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {} | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     struct ManualFunction { |     struct ManualFunction { | ||||||
|  | @ -53,7 +53,7 @@ namespace RecompPort { | ||||||
|         uint32_t vram; |         uint32_t vram; | ||||||
|         uint32_t size; |         uint32_t size; | ||||||
| 
 | 
 | ||||||
|         ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(func_name), section_name(std::move(section_name)), vram(vram), size(size) {} |         ManualFunction(const std::string& func_name, std::string section_name, uint32_t vram, uint32_t size) : func_name(std::move(func_name)), section_name(std::move(section_name)), vram(vram), size(size) {} | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     struct Config { |     struct Config { | ||||||
|  | @ -63,6 +63,8 @@ namespace RecompPort { | ||||||
|         bool single_file_output; |         bool single_file_output; | ||||||
|         bool use_absolute_symbols; |         bool use_absolute_symbols; | ||||||
|         std::filesystem::path elf_path; |         std::filesystem::path elf_path; | ||||||
|  |         std::filesystem::path symbols_file_path; | ||||||
|  |         std::filesystem::path rom_file_path; | ||||||
|         std::filesystem::path output_func_path; |         std::filesystem::path output_func_path; | ||||||
|         std::filesystem::path relocatable_sections_path; |         std::filesystem::path relocatable_sections_path; | ||||||
|         std::vector<std::string> stubbed_funcs; |         std::vector<std::string> stubbed_funcs; | ||||||
|  | @ -111,6 +113,7 @@ namespace RecompPort { | ||||||
| 
 | 
 | ||||||
|         Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false) |         Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half 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) {} |                 : 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 { |     enum class RelocType : uint8_t { | ||||||
|  | @ -130,7 +133,6 @@ namespace RecompPort { | ||||||
|         uint32_t symbol_index; |         uint32_t symbol_index; | ||||||
|         uint32_t target_section; |         uint32_t target_section; | ||||||
|         RelocType type; |         RelocType type; | ||||||
|         bool needs_relocation; |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     struct Section { |     struct Section { | ||||||
|  | @ -175,6 +177,10 @@ namespace RecompPort { | ||||||
|             rom.reserve(8 * 1024 * 1024); |             rom.reserve(8 * 1024 * 1024); | ||||||
|             executable_section_count = 0; |             executable_section_count = 0; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, Context& out); | ||||||
|  | 
 | ||||||
|  |         Context() = default; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats); |     bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats); | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								lib/fmt
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								lib/fmt
									
										
									
									
									
								
							|  | @ -1 +1 @@ | ||||||
| Subproject commit d2e89c8b080394e996d449371267365c223ca76b | Subproject commit 8e728044f673774160f43b44a07c6b185352310f | ||||||
|  | @ -1 +0,0 @@ | ||||||
| Subproject commit d47fe788bcb08c9d0d2a73954a0dfaf512964fdc |  | ||||||
							
								
								
									
										1
									
								
								lib/tomlplusplus
									
										
									
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								lib/tomlplusplus
									
										
									
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit 1f7884e59165e517462f922e7b6de131bd9844f3 | ||||||
|  | @ -19,31 +19,31 @@ struct RegState { | ||||||
| 	bool valid_addiu; | 	bool valid_addiu; | ||||||
| 	bool valid_addend; | 	bool valid_addend; | ||||||
| 	// For tracking a register that has been loaded from RAM
 | 	// For tracking a register that has been loaded from RAM
 | ||||||
| uint32_t loaded_lw_vram; | 	uint32_t loaded_lw_vram; | ||||||
| uint32_t loaded_addu_vram; | 	uint32_t loaded_addu_vram; | ||||||
| uint32_t loaded_address; | 	uint32_t loaded_address; | ||||||
| uint8_t loaded_addend_reg; | 	uint8_t loaded_addend_reg; | ||||||
| bool valid_loaded; | 	bool valid_loaded; | ||||||
| 
 | 
 | ||||||
| RegState() = default; | 	RegState() = default; | ||||||
| 
 | 
 | ||||||
| void invalidate() { | 	void invalidate() { | ||||||
| 	prev_lui = 0; | 		prev_lui = 0; | ||||||
| 	prev_addiu_vram = 0; | 		prev_addiu_vram = 0; | ||||||
| 	prev_addu_vram = 0; | 		prev_addu_vram = 0; | ||||||
| 	prev_addend_reg = 0; | 		prev_addend_reg = 0; | ||||||
| 
 | 
 | ||||||
| 	valid_lui = false; | 		valid_lui = false; | ||||||
| 	valid_addiu = false; | 		valid_addiu = false; | ||||||
| 	valid_addend = false; | 		valid_addend = false; | ||||||
| 
 | 
 | ||||||
| 	loaded_lw_vram = 0; | 		loaded_lw_vram = 0; | ||||||
| 	loaded_addu_vram = 0; | 		loaded_addu_vram = 0; | ||||||
| 	loaded_address = 0; | 		loaded_address = 0; | ||||||
| 	loaded_addend_reg = 0; | 		loaded_addend_reg = 0; | ||||||
| 
 | 
 | ||||||
| 	valid_loaded = false; | 		valid_loaded = false; | ||||||
| } | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| using InstrId = rabbitizer::InstrId::UniqueId; | using InstrId = rabbitizer::InstrId::UniqueId; | ||||||
|  |  | ||||||
							
								
								
									
										629
									
								
								src/config.cpp
									
										
									
									
									
								
							
							
						
						
									
										629
									
								
								src/config.cpp
									
										
									
									
									
								
							|  | @ -1,94 +1,80 @@ | ||||||
| #include <source_location> | #include <source_location> | ||||||
| 
 | 
 | ||||||
| #include "toml.hpp" | #include <toml++/toml.hpp> | ||||||
| #include "fmt/format.h" | #include "fmt/format.h" | ||||||
| #include "recomp_port.h" | #include "recomp_port.h" | ||||||
| 
 | 
 | ||||||
| // Error type for invalid values in the config file.
 | std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) { | ||||||
| struct value_error : public toml::exception { |  | ||||||
| 	public: |  | ||||||
| 		explicit value_error(const std::string& what_arg, const toml::source_location& loc) |  | ||||||
| 			: exception(loc), what_(what_arg) { |  | ||||||
| 		} |  | ||||||
| 		virtual ~value_error() noexcept override = default; |  | ||||||
| 		virtual const char* what() const noexcept override { return what_.c_str(); } |  | ||||||
| 
 |  | ||||||
| 	protected: |  | ||||||
| 		std::string what_; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::value& manual_funcs_data) { |  | ||||||
| 	std::vector<RecompPort::ManualFunction> ret; | 	std::vector<RecompPort::ManualFunction> ret; | ||||||
| 
 | 
 | ||||||
| 	if (manual_funcs_data.type() != toml::value_t::array) { |  | ||||||
| 		return ret; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Get the funcs array as an array type.
 |  | ||||||
| 	const toml::array& manual_funcs_array = manual_funcs_data.as_array(); |  | ||||||
| 
 |  | ||||||
| 	// Reserve room for all the funcs in the map.
 | 	// Reserve room for all the funcs in the map.
 | ||||||
| 	ret.reserve(manual_funcs_array.size()); | 	ret.reserve(manual_funcs_array->size()); | ||||||
| 	for (const toml::value& cur_func_val : manual_funcs_array) { |     manual_funcs_array->for_each([&ret](auto&& el) { | ||||||
| 		const std::string& func_name = toml::find<std::string>(cur_func_val, "name"); |         if constexpr (toml::is_table<decltype(el)>) { | ||||||
| 		const std::string& section_name = toml::find<std::string>(cur_func_val, "section"); |             std::optional<std::string> func_name = el["name"].template value<std::string>(); | ||||||
| 		uint32_t vram_in = toml::find<uint32_t>(cur_func_val, "vram"); |             std::optional<std::string> section_name = el["section"].template value<std::string>(); | ||||||
| 		uint32_t size = toml::find<uint32_t>(cur_func_val, "size"); |             std::optional<uint32_t> vram_in = el["vram"].template value<uint32_t>(); | ||||||
|  |             std::optional<uint32_t> size = el["size"].template value<uint32_t>(); | ||||||
| 
 | 
 | ||||||
| 		ret.emplace_back(func_name, section_name, vram_in, size); |             if (func_name.has_value() && section_name.has_value() && vram_in.has_value() && size.has_value()) { | ||||||
| 	} |                 ret.emplace_back(func_name.value(), section_name.value(), vram_in.value(), size.value()); | ||||||
|  |             } else { | ||||||
|  |                 throw toml::parse_error("Missing required value in manual_funcs array", el.source()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing required value in manual_funcs array", el.source()); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<std::string> get_stubbed_funcs(const toml::value& patches_data) { | std::vector<std::string> get_stubbed_funcs(const toml::table* patches_data) { | ||||||
| 	std::vector<std::string> stubbed_funcs{}; | 	std::vector<std::string> stubbed_funcs{}; | ||||||
| 
 | 
 | ||||||
| 	// Check if the stubs array exists.
 | 	// Check if the stubs array exists.
 | ||||||
| 	const auto& stubs_data = toml::find_or<toml::value>(patches_data, "stubs", toml::value{}); |     const toml::node_view stubs_data = (*patches_data)["stubs"]; | ||||||
| 
 | 
 | ||||||
| 	if (stubs_data.type() == toml::value_t::empty) { |     if (stubs_data.is_array()) { | ||||||
| 		// No stubs, nothing to do here.
 |         const toml::array* stubs_array = stubs_data.as_array(); | ||||||
| 		return stubbed_funcs; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Get the stubs array as an array type.
 |         // Make room for all the stubs in the array.
 | ||||||
| 	const toml::array& stubs_array = stubs_data.as_array(); |         stubbed_funcs.reserve(stubs_array->size()); | ||||||
| 
 | 
 | ||||||
| 	// Make room for all the stubs in the array.
 |         // Gather the stubs and place them into the array.
 | ||||||
| 	stubbed_funcs.resize(stubs_array.size()); |         stubs_array->for_each([&stubbed_funcs](auto&& el) { | ||||||
| 
 |             if constexpr (toml::is_string<decltype(el)>) { | ||||||
| 	// Gather the stubs and place them into the array.
 |                 stubbed_funcs.push_back(*el); | ||||||
| 	for (size_t stub_idx = 0; stub_idx < stubs_array.size(); stub_idx++) { |             } | ||||||
| 		// Copy the entry into the stubbed function list.
 |             else { | ||||||
| 		stubbed_funcs[stub_idx] = stubs_array[stub_idx].as_string(); |                 throw toml::parse_error("Invalid stubbed function", el.source()); | ||||||
| 	} |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	return stubbed_funcs; | 	return stubbed_funcs; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<std::string> get_ignored_funcs(const toml::value& patches_data) { | std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) { | ||||||
| 	std::vector<std::string> ignored_funcs{}; | 	std::vector<std::string> ignored_funcs{}; | ||||||
| 
 | 
 | ||||||
| 	// Check if the ignored funcs array exists.
 | 	// Check if the ignored funcs array exists.
 | ||||||
| 	const auto& ignored_funcs_data = toml::find_or<toml::value>(patches_data, "ignored", toml::value{}); |     const toml::node_view ignored_funcs_data = (*patches_data)["ignored"]; | ||||||
| 
 | 
 | ||||||
| 	if (ignored_funcs_data.type() == toml::value_t::empty) { |     if (ignored_funcs_data.is_array()) { | ||||||
| 		// No stubs, nothing to do here.
 |         const toml::array* ignored_funcs_array = ignored_funcs_data.as_array(); | ||||||
| 		return ignored_funcs; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Get the ignored funcs array as an array type.
 |         // Make room for all the ignored funcs in the array.
 | ||||||
| 	const toml::array& ignored_funcs_array = ignored_funcs_data.as_array(); |         ignored_funcs.reserve(ignored_funcs_array->size()); | ||||||
| 
 | 
 | ||||||
| 	// Make room for all the ignored funcs in the array.
 |         // Gather the stubs and place them into the array.
 | ||||||
| 	ignored_funcs.resize(ignored_funcs_array.size()); |         ignored_funcs_array->for_each([&ignored_funcs](auto&& el) { | ||||||
| 
 |             if constexpr (toml::is_string<decltype(el)>) { | ||||||
| 	// Gather the stubs and place them into the array.
 |                 ignored_funcs.push_back(*el); | ||||||
| 	for (size_t stub_idx = 0; stub_idx < ignored_funcs_array.size(); stub_idx++) { |             } | ||||||
| 		// Copy the entry into the ignored function list.
 |         }); | ||||||
| 		ignored_funcs[stub_idx] = ignored_funcs_array[stub_idx].as_string(); |     } | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return ignored_funcs; | 	return ignored_funcs; | ||||||
| } | } | ||||||
|  | @ -98,120 +84,142 @@ std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{ | ||||||
| 	{"s32", RecompPort::FunctionArgType::s32}, | 	{"s32", RecompPort::FunctionArgType::s32}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| std::vector<RecompPort::FunctionArgType> parse_args(const toml::array& args_in) { | std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) { | ||||||
| 	std::vector<RecompPort::FunctionArgType> ret(args_in.size()); | 	std::vector<RecompPort::FunctionArgType> ret(args_in->size()); | ||||||
| 
 | 
 | ||||||
| 	for (size_t arg_idx = 0; arg_idx < args_in.size(); arg_idx++) { |     args_in->for_each([&ret](auto&& el) { | ||||||
| 		const toml::value& arg_val = args_in[arg_idx]; |         if constexpr (toml::is_string<decltype(el)>) { | ||||||
| 		const std::string& arg_str = arg_val.as_string(); |             const std::string& arg_str = *el; | ||||||
| 
 | 
 | ||||||
| 		// Check if the argument type string is valid.
 |             // Check if the argument type string is valid.
 | ||||||
| 		auto type_find = arg_type_map.find(arg_str); |             auto type_find = arg_type_map.find(arg_str); | ||||||
| 		if (type_find == arg_type_map.end()) { |             if (type_find == arg_type_map.end()) { | ||||||
| 			// It's not, so throw an error (and make it look like a normal toml one).
 |                 // It's not, so throw an error (and make it look like a normal toml one).
 | ||||||
| 			throw toml::type_error(toml::detail::format_underline( |                 throw toml::parse_error(("Invalid argument type: " + arg_str).c_str(), el.source()); | ||||||
| 				std::string{ std::source_location::current().function_name() } + ": invalid function arg type", { |             } | ||||||
| 					{arg_val.location(), ""} |             ret.push_back(type_find->second); | ||||||
| 				}), arg_val.location()); |         } | ||||||
| 		} |         else { | ||||||
| 		ret[arg_idx] = type_find->second; |             throw toml::parse_error("Invalid function argument entry", el.source()); | ||||||
| 	} |         } | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::value& patches_data) { | RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) { | ||||||
| 	RecompPort::DeclaredFunctionMap declared_funcs{}; | 	RecompPort::DeclaredFunctionMap declared_funcs{}; | ||||||
| 
 | 
 | ||||||
| 	// Check if the func array exists.
 | 	// Check if the func array exists.
 | ||||||
| 	const toml::value& funcs_data = toml::find_or<toml::value>(patches_data, "func", toml::value{}); |     const toml::node_view funcs_data = (*patches_data)["func"]; | ||||||
| 	if (funcs_data.type() == toml::value_t::empty) { |  | ||||||
| 		// No func array, nothing to do here
 |  | ||||||
| 		return declared_funcs; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Get the funcs array as an array type.
 |     if (funcs_data.is_array()) { | ||||||
| 	const toml::array& funcs_array = funcs_data.as_array(); |         const toml::array* funcs_array = funcs_data.as_array(); | ||||||
| 
 | 
 | ||||||
| 	// Reserve room for all the funcs in the map.
 |         // Reserve room for all the funcs in the map.
 | ||||||
| 	declared_funcs.reserve(funcs_array.size()); |         declared_funcs.reserve(funcs_array->size()); | ||||||
| 	for (const toml::value& cur_func_val : funcs_array) { | 
 | ||||||
| 		const std::string& func_name = toml::find<std::string>(cur_func_val, "name"); |         // Gather the funcs and place them into the map.
 | ||||||
| 		const toml::array& args_in = toml::find<toml::array>(cur_func_val, "args"); |         funcs_array->for_each([&declared_funcs](auto&& el) { | ||||||
| 		 |             if constexpr (toml::is_table<decltype(el)>) { | ||||||
| 		declared_funcs.emplace(func_name, parse_args(args_in)); |                 std::optional<std::string> func_name = el["name"].template value<std::string>(); | ||||||
| 	} |                 toml::node_view args_in = el["args"]; | ||||||
|  | 
 | ||||||
|  |                 if (func_name.has_value() && args_in.is_array()) { | ||||||
|  |                     const toml::array* args_array = args_in.as_array(); | ||||||
|  |                     declared_funcs.emplace(func_name.value(), parse_args(args_array)); | ||||||
|  |                 } else { | ||||||
|  |                     throw toml::parse_error("Missing required value in func array", el.source()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 throw toml::parse_error("Invalid declared function entry", el.source()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	return declared_funcs; | 	return declared_funcs; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::value& patches_data) { | std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) { | ||||||
| 	std::vector<RecompPort::FunctionSize> func_sizes{}; | 	std::vector<RecompPort::FunctionSize> func_sizes{}; | ||||||
| 
 | 
 | ||||||
| 	// Check if the func size array exists.
 | 	// Check if the func size array exists.
 | ||||||
| 	const toml::value& sizes_data = toml::find_or<toml::value>(patches_data, "function_sizes", toml::value{}); |     const toml::node_view funcs_data = (*patches_data)["function_sizes"]; | ||||||
| 	if (sizes_data.type() == toml::value_t::empty) { |     if (funcs_data.is_array()) { | ||||||
| 		// No func size array, nothing to do here
 |         const toml::array* sizes_array = funcs_data.as_array(); | ||||||
| 		return func_sizes; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Get the funcs array as an array type.
 |         // Copy all the sizes into the output vector.
 | ||||||
| 	const toml::array& sizes_array = sizes_data.as_array(); |         sizes_array->for_each([&func_sizes](auto&& el) { | ||||||
|  |             if constexpr (toml::is_table<decltype(el)>) { | ||||||
|  |                 const toml::table& cur_size = *el.as_table(); | ||||||
| 
 | 
 | ||||||
| 	// Reserve room for all the funcs in the map.
 |                 // Get the function name and size.
 | ||||||
| 	func_sizes.reserve(sizes_array.size()); |                 std::optional<std::string> func_name = cur_size["name"].value<std::string>(); | ||||||
| 	for (const toml::value& cur_func_size : sizes_array) { |                 std::optional<uint32_t> func_size = cur_size["size"].value<uint32_t>(); | ||||||
| 		const std::string& func_name = toml::find<std::string>(cur_func_size, "name"); |  | ||||||
| 		uint32_t func_size = toml::find<uint32_t>(cur_func_size, "size"); |  | ||||||
| 
 | 
 | ||||||
| 		// Make sure the size is divisible by 4
 |                 if (func_name.has_value() && func_size.has_value()) { | ||||||
| 		if (func_size & (4 - 1)) { |                     // Make sure the size is divisible by 4
 | ||||||
| 			// It's not, so throw an error (and make it look like a normal toml one).
 |                     if (func_size.value() & (4 - 1)) { | ||||||
| 			throw toml::type_error(toml::detail::format_underline( |                         // It's not, so throw an error (and make it look like a normal toml one).
 | ||||||
| 				std::string{ std::source_location::current().function_name() } + ": function size not divisible by 4", { |                         throw toml::parse_error("Function size is not divisible by 4", el.source()); | ||||||
| 					{cur_func_size.location(), ""} |                     } | ||||||
| 				}), cur_func_size.location()); |                 } | ||||||
| 		} |                 else { | ||||||
|  |                     throw toml::parse_error("Manually size function is missing required value(s)", el.source()); | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
| 		func_sizes.emplace_back(func_name, func_size); |                 func_sizes.emplace_back(func_name.value(), func_size.value()); | ||||||
| 	} |             } | ||||||
|  |             else { | ||||||
|  |                 throw toml::parse_error("Invalid manually sized function entry", el.source()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	return func_sizes; | 	return func_sizes; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::value& patches_data) { | std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) { | ||||||
| 	std::vector<RecompPort::InstructionPatch> ret; | 	std::vector<RecompPort::InstructionPatch> ret; | ||||||
| 
 | 
 | ||||||
| 	// Check if the instruction patch array exists.
 | 	// Check if the instruction patch array exists.
 | ||||||
| 	const toml::value& insn_patch_data = toml::find_or<toml::value>(patches_data, "instruction", toml::value{}); |     const toml::node_view insn_patch_data = (*patches_data)["instruction"]; | ||||||
| 	if (insn_patch_data.type() == toml::value_t::empty) { |  | ||||||
| 		// No instruction patch array, nothing to do here
 |  | ||||||
| 		return ret; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Get the instruction patch array as an array type.
 |     if (insn_patch_data.is_array()) { | ||||||
| 	const toml::array& insn_patch_array = insn_patch_data.as_array(); |         const toml::array* insn_patch_array = insn_patch_data.as_array(); | ||||||
| 	ret.resize(insn_patch_array.size()); |         ret.reserve(insn_patch_array->size()); | ||||||
| 
 | 
 | ||||||
| 	// Copy all the patches into the output vector.
 |         // Copy all the patches into the output vector.
 | ||||||
| 	for (size_t patch_idx = 0; patch_idx < insn_patch_array.size(); patch_idx++) { |         insn_patch_array->for_each([&ret](auto&& el) { | ||||||
| 		const toml::value& cur_patch = insn_patch_array[patch_idx]; |             if constexpr (toml::is_table<decltype(el)>) { | ||||||
|  |                 const toml::table& cur_patch = *el.as_table(); | ||||||
| 
 | 
 | ||||||
| 		// Get the vram and make sure it's 4-byte aligned.
 |                 // Get the vram and make sure it's 4-byte aligned.
 | ||||||
| 		const toml::value& vram_value = toml::find<toml::value>(cur_patch, "vram"); |                 std::optional<uint32_t> vram = cur_patch["vram"].value<uint32_t>(); | ||||||
| 		int32_t vram = toml::get<int32_t>(vram_value); |                 std::optional<std::string> func_name = cur_patch["func"].value<std::string>(); | ||||||
| 		if (vram & 0b11) { |                 std::optional<uint32_t> value = cur_patch["value"].value<uint32_t>(); | ||||||
| 			// Not properly aligned, so throw an error (and make it look like a normal toml one).
 |  | ||||||
| 			throw value_error(toml::detail::format_underline( |  | ||||||
| 				std::string{ std::source_location::current().function_name() } + ": instruction vram is not 4-byte aligned!", { |  | ||||||
| 					{vram_value.location(), ""} |  | ||||||
| 				}), vram_value.location()); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		ret[patch_idx].func_name = toml::find<std::string>(cur_patch, "func"); |                 if (!vram.has_value() || !func_name.has_value() || !value.has_value()) { | ||||||
| 		ret[patch_idx].vram = toml::find<int32_t>(cur_patch, "vram"); |                     throw toml::parse_error("Instruction patch is missing required value(s)", el.source()); | ||||||
| 		ret[patch_idx].value = toml::find<uint32_t>(cur_patch, "value"); |                 } | ||||||
| 	} | 
 | ||||||
|  |                 if (vram.value() & 0b11) { | ||||||
|  |                     // Not properly aligned, so throw an error (and make it look like a normal toml one).
 | ||||||
|  |                     throw toml::parse_error("Instruction patch is not word-aligned", el.source()); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 ret.push_back(RecompPort::InstructionPatch{ | ||||||
|  |                     .func_name = func_name.value(), | ||||||
|  |                     .vram = (int32_t)vram.value(), | ||||||
|  |                     .value = value.value(), | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 throw toml::parse_error("Invalid instruction patch entry", el.source()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  | @ -227,71 +235,296 @@ RecompPort::Config::Config(const char* path) { | ||||||
| 	// Start this config out as bad so that it has to finish parsing without errors to be good.
 | 	// Start this config out as bad so that it has to finish parsing without errors to be good.
 | ||||||
| 	entrypoint = 0; | 	entrypoint = 0; | ||||||
| 	bad = true; | 	bad = true; | ||||||
|  |     toml::table config_data{}; | ||||||
| 
 | 
 | ||||||
| 	try { |     try { | ||||||
| 		const toml::value config_data = toml::parse(path); |         config_data = toml::parse_file(path); | ||||||
| 		std::filesystem::path basedir = std::filesystem::path{ path }.parent_path(); |         std::filesystem::path basedir = std::filesystem::path{ path }.parent_path(); | ||||||
| 
 | 
 | ||||||
| 		// Input section (required)
 |         // Input section (required)
 | ||||||
| 		const toml::value& input_data = toml::find<toml::value>(config_data, "input"); |         const auto input_data = config_data["input"]; | ||||||
|  |         const auto entrypoint_data = input_data["entrypoint"]; | ||||||
| 
 | 
 | ||||||
| 		if (input_data.contains("entrypoint")) { |         if (entrypoint_data) { | ||||||
| 			entrypoint = toml::find<int32_t>(input_data, "entrypoint"); |             const auto entrypoint_value = entrypoint_data.value<uint32_t>(); | ||||||
| 			has_entrypoint = true; |             if (entrypoint_value.has_value()) { | ||||||
| 		} |                 entrypoint = (int32_t)entrypoint_value.value(); | ||||||
| 		else { |                 has_entrypoint = true; | ||||||
| 			has_entrypoint = false; |             } | ||||||
| 		} |             else { | ||||||
| 		elf_path                  = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "elf_path")); |                 throw toml::parse_error("Invalid entrypoint", entrypoint_data.node()->source()); | ||||||
| 		output_func_path          = concat_if_not_empty(basedir, toml::find<std::string>(input_data, "output_func_path")); |             } | ||||||
| 		relocatable_sections_path = concat_if_not_empty(basedir, toml::find_or<std::string>(input_data, "relocatable_sections_path", "")); |         } | ||||||
| 		uses_mips3_float_mode     = toml::find_or<bool>(input_data, "uses_mips3_float_mode", false); |         else { | ||||||
| 		bss_section_suffix        = toml::find_or<std::string>(input_data, "bss_section_suffix", ".bss"); |             has_entrypoint = false; | ||||||
| 		single_file_output        = toml::find_or<bool>(input_data, "single_file_output", false); |         } | ||||||
| 		use_absolute_symbols      = toml::find_or<bool>(input_data, "use_absolute_symbols", false); |  | ||||||
| 
 | 
 | ||||||
| 		// Manual functions (optional)
 |         std::optional<std::string> elf_path_opt = input_data["elf_path"].value<std::string>(); | ||||||
| 		const toml::value& manual_functions_data = toml::find_or<toml::value>(input_data, "manual_funcs", toml::value{}); |         if (elf_path_opt.has_value()) { | ||||||
| 		if (manual_functions_data.type() != toml::value_t::empty) { |             elf_path = concat_if_not_empty(basedir, elf_path_opt.value()); | ||||||
| 			manual_functions = get_manual_funcs(manual_functions_data); |         } | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		// Patches section (optional)
 |         std::optional<std::string> symbols_file_path_opt = input_data["symbols_file_path"].value<std::string>(); | ||||||
| 		const toml::value& patches_data = toml::find_or<toml::value>(config_data, "patches", toml::value{}); |         if (symbols_file_path_opt.has_value()) { | ||||||
| 		if (patches_data.type() != toml::value_t::empty) { |             symbols_file_path = concat_if_not_empty(basedir, symbols_file_path_opt.value()); | ||||||
| 			// Stubs array (optional)
 |         } | ||||||
| 			stubbed_funcs = get_stubbed_funcs(patches_data); |  | ||||||
| 
 | 
 | ||||||
| 			// Ignored funcs array (optional)
 |         std::optional<std::string> rom_file_path_opt = input_data["rom_file_path"].value<std::string>(); | ||||||
| 			ignored_funcs = get_ignored_funcs(patches_data); |         if (rom_file_path_opt.has_value()) { | ||||||
|  |             rom_file_path = concat_if_not_empty(basedir, rom_file_path_opt.value()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 			// Functions (optional)
 |         std::optional<std::string> output_func_path_opt = input_data["output_func_path"].value<std::string>(); | ||||||
| 			declared_funcs = get_declared_funcs(patches_data); |         if (output_func_path_opt.has_value()) { | ||||||
|  |             output_func_path = concat_if_not_empty(basedir, output_func_path_opt.value()); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             throw toml::parse_error("Missing output_func_path in config file", input_data.node()->source()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 			// Single-instruction patches (optional)
 |         std::optional<std::string> relocatable_sections_path_opt = input_data["relocatable_sections_path"].value<std::string>(); | ||||||
| 			instruction_patches = get_instruction_patches(patches_data); |         if (relocatable_sections_path_opt.has_value()) { | ||||||
|  |             relocatable_sections_path = concat_if_not_empty(basedir, relocatable_sections_path_opt.value()); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             relocatable_sections_path = ""; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 			// Manual function sizes (optional)
 |         std::optional<bool> uses_mips3_float_mode_opt = input_data["uses_mips3_float_mode"].value<bool>(); | ||||||
| 			manual_func_sizes = get_func_sizes(patches_data); |         if (uses_mips3_float_mode_opt.has_value()) { | ||||||
| 		} |             uses_mips3_float_mode = uses_mips3_float_mode_opt.value(); | ||||||
| 	} |         } | ||||||
| 	catch (const toml::syntax_error& err) { |         else { | ||||||
| 		fmt::print(stderr, "Syntax error in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); |             uses_mips3_float_mode = false; | ||||||
| 		return; |         } | ||||||
| 	} | 
 | ||||||
| 	catch (const toml::type_error& err) { |         std::optional<std::string> bss_section_suffix_opt = input_data["bss_section_suffix"].value<std::string>(); | ||||||
| 		fmt::print(stderr, "Incorrect type in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); |         if (bss_section_suffix_opt.has_value()) { | ||||||
| 		return; |             bss_section_suffix = bss_section_suffix_opt.value(); | ||||||
| 	} |         } | ||||||
| 	catch (const value_error& err) { |         else { | ||||||
| 		fmt::print(stderr, "Invalid value in config file on line {}, full error:\n{}\n", err.location().line(), err.what()); |             bss_section_suffix = ".bss"; | ||||||
| 		return; |         } | ||||||
| 	} | 
 | ||||||
| 	catch (const std::out_of_range& err) { |         std::optional<bool> single_file_output_opt = input_data["single_file_output"].value<bool>(); | ||||||
| 		fmt::print(stderr, "Missing value in config file, full error:\n{}\n", err.what()); |         if (single_file_output_opt.has_value()) { | ||||||
| 		return; |             single_file_output = single_file_output_opt.value(); | ||||||
| 	} |         } | ||||||
|  |         else { | ||||||
|  |             single_file_output = false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::optional<bool> use_absolute_symbols_opt = input_data["use_absolute_symbols"].value<bool>(); | ||||||
|  |         if (use_absolute_symbols_opt.has_value()) { | ||||||
|  |             use_absolute_symbols = use_absolute_symbols_opt.value(); | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             use_absolute_symbols = false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Manual functions (optional)
 | ||||||
|  |         toml::node_view manual_functions_data = input_data["manual_funcs"]; | ||||||
|  |         if (manual_functions_data.is_array()) { | ||||||
|  |             const toml::array* array = manual_functions_data.as_array(); | ||||||
|  |             get_manual_funcs(array); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Patches section (optional)
 | ||||||
|  |         toml::node_view patches_data = config_data["patches"]; | ||||||
|  |         if (patches_data.is_table()) { | ||||||
|  |             const toml::table* table = patches_data.as_table(); | ||||||
|  | 
 | ||||||
|  |             // Stubs array (optional)
 | ||||||
|  |             stubbed_funcs = get_stubbed_funcs(table); | ||||||
|  | 
 | ||||||
|  |             // Ignored funcs array (optional)
 | ||||||
|  |             ignored_funcs = get_ignored_funcs(table); | ||||||
|  | 
 | ||||||
|  |             // Functions (optional)
 | ||||||
|  |             declared_funcs = get_declared_funcs(table); | ||||||
|  | 
 | ||||||
|  |             // Single-instruction patches (optional)
 | ||||||
|  |             instruction_patches = get_instruction_patches(table); | ||||||
|  | 
 | ||||||
|  |             // Manual function sizes (optional)
 | ||||||
|  |             manual_func_sizes = get_func_sizes(table); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     catch (const toml::parse_error& err) { | ||||||
|  |         std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin <<  "):\n" << err.description() << std::endl; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 	// No errors occured, so mark this config file as good.
 | 	// No errors occured, so mark this config file as good.
 | ||||||
| 	bad = false; | 	bad = false; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | const std::unordered_map<std::string, RecompPort::RelocType> reloc_type_name_map { | ||||||
|  | 	{ "R_MIPS_NONE", RecompPort::RelocType::R_MIPS_NONE }, | ||||||
|  | 	{ "R_MIPS_16", RecompPort::RelocType::R_MIPS_16 }, | ||||||
|  | 	{ "R_MIPS_32", RecompPort::RelocType::R_MIPS_32 }, | ||||||
|  | 	{ "R_MIPS_REL32", RecompPort::RelocType::R_MIPS_REL32 }, | ||||||
|  | 	{ "R_MIPS_26", RecompPort::RelocType::R_MIPS_26 }, | ||||||
|  | 	{ "R_MIPS_HI16", RecompPort::RelocType::R_MIPS_HI16 }, | ||||||
|  | 	{ "R_MIPS_LO16", RecompPort::RelocType::R_MIPS_LO16 }, | ||||||
|  | 	{ "R_MIPS_GPREL16", RecompPort::RelocType::R_MIPS_GPREL16 }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) { | ||||||
|  | 	auto find_it = reloc_type_name_map.find(reloc_type_name); | ||||||
|  | 	if (find_it != reloc_type_name_map.end()) { | ||||||
|  | 		return find_it->second; | ||||||
|  | 	} | ||||||
|  | 	return RecompPort::RelocType::R_MIPS_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, RecompPort::Context& out) { | ||||||
|  | 	RecompPort::Context ret{}; | ||||||
|  | 
 | ||||||
|  | 	try { | ||||||
|  | 		const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); | ||||||
|  |         const toml::node_view config_sections_value = config_data["section"]; | ||||||
|  | 
 | ||||||
|  |         if (!config_sections_value.is_array()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const toml::array* config_sections = config_sections_value.as_array(); | ||||||
|  |         ret.section_functions.resize(config_sections->size()); | ||||||
|  | 
 | ||||||
|  |         config_sections->for_each([&ret, &rom](auto&& el) { | ||||||
|  |             if constexpr (toml::is_table<decltype(el)>) { | ||||||
|  |                 std::optional<uint32_t> rom_addr = el["rom"].template value<uint32_t>(); | ||||||
|  |                 std::optional<uint32_t> vram_addr = el["vram"].template value<uint32_t>(); | ||||||
|  |                 std::optional<uint32_t> size = el["size"].template value<uint32_t>(); | ||||||
|  |                 std::optional<std::string> name = el["name"].template value<std::string>(); | ||||||
|  | 
 | ||||||
|  |                 if (!rom_addr.has_value() || !vram_addr.has_value() || !size.has_value() || !name.has_value()) { | ||||||
|  |                     throw toml::parse_error("Section entry missing required field(s)", el.source()); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 size_t section_index = ret.sections.size(); | ||||||
|  | 
 | ||||||
|  |                 Section& section = ret.sections.emplace_back(Section{}); | ||||||
|  |                 section.rom_addr = rom_addr.value(); | ||||||
|  |                 section.ram_addr = vram_addr.value(); | ||||||
|  |                 section.size = size.value(); | ||||||
|  |                 section.name = name.value(); | ||||||
|  |                 section.executable = true; | ||||||
|  | 
 | ||||||
|  |                 // Read functions for the section.
 | ||||||
|  |                 const toml::node_view cur_functions_value = el["functions"]; | ||||||
|  |                 if (!cur_functions_value.is_array()) { | ||||||
|  |                     throw toml::parse_error("Invalid functions array", cur_functions_value.node()->source()); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const toml::array* cur_functions = cur_functions_value.as_array(); | ||||||
|  |                 cur_functions->for_each([&ret, &rom, §ion, section_index](auto&& func_el) { | ||||||
|  |                     size_t function_index = ret.functions.size(); | ||||||
|  | 
 | ||||||
|  |                     if constexpr (toml::is_table<decltype(func_el)>) { | ||||||
|  |                         std::optional<std::string> name = func_el["name"].template value<std::string>(); | ||||||
|  |                         std::optional<uint32_t> vram_addr = func_el["vram"].template value<uint32_t>(); | ||||||
|  |                         std::optional<uint32_t> func_size_ = func_el["size"].template value<uint32_t>(); | ||||||
|  | 
 | ||||||
|  |                         if (!name.has_value() || !vram_addr.has_value() || !func_size_.has_value()) { | ||||||
|  |                             throw toml::parse_error("Function symbol entry is missing required field(s)", func_el.source()); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         uint32_t func_size = func_size_.value(); | ||||||
|  | 
 | ||||||
|  |                         Function cur_func{}; | ||||||
|  |                         cur_func.name = name.value(); | ||||||
|  |                         cur_func.vram = vram_addr.value(); | ||||||
|  |                         cur_func.rom = cur_func.vram - section.ram_addr + section.rom_addr; | ||||||
|  |                         cur_func.section_index = section_index; | ||||||
|  | 
 | ||||||
|  |                         if (cur_func.vram & 0b11) { | ||||||
|  |                             // Function isn't word aligned in vram.
 | ||||||
|  |                             throw toml::parse_error("Function's vram address isn't word aligned", func_el.source()); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         if (cur_func.rom & 0b11) { | ||||||
|  |                             // Function isn't word aligned in rom.
 | ||||||
|  |                             throw toml::parse_error("Function's rom address isn't word aligned", func_el.source()); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         if (cur_func.rom + func_size > rom.size()) { | ||||||
|  |                             // Function is out of bounds of the provided rom.
 | ||||||
|  |                             throw toml::parse_error("Functio is out of bounds of the provided rom", func_el.source()); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         // Get the function's words from the rom.
 | ||||||
|  |                         cur_func.words.reserve(func_size / sizeof(uint32_t)); | ||||||
|  |                         for (size_t rom_addr = cur_func.rom; rom_addr < cur_func.rom + func_size; rom_addr += sizeof(uint32_t)) { | ||||||
|  |                             cur_func.words.push_back(*reinterpret_cast<const uint32_t*>(rom.data() + rom_addr)); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         section.function_addrs.push_back(cur_func.vram); | ||||||
|  |                         ret.functions_by_name[cur_func.name] = function_index; | ||||||
|  |                         ret.functions_by_vram[cur_func.vram].push_back(function_index); | ||||||
|  |                         ret.section_functions[section_index].push_back(function_index); | ||||||
|  | 
 | ||||||
|  |                         ret.functions.emplace_back(std::move(cur_func)); | ||||||
|  |                     } | ||||||
|  |                     else { | ||||||
|  |                         throw toml::parse_error("Invalid function symbol entry", func_el.source()); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |                 // Check if relocs exist for the section and read them if so.
 | ||||||
|  |                 const toml::node_view relocs_value = el["relocs"]; | ||||||
|  |                 if (relocs_value.is_array()) { | ||||||
|  |                     // Mark the section as relocatable, since it has relocs.
 | ||||||
|  |                     section.relocatable = true; | ||||||
|  | 
 | ||||||
|  |                     // Read relocs for the section.
 | ||||||
|  |                     const toml::array* relocs_array = relocs_value.as_array(); | ||||||
|  |                     relocs_array->for_each([&ret, &rom, §ion, section_index](auto&& reloc_el) { | ||||||
|  |                         if constexpr (toml::is_table<decltype(reloc_el)>) { | ||||||
|  |                             std::optional<uint32_t> vram = reloc_el["vram"].template value<uint32_t>(); | ||||||
|  |                             std::optional<uint32_t> target_vram = reloc_el["target_vram"].template value<uint32_t>(); | ||||||
|  |                             std::optional<std::string> type_string = reloc_el["type"].template value<std::string>(); | ||||||
|  | 
 | ||||||
|  |                             if (!vram.has_value() || !target_vram.has_value() || !type_string.has_value()) { | ||||||
|  |                                 throw toml::parse_error("Reloc entry missing required field(s)", reloc_el.source()); | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                             RelocType reloc_type = reloc_type_from_name(type_string.value()); | ||||||
|  | 
 | ||||||
|  |                             // TODO also accept MIPS32 for TLB relocations.
 | ||||||
|  |                             if (reloc_type != RelocType::R_MIPS_HI16 && reloc_type != RelocType::R_MIPS_LO16) { | ||||||
|  |                                 throw toml::parse_error("Invalid reloc entry type", reloc_el.source()); | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                             Reloc cur_reloc{}; | ||||||
|  |                             cur_reloc.address = vram.value(); | ||||||
|  |                             cur_reloc.target_address = target_vram.value(); | ||||||
|  |                             cur_reloc.symbol_index = (uint32_t)-1; | ||||||
|  |                             cur_reloc.target_section = section_index; | ||||||
|  |                             cur_reloc.type = reloc_type; | ||||||
|  | 
 | ||||||
|  |                             section.relocs.emplace_back(cur_reloc); | ||||||
|  |                         } | ||||||
|  |                         else { | ||||||
|  |                             throw toml::parse_error("Invalid reloc entry", reloc_el.source()); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     section.relocatable = false; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 throw toml::parse_error("Invalid section entry", el.source()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 	} | ||||||
|  |     catch (const toml::parse_error& err) { | ||||||
|  |         std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin <<  "):\n" << err.description() << std::endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 	ret.rom = std::move(rom); | ||||||
|  | 	out = std::move(ret); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										236
									
								
								src/main.cpp
									
										
									
									
									
								
							
							
						
						
									
										236
									
								
								src/main.cpp
									
										
									
									
									
								
							|  | @ -1018,7 +1018,6 @@ ELFIO::section* read_sections(RecompPort::Context& context, const RecompPort::Co | ||||||
|                     reloc_out.address = rel_offset; |                     reloc_out.address = rel_offset; | ||||||
|                     reloc_out.symbol_index = rel_symbol; |                     reloc_out.symbol_index = rel_symbol; | ||||||
|                     reloc_out.type = static_cast<RecompPort::RelocType>(rel_type); |                     reloc_out.type = static_cast<RecompPort::RelocType>(rel_type); | ||||||
|                     reloc_out.needs_relocation = false; |  | ||||||
| 
 | 
 | ||||||
|                     std::string       rel_symbol_name; |                     std::string       rel_symbol_name; | ||||||
|                     ELFIO::Elf64_Addr rel_symbol_value; |                     ELFIO::Elf64_Addr rel_symbol_value; | ||||||
|  | @ -1033,12 +1032,6 @@ ELFIO::section* read_sections(RecompPort::Context& context, const RecompPort::Co | ||||||
| 
 | 
 | ||||||
|                     reloc_out.target_section = rel_symbol_section_index; |                     reloc_out.target_section = rel_symbol_section_index; | ||||||
| 
 | 
 | ||||||
|                     bool rel_needs_relocation = false; |  | ||||||
| 
 |  | ||||||
|                     if (rel_symbol_section_index < context.sections.size()) { |  | ||||||
|                         rel_needs_relocation = context.sections[rel_symbol_section_index].relocatable; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     // 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)
 | ||||||
|                     if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) { |                     if (reloc_out.type == RecompPort::RelocType::R_MIPS_LO16) { | ||||||
|                         if (prev_hi) { |                         if (prev_hi) { | ||||||
|  | @ -1212,6 +1205,79 @@ bool recompile_single_function(const RecompPort::Context& context, const RecompP | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::vector<std::string> reloc_names { | ||||||
|  |     "R_MIPS_NONE ", | ||||||
|  |     "R_MIPS_16", | ||||||
|  |     "R_MIPS_32", | ||||||
|  |     "R_MIPS_REL32", | ||||||
|  |     "R_MIPS_26", | ||||||
|  |     "R_MIPS_HI16", | ||||||
|  |     "R_MIPS_LO16", | ||||||
|  |     "R_MIPS_GPREL16", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void dump_context(const RecompPort::Context& context, const std::filesystem::path& path) { | ||||||
|  |     std::ofstream context_file {path}; | ||||||
|  | 
 | ||||||
|  |     for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { | ||||||
|  |         const RecompPort::Section& section = context.sections[section_index]; | ||||||
|  |         const std::vector<size_t>& section_funcs = context.section_functions[section_index]; | ||||||
|  |         if (!section_funcs.empty()) { | ||||||
|  |             fmt::print(context_file, | ||||||
|  |                 "# Autogenerated from an ELF via N64Recomp\n" | ||||||
|  |                 "[[section]]\n" | ||||||
|  |                 "name = \"{}\"\n" | ||||||
|  |                 "rom = 0x{:08X}\n" | ||||||
|  |                 "vram = 0x{:08X}\n" | ||||||
|  |                 "size = 0x{:X}\n" | ||||||
|  |                 "\n", | ||||||
|  |                 section.name, section.rom_addr, section.ram_addr, section.size); | ||||||
|  | 
 | ||||||
|  |             if (!section.relocs.empty()) { | ||||||
|  |                 fmt::print(context_file, "relocs = [\n"); | ||||||
|  | 
 | ||||||
|  |                 for (const RecompPort::Reloc& reloc : section.relocs) { | ||||||
|  |                     if (reloc.target_section == section_index || reloc.target_section == section.bss_section_index) { | ||||||
|  |                         // TODO allow MIPS32 relocs for TLB mapping support.
 | ||||||
|  |                         if (reloc.type == RecompPort::RelocType::R_MIPS_HI16 || reloc.type == RecompPort::RelocType::R_MIPS_LO16) { | ||||||
|  |                             fmt::print(context_file, "    {{ type = \"{}\", vram = 0x{:08X}, target_vram = 0x{:08X} }},\n", | ||||||
|  |                                 reloc_names[static_cast<int>(reloc.type)], reloc.address, reloc.target_address); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 fmt::print(context_file, "]\n\n"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             fmt::print(context_file, "functions = [\n"); | ||||||
|  | 
 | ||||||
|  |             for (const size_t& function_index : section_funcs) { | ||||||
|  |                 const RecompPort::Function& func = context.functions[function_index]; | ||||||
|  |                 fmt::print(context_file, "    {{ name = \"{}\", vram = 0x{:08X}, size = 0x{:X} }},\n", | ||||||
|  |                     func.name, func.vram, func.words.size() * sizeof(func.words[0])); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             fmt::print(context_file, "]\n\n"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static std::vector<uint8_t> read_file(const std::filesystem::path& path) { | ||||||
|  |     std::vector<uint8_t> ret; | ||||||
|  | 
 | ||||||
|  |     std::ifstream file{ path, std::ios::binary}; | ||||||
|  | 
 | ||||||
|  |     if (file.good()) { | ||||||
|  |         file.seekg(0, std::ios::end); | ||||||
|  |         ret.resize(file.tellg()); | ||||||
|  |         file.seekg(0, std::ios::beg); | ||||||
|  | 
 | ||||||
|  |         file.read(reinterpret_cast<char*>(ret.data()), ret.size()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int main(int argc, char** argv) { | int main(int argc, char** argv) { | ||||||
|     auto exit_failure = [] (const std::string& error_str) { |     auto exit_failure = [] (const std::string& error_str) { | ||||||
|         fmt::vprint(stderr, error_str, fmt::make_format_args()); |         fmt::vprint(stderr, error_str, fmt::make_format_args()); | ||||||
|  | @ -1230,7 +1296,6 @@ int main(int argc, char** argv) { | ||||||
|         exit_failure(fmt::format("Failed to load config file: {}\n", config_path)); |         exit_failure(fmt::format("Failed to load config file: {}\n", config_path)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ELFIO::elfio elf_file; |  | ||||||
|     RabbitizerConfig_Cfg.pseudos.pseudoMove = false; |     RabbitizerConfig_Cfg.pseudos.pseudoMove = false; | ||||||
|     RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; |     RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; | ||||||
|     RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; |     RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; | ||||||
|  | @ -1248,52 +1313,118 @@ int main(int argc, char** argv) { | ||||||
|     std::unordered_set<std::string> relocatable_sections{}; |     std::unordered_set<std::string> relocatable_sections{}; | ||||||
|     relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end()); |     relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end()); | ||||||
| 
 | 
 | ||||||
|     if (!elf_file.load(config.elf_path.string())) { |     RecompPort::Context context{}; | ||||||
|         exit_failure("Failed to load provided elf file\n"); |      | ||||||
|  |     if (!config.elf_path.empty() && !config.symbols_file_path.empty()) { | ||||||
|  |         exit_failure("Config file cannot provide both an elf and a symbols file\n"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (elf_file.get_class() != ELFIO::ELFCLASS32) { |     // Build a context from the provided elf file.
 | ||||||
|         exit_failure("Incorrect elf class\n"); |     if (!config.elf_path.empty()) { | ||||||
|  |         ELFIO::elfio elf_file; | ||||||
|  | 
 | ||||||
|  |         if (!elf_file.load(config.elf_path.string())) { | ||||||
|  |             exit_failure("Failed to load provided elf file\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (elf_file.get_class() != ELFIO::ELFCLASS32) { | ||||||
|  |             exit_failure("Incorrect elf class\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { | ||||||
|  |             exit_failure("Incorrect endianness\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         context = { elf_file }; | ||||||
|  |         context.relocatable_sections = std::move(relocatable_sections); | ||||||
|  | 
 | ||||||
|  |         // Read all of the sections in the elf and look for the symbol table section
 | ||||||
|  |         ELFIO::section* symtab_section = read_sections(context, config, elf_file); | ||||||
|  | 
 | ||||||
|  |         // Search the sections to see if any are overlays or TLB-mapped
 | ||||||
|  |         analyze_sections(context, elf_file); | ||||||
|  | 
 | ||||||
|  |         // If no symbol table was found then exit
 | ||||||
|  |         if (symtab_section == nullptr) { | ||||||
|  |             exit_failure("No symbol table section found\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Manually sized functions
 | ||||||
|  |         for (const auto& func_size : config.manual_func_sizes) { | ||||||
|  |             context.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Read all of the symbols in the elf and look for the entrypoint function
 | ||||||
|  |         bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint, config.has_entrypoint, config.use_absolute_symbols); | ||||||
|  | 
 | ||||||
|  |         // Add any manual functions
 | ||||||
|  |         add_manual_functions(context, elf_file, config.manual_functions); | ||||||
|  | 
 | ||||||
|  |         if (config.has_entrypoint && !found_entrypoint_func) { | ||||||
|  |             exit_failure("Could not find entrypoint function\n"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     // Build a context from the provided symbols file.
 | ||||||
|  |     else if (!config.symbols_file_path.empty()) { | ||||||
|  |         if (config.rom_file_path.empty()) { | ||||||
|  |             exit_failure("A ROM file must be provided when using a symbols file\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         std::vector<uint8_t> rom = read_file(config.rom_file_path); | ||||||
|  |         if (rom.empty()) { | ||||||
|  |             exit_failure("Failed to load ROM file: " + config.rom_file_path.string() + "\n"); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         if (!RecompPort::Context::from_symbol_file(config.symbols_file_path, std::move(rom), context)) { | ||||||
|  |             exit_failure("Failed to load symbols file\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         auto rename_function = [&context](size_t func_index, const std::string& new_name) { | ||||||
|  |             RecompPort::Function& func = context.functions[func_index]; | ||||||
|  | 
 | ||||||
|  |             context.functions_by_name.erase(func.name); | ||||||
|  |             func.name = new_name; | ||||||
|  |             context.functions_by_name[func.name] = func_index; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         for (size_t func_index = 0; func_index < context.functions.size(); func_index++) { | ||||||
|  |             RecompPort::Function& func = context.functions[func_index]; | ||||||
|  |             if (reimplemented_funcs.contains(func.name)) { | ||||||
|  |                 rename_function(func_index, func.name + "_recomp"); | ||||||
|  |                 func.reimplemented = true; | ||||||
|  |                 func.ignored = true; | ||||||
|  |             } else if (ignored_funcs.contains(func.name)) { | ||||||
|  |                 rename_function(func_index, func.name + "_recomp"); | ||||||
|  |                 func.ignored = true; | ||||||
|  |             } else if (renamed_funcs.contains(func.name)) { | ||||||
|  |                 rename_function(func_index, func.name + "_recomp"); | ||||||
|  |                 func.ignored = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         if (config.has_entrypoint) { | ||||||
|  |             bool found_entrypoint = false; | ||||||
|  | 
 | ||||||
|  |             for (uint32_t func_index : context.functions_by_vram[config.entrypoint]) { | ||||||
|  |                 auto& func = context.functions[func_index]; | ||||||
|  |                 if (func.rom == 0x1000) { | ||||||
|  |                     rename_function(func_index, "recomp_entrypoint"); | ||||||
|  |                     found_entrypoint = true; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!found_entrypoint) { | ||||||
|  |                 exit_failure("No entrypoint provided in symbol file\n"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         exit_failure("Config file must provide either an elf or a symbols file\n"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { |  | ||||||
|         exit_failure("Incorrect endianness\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     RecompPort::Context context{ elf_file }; |  | ||||||
|     context.relocatable_sections = std::move(relocatable_sections); |  | ||||||
| 
 |  | ||||||
|     // Read all of the sections in the elf and look for the symbol table section
 |  | ||||||
|     ELFIO::section* symtab_section = read_sections(context, config, elf_file); |  | ||||||
| 
 |  | ||||||
|     // Search the sections to see if any are overlays or TLB-mapped
 |  | ||||||
|     analyze_sections(context, elf_file); |  | ||||||
| 
 |  | ||||||
|     // If no symbol table was found then exit
 |  | ||||||
|     if (symtab_section == nullptr) { |  | ||||||
|         exit_failure("No symbol table section found\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Functions that weren't declared properly and thus have no size in the elf
 |  | ||||||
|     //context.manually_sized_funcs.emplace("guMtxF2L", 0x64);
 |  | ||||||
|     //context.manually_sized_funcs.emplace("guScaleF", 0x48);
 |  | ||||||
|     //context.manually_sized_funcs.emplace("guTranslateF", 0x48);
 |  | ||||||
|     //context.manually_sized_funcs.emplace("guMtxIdentF", 0x48);
 |  | ||||||
|     //context.manually_sized_funcs.emplace("sqrtf", 0x8);
 |  | ||||||
|     //context.manually_sized_funcs.emplace("guMtxIdent", 0x4C);
 |  | ||||||
|     for (const auto& func_size : config.manual_func_sizes) { |  | ||||||
|         context.manually_sized_funcs.emplace(func_size.func_name, func_size.size_bytes); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Read all of the symbols in the elf and look for the entrypoint function
 |  | ||||||
|     bool found_entrypoint_func = read_symbols(context, elf_file, symtab_section, config.entrypoint, config.has_entrypoint, config.use_absolute_symbols); |  | ||||||
| 
 |  | ||||||
|     // Add any manual functions
 |  | ||||||
|     add_manual_functions(context, elf_file, config.manual_functions); |  | ||||||
| 
 |  | ||||||
|     if (config.has_entrypoint && !found_entrypoint_func) { |  | ||||||
|         exit_failure("Could not find entrypoint function\n"); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     fmt::print("Function count: {}\n", context.functions.size()); |     fmt::print("Function count: {}\n", context.functions.size()); | ||||||
| 
 | 
 | ||||||
|  | @ -1312,6 +1443,11 @@ int main(int argc, char** argv) { | ||||||
| 
 | 
 | ||||||
|     std::vector<std::vector<uint32_t>> static_funcs_by_section{ context.sections.size() }; |     std::vector<std::vector<uint32_t>> static_funcs_by_section{ context.sections.size() }; | ||||||
| 
 | 
 | ||||||
|  |     // TODO expose a way to dump the context from the command line. Make sure not to rename functions when doing so.
 | ||||||
|  |     //fmt::print("Dumping context\n");
 | ||||||
|  |     //dump_context(context, "dump.toml");
 | ||||||
|  |     //return 0;
 | ||||||
|  | 
 | ||||||
|     fmt::print("Working dir: {}\n", std::filesystem::current_path().string()); |     fmt::print("Working dir: {}\n", std::filesystem::current_path().string()); | ||||||
| 
 | 
 | ||||||
|     // Stub out any functions specified in the config file.
 |     // Stub out any functions specified in the config file.
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Mr-Wiseguy
						Mr-Wiseguy