mirror of
				https://github.com/N64Recomp/N64Recomp.git
				synced 2025-10-30 08:02:11 +00:00 
			
		
		
		
	Modding Support PR 1 (Instruction tables, modding support, mod symbol format, library conversion) (#89)
* Initial implementation of binary operation table * Initial implementation of unary operation table * More binary op types, moved binary expression string generation into separate function * Added and implemented conditional branch instruction table * Fixed likely swap on bgezal, fixed extra indent branch close and missing indent on branch statement * Add operands for other uses of float registers * Added CHECK_FR generation to binary operation processing, moved float comparison instructions to binary op table * Finished moving float arithmetic instructions to operation tables * Added store instruction operation table * Created Generator interface, separated operation types and tables and C generation code into new files * Fix mov.d using the wrong input operand * Move recompiler core logic into a core library and make the existing CLI consume the core library * Removed unnecessary config input to recompilation functions * Moved parts of recomp_port.h into new internal headers in src folder * Changed recomp port naming to N64Recomp * Remove some unused code and document which Context fields are actually required for recompilation * Implement mod symbol parsing * Restructure mod symbols to make replacements global instead of per-section * Refactor elf parsing into static Context method for reusability * Move elf parsing into a separate library * WIP elf to mod tool, currently working without relocations or API exports/imports * Make mod tool emit relocs and patch binary for non-relocatable symbol references as needed * Implemented writing import and exports in the mod tool * Add dependencies to the mod symbol format, finish exporting and importing of mod symbols * Add first pass offline mod recompiler (generates C from mods that can be compiled and linked into a dynamic library) * Add strict mode and ability to generate exports for normal recompilation (for patches) * Move mod context fields into base context, move import symbols into separate vector, misc cleanup * Some cleanup by making some Context members private * Add events (from dependencies and exported) and callbacks to the mod symbol format and add support to them in elf parsing * Add runtime-driven fields to offline mod recompiler, fix event symbol relocs using the wrong section in the mod tool * Move file header writing outside of function recompilation * Allow cross-section relocations, encode exact target section in mod relocations, add way to tag reference symbol relocations * Add local symbol addresses array to offline mod recompiler output and rename original one to reference section addresses * Add more comments to the offline mod recompiler's output * Fix handling of section load addresses to match objcopy behavior, added event parsing to dependency tomls, minor cleanup * Fixed incorrect size used for finding section segments * Add missing includes for libstdc++ * Rework callbacks and imports to use the section name for identifying the dependency instead of relying on per-dependency tomls
This commit is contained in:
		
							parent
							
								
									f8d439aeee
								
							
						
					
					
						commit
						5b17bf8bb5
					
				
					 18 changed files with 5121 additions and 2515 deletions
				
			
		
							
								
								
									
										180
									
								
								CMakeLists.txt
									
										
									
									
									
								
							
							
						
						
									
										180
									
								
								CMakeLists.txt
									
										
									
									
									
								
							|  | @ -10,50 +10,50 @@ project(rabbitizer) | |||
| add_library(rabbitizer STATIC) | ||||
| 
 | ||||
| target_sources(rabbitizer PRIVATE | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c") | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerConfig.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/RabbitizerVersion.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/common/Utils.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrId.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegister.c" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c") | ||||
| 
 | ||||
| target_include_directories(rabbitizer PUBLIC | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/include" | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/cplusplus/include") | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/include" | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/cplusplus/include") | ||||
| 
 | ||||
| target_include_directories(rabbitizer PRIVATE | ||||
|     "${CMAKE_SOURCE_DIR}/lib/rabbitizer/tables") | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/rabbitizer/tables") | ||||
| 
 | ||||
| # fmtlib | ||||
| add_subdirectory(lib/fmt) | ||||
|  | @ -62,29 +62,105 @@ add_subdirectory(lib/fmt) | |||
| set(TOML_ENABLE_FORMATTERS OFF) | ||||
| add_subdirectory(lib/tomlplusplus) | ||||
| 
 | ||||
| # N64 recompiler | ||||
| # Hardcoded symbol lists (separate library to not force a dependency on N64Recomp) | ||||
| project(SymbolLists) | ||||
| add_library(SymbolLists) | ||||
| 
 | ||||
| target_sources(SymbolLists PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(SymbolLists PUBLIC | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/include" | ||||
| ) | ||||
| 
 | ||||
| # N64 recompiler core library | ||||
| project(N64Recomp) | ||||
| add_executable(N64Recomp) | ||||
| add_library(N64Recomp) | ||||
| 
 | ||||
| target_sources(N64Recomp PRIVATE | ||||
|     ${CMAKE_SOURCE_DIR}/src/analysis.cpp | ||||
|     ${CMAKE_SOURCE_DIR}/src/config.cpp | ||||
|     ${CMAKE_SOURCE_DIR}/src/main.cpp | ||||
|     ${CMAKE_SOURCE_DIR}/src/recompilation.cpp) | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/analysis.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/operations.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/cgenerator.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/recompilation.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(N64Recomp PRIVATE | ||||
|     "${CMAKE_SOURCE_DIR}/lib/ELFIO" | ||||
|     "${CMAKE_SOURCE_DIR}/include") | ||||
| target_include_directories(N64Recomp PUBLIC | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/include" | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(N64Recomp fmt rabbitizer tomlplusplus::tomlplusplus) | ||||
| target_link_libraries(N64Recomp SymbolLists fmt rabbitizer tomlplusplus::tomlplusplus) | ||||
| 
 | ||||
| # N64 recompiler elf parsing | ||||
| project(N64RecompElf) | ||||
| add_library(N64RecompElf) | ||||
| 
 | ||||
| target_sources(N64RecompElf PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/elf.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(N64RecompElf PUBLIC | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/include" | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(N64RecompElf PRIVATE | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/lib/ELFIO" | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(N64RecompElf fmt) | ||||
| 
 | ||||
| # N64 recompiler executable | ||||
| project(N64RecompCLI) | ||||
| add_executable(N64RecompCLI) | ||||
| 
 | ||||
| target_sources(N64RecompCLI PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(N64RecompCLI PRIVATE | ||||
|     "${CMAKE_CURRENT_SOURCE_DIR}/include" | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(N64RecompCLI fmt rabbitizer tomlplusplus::tomlplusplus N64Recomp N64RecompElf) | ||||
| set_target_properties(N64RecompCLI PROPERTIES OUTPUT_NAME N64Recomp) | ||||
| 
 | ||||
| # RSP recompiler | ||||
| project(RSPRecomp) | ||||
| add_executable(RSPRecomp) | ||||
| 
 | ||||
| target_include_directories(RSPRecomp PRIVATE "${CMAKE_SOURCE_DIR}/include") | ||||
| target_include_directories(RSPRecomp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") | ||||
| 
 | ||||
| target_link_libraries(RSPRecomp fmt rabbitizer tomlplusplus::tomlplusplus) | ||||
| 
 | ||||
| target_sources(RSPRecomp PRIVATE | ||||
|     ${CMAKE_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/RSPRecomp/src/rsp_recomp.cpp) | ||||
| 
 | ||||
| # Mod tool | ||||
| project(RecompModTool) | ||||
| add_executable(RecompModTool) | ||||
| 
 | ||||
| target_sources(RecompModTool PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/mod_symbols.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/RecompModTool/main.cpp | ||||
| ) | ||||
| 
 | ||||
| target_include_directories(RecompModTool PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/lib/ELFIO | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(RecompModTool fmt tomlplusplus::tomlplusplus N64RecompElf) | ||||
| 
 | ||||
| # Offline mod recompiler | ||||
| project(OfflineModRecomp) | ||||
| add_executable(OfflineModRecomp) | ||||
| 
 | ||||
| target_sources(OfflineModRecomp PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/src/config.cpp | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR}/OfflineModRecomp/main.cpp | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(OfflineModRecomp fmt rabbitizer tomlplusplus::tomlplusplus N64Recomp) | ||||
|  |  | |||
							
								
								
									
										191
									
								
								OfflineModRecomp/main.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								OfflineModRecomp/main.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,191 @@ | |||
| #include <filesystem> | ||||
| #include <fstream> | ||||
| #include <vector> | ||||
| #include <span> | ||||
| 
 | ||||
| #include "n64recomp.h" | ||||
| #include "rabbitizer.hpp" | ||||
| 
 | ||||
| static std::vector<uint8_t> read_file(const std::filesystem::path& path, bool& found) { | ||||
|     std::vector<uint8_t> ret; | ||||
|     found = false; | ||||
| 
 | ||||
|     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()); | ||||
|         found = true; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| const std::filesystem::path func_reference_syms_file_path { | ||||
|     "C:/n64/MMRecompTestMod/Zelda64RecompSyms/mm.us.rev1.syms.toml" | ||||
| }; | ||||
| const std::vector<std::filesystem::path> data_reference_syms_file_paths { | ||||
|     "C:/n64/MMRecompTestMod/Zelda64RecompSyms/mm.us.rev1.datasyms.toml", | ||||
|     "C:/n64/MMRecompTestMod/Zelda64RecompSyms/mm.us.rev1.datasyms_static.toml" | ||||
| }; | ||||
| 
 | ||||
| int main(int argc, const char** argv) { | ||||
|     if (argc != 4) { | ||||
|         printf("Usage: %s [mod symbol file] [ROM] [output C file]\n", argv[0]); | ||||
|         return EXIT_SUCCESS; | ||||
|     } | ||||
|     bool found; | ||||
|     std::vector<uint8_t> symbol_data = read_file(argv[1], found); | ||||
|     if (!found) { | ||||
|         fprintf(stderr, "Failed to open symbol file\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<uint8_t> rom_data = read_file(argv[2], found); | ||||
|     if (!found) { | ||||
|         fprintf(stderr, "Failed to open ROM\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     std::span<const char> symbol_data_span { reinterpret_cast<const char*>(symbol_data.data()), symbol_data.size() }; | ||||
| 
 | ||||
|     std::vector<uint8_t> dummy_rom{}; | ||||
|     N64Recomp::Context reference_context{}; | ||||
|     if (!N64Recomp::Context::from_symbol_file(func_reference_syms_file_path, std::move(dummy_rom), reference_context, false)) { | ||||
|         printf("Failed to load provided function reference symbol file\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     //for (const std::filesystem::path& cur_data_sym_path : data_reference_syms_file_paths) {
 | ||||
|     //    if (!reference_context.read_data_reference_syms(cur_data_sym_path)) {
 | ||||
|     //        printf("Failed to load provided data reference symbol file\n");
 | ||||
|     //        return EXIT_FAILURE;
 | ||||
|     //    }
 | ||||
|     //}
 | ||||
| 
 | ||||
|     std::unordered_map<uint32_t, uint16_t> sections_by_vrom{}; | ||||
|     for (uint16_t section_index = 0; section_index < reference_context.sections.size(); section_index++) { | ||||
|         sections_by_vrom[reference_context.sections[section_index].rom_addr] = section_index; | ||||
|     } | ||||
| 
 | ||||
|     N64Recomp::Context mod_context; | ||||
| 
 | ||||
| 	N64Recomp::ModSymbolsError error = N64Recomp::parse_mod_symbols(symbol_data_span, rom_data, sections_by_vrom, reference_context, mod_context); | ||||
|     if (error != N64Recomp::ModSymbolsError::Good) { | ||||
|         fprintf(stderr, "Error parsing mod symbols: %d\n", (int)error); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     // Populate R_MIPS_26 reloc symbol indices. Start by building a map of vram address to matching reference symbols.
 | ||||
|     std::unordered_map<uint32_t, std::vector<size_t>> reference_symbols_by_vram{}; | ||||
|     for (size_t reference_symbol_index = 0; reference_symbol_index < mod_context.num_regular_reference_symbols(); reference_symbol_index++) { | ||||
|         const auto& sym = mod_context.get_regular_reference_symbol(reference_symbol_index); | ||||
|         uint16_t section_index = sym.section_index; | ||||
|         if (section_index != N64Recomp::SectionAbsolute) { | ||||
|             uint32_t section_vram = mod_context.get_reference_section_vram(section_index); | ||||
|             reference_symbols_by_vram[section_vram + sym.section_offset].push_back(reference_symbol_index); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Use the mapping to populate the symbol index for every R_MIPS_26 reference symbol reloc. 
 | ||||
|     for (auto& section : mod_context.sections) { | ||||
|         for (auto& reloc : section.relocs) { | ||||
|             if (reloc.type == N64Recomp::RelocType::R_MIPS_26 && reloc.reference_symbol) { | ||||
|                 if (mod_context.is_regular_reference_section(reloc.target_section)) { | ||||
|                     uint32_t section_vram = mod_context.get_reference_section_vram(reloc.target_section); | ||||
|                     uint32_t target_vram = section_vram + reloc.target_section_offset; | ||||
| 
 | ||||
|                     auto find_funcs_it = reference_symbols_by_vram.find(target_vram); | ||||
|                     bool found = false; | ||||
|                     if (find_funcs_it != reference_symbols_by_vram.end()) { | ||||
|                         for (size_t symbol_index : find_funcs_it->second) { | ||||
|                             const auto& cur_symbol = mod_context.get_reference_symbol(reloc.target_section, symbol_index); | ||||
|                             if (cur_symbol.section_index == reloc.target_section) { | ||||
|                                 reloc.symbol_index = symbol_index; | ||||
|                                 found = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     if (!found) { | ||||
|                         fprintf(stderr, "Failed to find R_MIPS_26 relocation target in section %d with vram 0x%08X\n", reloc.target_section, target_vram); | ||||
|                         return EXIT_FAILURE; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     mod_context.rom = std::move(rom_data); | ||||
| 
 | ||||
|     std::vector<std::vector<uint32_t>> static_funcs_by_section{}; | ||||
|     static_funcs_by_section.resize(mod_context.sections.size()); | ||||
| 
 | ||||
|     std::ofstream output_file { argv[3] }; | ||||
| 
 | ||||
|     RabbitizerConfig_Cfg.pseudos.pseudoMove = false; | ||||
|     RabbitizerConfig_Cfg.pseudos.pseudoBeqz = false; | ||||
|     RabbitizerConfig_Cfg.pseudos.pseudoBnez = false; | ||||
|     RabbitizerConfig_Cfg.pseudos.pseudoNot = false; | ||||
|     RabbitizerConfig_Cfg.pseudos.pseudoBal = false; | ||||
| 
 | ||||
|     output_file << "#include \"mod_recomp.h\"\n\n"; | ||||
| 
 | ||||
|     output_file << "// Values populated by the runtime:\n\n"; | ||||
| 
 | ||||
|     // Write import function pointer array and defines (i.e. `#define testmod_inner_import imported_funcs[0]`)
 | ||||
|     output_file << "// Array of pointers to imported functions with defines to alias their names.\n"; | ||||
|     size_t num_imports = mod_context.import_symbols.size(); | ||||
|     for (size_t import_index = 0; import_index < num_imports; import_index++) { | ||||
|         const auto& import = mod_context.import_symbols[import_index]; | ||||
|         output_file << "#define " << import.base.name << " imported_funcs[" << import_index << "]\n"; | ||||
|     } | ||||
|     output_file << "RECOMP_EXPORT recomp_func_t* imported_funcs[" << num_imports << "] = {};\n"; | ||||
|     output_file << "\n"; | ||||
| 
 | ||||
|     // Use reloc list to write reference symbol function pointer array and defines (i.e. `#define func_80102468 reference_symbol_funcs[0]`)
 | ||||
|     output_file << "// Array of pointers to functions from the original ROM with defines to alias their names.\n"; | ||||
|     size_t num_reference_symbols = 0; | ||||
|     for (const auto& section : mod_context.sections) { | ||||
|         for (const auto& reloc : section.relocs) { | ||||
|             if (reloc.type == N64Recomp::RelocType::R_MIPS_26 && reloc.reference_symbol && mod_context.is_regular_reference_section(reloc.target_section)) { | ||||
|                 const auto& sym = mod_context.get_reference_symbol(reloc.target_section, reloc.symbol_index); | ||||
|                 output_file << "#define " << sym.name << " reference_symbol_funcs[" << num_reference_symbols << "]\n"; | ||||
|                 num_reference_symbols++; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     output_file << "RECOMP_EXPORT recomp_func_t* reference_symbol_funcs[" << num_reference_symbols << "] = {};\n\n"; | ||||
| 
 | ||||
|     // Write provided event array (maps internal event indices to global ones).
 | ||||
|     output_file << "// Mapping of internal event indices to global ones.\n"; | ||||
|     output_file << "RECOMP_EXPORT uint32_t event_indices[" << mod_context.event_symbols.size() <<"] = {};\n\n"; | ||||
| 
 | ||||
|     // Write the event trigger function pointer.
 | ||||
|     output_file << "// Pointer to the runtime function for triggering events.\n"; | ||||
|     output_file << "RECOMP_EXPORT void (*recomp_trigger_event)(uint8_t* rdram, recomp_context* ctx, uint32_t) = NULL;\n\n"; | ||||
|     // Write the get_function pointer.
 | ||||
| 
 | ||||
|     output_file << "// Pointer to the runtime function for looking up functions from vram address.\n"; | ||||
|     output_file << "RECOMP_EXPORT recomp_func_t* (*get_function)(int32_t vram) = NULL;\n\n"; | ||||
| 
 | ||||
|     // Write the section_addresses pointer.
 | ||||
|     output_file << "// Pointer to the runtime's array of loaded section addresses for the base ROM.\n"; | ||||
|     output_file << "RECOMP_EXPORT int32_t* reference_section_addresses = NULL;\n\n"; | ||||
| 
 | ||||
|     // Write the local section addresses pointer array.
 | ||||
|     size_t num_sections = mod_context.sections.size(); | ||||
|     output_file << "// Array of this mod's loaded section addresses.\n"; | ||||
|     output_file << "RECOMP_EXPORT int32_t section_addresses[" << num_sections << "] = {};\n\n"; | ||||
| 
 | ||||
|     for (size_t func_index = 0; func_index < mod_context.functions.size(); func_index++) { | ||||
|         auto& func = mod_context.functions[func_index]; | ||||
|         func.name = "mod_func_" + std::to_string(func_index); | ||||
|         N64Recomp::recompile_function(mod_context, func, output_file, static_funcs_by_section, true); | ||||
|     } | ||||
| 
 | ||||
| 	return EXIT_SUCCESS; | ||||
| } | ||||
							
								
								
									
										737
									
								
								RecompModTool/main.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										737
									
								
								RecompModTool/main.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,737 @@ | |||
| #include <array> | ||||
| #include <fstream> | ||||
| #include <filesystem> | ||||
| #include <iostream> | ||||
| #include <numeric> | ||||
| #include "fmt/format.h" | ||||
| #include "n64recomp.h" | ||||
| #include <toml++/toml.hpp> | ||||
| 
 | ||||
| struct ModConfig { | ||||
|     std::filesystem::path output_syms_path; | ||||
|     std::filesystem::path output_binary_path; | ||||
|     std::filesystem::path elf_path; | ||||
|     std::filesystem::path func_reference_syms_file_path; | ||||
|     std::vector<std::filesystem::path> data_reference_syms_file_paths; | ||||
|     std::vector<N64Recomp::Dependency> dependencies; | ||||
| }; | ||||
| 
 | ||||
| static std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { | ||||
|     if (!child.empty()) { | ||||
|         return parent / child; | ||||
|     } | ||||
|     return child; | ||||
| } | ||||
| 
 | ||||
| static bool parse_version_string(std::string_view str, uint8_t& major, uint8_t& minor, uint8_t& patch) { | ||||
|     std::array<size_t, 2> period_indices; | ||||
|     size_t num_periods = 0; | ||||
|     size_t cur_pos = 0; | ||||
| 
 | ||||
|     // Find the 2 required periods.
 | ||||
|     cur_pos = str.find('.', cur_pos); | ||||
|     period_indices[0] = cur_pos; | ||||
|     cur_pos = str.find('.', cur_pos + 1); | ||||
|     period_indices[1] = cur_pos; | ||||
| 
 | ||||
|     // Check that both were found.
 | ||||
|     if (period_indices[0] == std::string::npos || period_indices[1] == std::string::npos) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Parse the 3 numbers formed by splitting the string via the periods.
 | ||||
|     std::array<std::from_chars_result, 3> parse_results;  | ||||
|     std::array<size_t, 3> parse_starts { 0, period_indices[0] + 1, period_indices[1] + 1 }; | ||||
|     std::array<size_t, 3> parse_ends { period_indices[0], period_indices[1], str.size() }; | ||||
|     parse_results[0] = std::from_chars(str.data() + parse_starts[0], str.data() + parse_ends[0], major); | ||||
|     parse_results[1] = std::from_chars(str.data() + parse_starts[1], str.data() + parse_ends[1], minor); | ||||
|     parse_results[2] = std::from_chars(str.data() + parse_starts[2], str.data() + parse_ends[2], patch); | ||||
| 
 | ||||
|     // Check that all 3 parsed correctly.
 | ||||
|     auto did_parse = [&](size_t i) { | ||||
|         return parse_results[i].ec == std::errc{} && parse_results[i].ptr == str.data() + parse_ends[i]; | ||||
|     }; | ||||
|      | ||||
|      | ||||
|     if (!did_parse(0) || !did_parse(1) || !did_parse(2)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool parse_dependency_string(const std::string& val, N64Recomp::Dependency& dep) { | ||||
|     N64Recomp::Dependency ret; | ||||
|     size_t id_pos = 0; | ||||
|     size_t id_length = 0; | ||||
| 
 | ||||
|     size_t colon_pos = val.find(':'); | ||||
|     if (colon_pos == std::string::npos) { | ||||
|         id_length = val.size(); | ||||
|         ret.major_version = 0; | ||||
|         ret.minor_version = 0; | ||||
|         ret.patch_version = 0; | ||||
|     } | ||||
|     else { | ||||
|         id_length = colon_pos; | ||||
|         uint8_t major, minor, patch; | ||||
|         if (!parse_version_string(std::string_view{val.begin() + colon_pos + 1, val.end()}, major, minor, patch)) { | ||||
|             return false; | ||||
|         } | ||||
|         ret.major_version = major; | ||||
|         ret.minor_version = minor; | ||||
|         ret.patch_version = patch; | ||||
|     } | ||||
| 
 | ||||
|     ret.mod_id = val.substr(id_pos, id_length); | ||||
| 
 | ||||
|     dep = std::move(ret); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static std::vector<std::filesystem::path> get_toml_path_array(const toml::array* toml_array, const std::filesystem::path& basedir) { | ||||
|     std::vector<std::filesystem::path> ret; | ||||
| 
 | ||||
|     // Reserve room for all the funcs in the map.
 | ||||
|     ret.reserve(toml_array->size()); | ||||
|     toml_array->for_each([&ret, &basedir](auto&& el) { | ||||
|         if constexpr (toml::is_string<decltype(el)>) { | ||||
|             ret.emplace_back(concat_if_not_empty(basedir, el.ref<std::string>())); | ||||
|         } | ||||
|         else { | ||||
|             throw toml::parse_error("Invalid type for data reference symbol file entry", el.source()); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| ModConfig parse_mod_config(const std::filesystem::path& config_path, bool& good) { | ||||
|     ModConfig ret{}; | ||||
|     good = false; | ||||
| 
 | ||||
|     toml::table toml_data{}; | ||||
| 
 | ||||
|     try { | ||||
|         toml_data = toml::parse_file(config_path.native()); | ||||
|         std::filesystem::path basedir = config_path.parent_path(); | ||||
|          | ||||
|         const auto config_data = toml_data["config"]; | ||||
| 
 | ||||
|         // Output symbol file path
 | ||||
|         std::optional<std::string> output_syms_path_opt = config_data["output_syms_path"].value<std::string>(); | ||||
|         if (output_syms_path_opt.has_value()) { | ||||
|             ret.output_syms_path = concat_if_not_empty(basedir, output_syms_path_opt.value()); | ||||
|         } | ||||
|         else { | ||||
|             throw toml::parse_error("Mod toml is missing output symbol file path", config_data.node()->source()); | ||||
|         } | ||||
| 
 | ||||
|         // Output binary file path
 | ||||
|         std::optional<std::string> output_binary_path_opt = config_data["output_binary_path"].value<std::string>(); | ||||
|         if (output_binary_path_opt.has_value()) { | ||||
|             ret.output_binary_path = concat_if_not_empty(basedir, output_binary_path_opt.value()); | ||||
|         } | ||||
|         else { | ||||
|             throw toml::parse_error("Mod toml is missing output binary file path", config_data.node()->source()); | ||||
|         } | ||||
| 
 | ||||
|         // Elf file
 | ||||
|         std::optional<std::string> elf_path_opt = config_data["elf_path"].value<std::string>(); | ||||
|         if (elf_path_opt.has_value()) { | ||||
|             ret.elf_path = concat_if_not_empty(basedir, elf_path_opt.value()); | ||||
|         } | ||||
|         else { | ||||
|             throw toml::parse_error("Mod toml is missing elf file", config_data.node()->source()); | ||||
|         } | ||||
|          | ||||
|         // Function reference symbols file
 | ||||
|         std::optional<std::string> func_reference_syms_file_opt = config_data["func_reference_syms_file"].value<std::string>(); | ||||
|         if (func_reference_syms_file_opt.has_value()) { | ||||
|             ret.func_reference_syms_file_path = concat_if_not_empty(basedir, func_reference_syms_file_opt.value()); | ||||
|         } | ||||
|         else { | ||||
|             throw toml::parse_error("Mod toml is missing function reference symbol file", config_data.node()->source()); | ||||
|         } | ||||
| 
 | ||||
|         // Data reference symbols files
 | ||||
|         toml::node_view data_reference_syms_file_data = config_data["data_reference_syms_files"]; | ||||
|         if (data_reference_syms_file_data.is_array()) { | ||||
|             const toml::array* array = data_reference_syms_file_data.as_array(); | ||||
|             ret.data_reference_syms_file_paths = get_toml_path_array(array, basedir); | ||||
|         } | ||||
|         else { | ||||
|             if (data_reference_syms_file_data) { | ||||
|                 throw toml::parse_error("Mod toml is missing data reference symbol file list", config_data.node()->source()); | ||||
|             } | ||||
|             else { | ||||
|                 throw toml::parse_error("Invalid data reference symbol file list", data_reference_syms_file_data.node()->source()); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // Dependency list (optional)
 | ||||
|         toml::node_view dependency_data = config_data["dependencies"]; | ||||
|         if (dependency_data.is_array()) { | ||||
|             const toml::array* dependency_array = dependency_data.as_array(); | ||||
|             // Reserve room for all the dependencies.
 | ||||
|             ret.dependencies.reserve(dependency_array->size()); | ||||
|             dependency_array->for_each([&ret](auto&& el) { | ||||
|                 if constexpr (toml::is_string<decltype(el)>) { | ||||
|                     N64Recomp::Dependency dep; | ||||
|                     if (!parse_dependency_string(el.ref<std::string>(), dep)) { | ||||
|                         throw toml::parse_error("Invalid dependency entry", el.source()); | ||||
|                     } | ||||
|                     ret.dependencies.emplace_back(std::move(dep)); | ||||
|                 } | ||||
|                 else { | ||||
|                     throw toml::parse_error("Invalid toml type for dependency", el.source()); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         else if (dependency_data) { | ||||
|             throw toml::parse_error("Invalid mod dependency list", dependency_data.node()->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 {}; | ||||
|     } | ||||
| 
 | ||||
|     good = true; | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static inline uint32_t round_up_16(uint32_t value) { | ||||
|     return (value + 15) & (~15); | ||||
| } | ||||
| 
 | ||||
| bool parse_callback_name(std::string_view data, std::string& dependency_name, std::string& event_name) { | ||||
|     size_t period_pos = data.find(':'); | ||||
| 
 | ||||
|     if (period_pos == std::string::npos) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     std::string_view dependency_name_view = std::string_view{data}.substr(0, period_pos); | ||||
|     std::string_view event_name_view = std::string_view{data}.substr(period_pos + 1); | ||||
| 
 | ||||
|     if (!N64Recomp::validate_mod_name(dependency_name_view)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     dependency_name = dependency_name_view; | ||||
|     event_name = event_name_view; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bool& good) { | ||||
|     N64Recomp::Context ret{}; | ||||
|     good = false; | ||||
| 
 | ||||
|     // Make a vector containing 0, 1, 2, ... section count - 1
 | ||||
|     std::vector<uint16_t> section_order; | ||||
|     section_order.resize(input_context.sections.size()); | ||||
|     std::iota(section_order.begin(), section_order.end(), 0); | ||||
| 
 | ||||
|     // TODO this sort is currently disabled because sections seem to already be ordered
 | ||||
|     // by elf offset. Determine if this is always the case and remove this if so.
 | ||||
|     //// Sort the vector based on the rom address of the corresponding section.
 | ||||
|     //std::sort(section_order.begin(), section_order.end(),
 | ||||
|     //    [&](uint16_t a, uint16_t b) {
 | ||||
|     //        const auto& section_a = input_context.sections[a];
 | ||||
|     //        const auto& section_b = input_context.sections[b];
 | ||||
|     //        // Sort primarily by ROM address.
 | ||||
|     //        if (section_a.rom_addr != section_b.rom_addr) {
 | ||||
|     //            return section_a.rom_addr < section_b.rom_addr;
 | ||||
|     //        }
 | ||||
|     //        // Sort secondarily by RAM address.
 | ||||
|     //        return section_a.ram_addr < section_b.ram_addr;
 | ||||
|     //    }
 | ||||
|     //);
 | ||||
| 
 | ||||
|     // TODO avoid a copy here.
 | ||||
|     ret.rom = input_context.rom; | ||||
| 
 | ||||
|     // Copy the dependency data from the input context.
 | ||||
|     ret.dependencies = input_context.dependencies; | ||||
|     ret.dependencies_by_name = input_context.dependencies_by_name; | ||||
|     ret.import_symbols = input_context.import_symbols; | ||||
|     ret.dependency_events = input_context.dependency_events; | ||||
|     ret.dependency_events_by_name = input_context.dependency_events_by_name; | ||||
|     ret.dependency_imports_by_name = input_context.dependency_imports_by_name; | ||||
| 
 | ||||
|     uint32_t rom_to_ram = (uint32_t)-1; | ||||
|     size_t output_section_index = (size_t)-1; | ||||
|     ret.sections.resize(1); | ||||
| 
 | ||||
|     // Mapping of input section to output section for fixing up relocations.
 | ||||
|     std::unordered_map<uint16_t, uint16_t> input_section_to_output_section{}; | ||||
| 
 | ||||
|     // Iterate over the input sections in their sorted order.
 | ||||
|     for (uint16_t section_index : section_order) { | ||||
|         const auto& cur_section = input_context.sections[section_index]; | ||||
|         uint32_t cur_rom_to_ram = cur_section.ram_addr - cur_section.rom_addr; | ||||
| 
 | ||||
|         // Check if this is a non-allocated section.
 | ||||
|         if (cur_section.rom_addr == (uint32_t)-1) { | ||||
|             // If so, check if it has a vram address directly after the current output section. If it does, then add this
 | ||||
|             // section's size to the output section's bss size.
 | ||||
|             if (output_section_index != -1 && cur_section.size != 0) { | ||||
|                 auto& section_out = ret.sections[output_section_index]; | ||||
|                 uint32_t output_section_bss_start = section_out.ram_addr + section_out.size; | ||||
|                 uint32_t output_section_bss_end = output_section_bss_start + section_out.bss_size; | ||||
|                 // Check if the current section starts at the end of the output section, allowing for a range of matches to account for 16 byte section alignment.
 | ||||
|                 if (cur_section.ram_addr >= output_section_bss_end && cur_section.ram_addr <= round_up_16(output_section_bss_end)) { | ||||
|                     // Calculate the output section's bss size by using its non-bss end address and the current section's end address.
 | ||||
|                     section_out.bss_size = cur_section.ram_addr + cur_section.size - output_section_bss_start; | ||||
|                     input_section_to_output_section[section_index] = output_section_index; | ||||
|                 } | ||||
|             } | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         // Check if this section matches up with the previous section to merge them together.
 | ||||
|         if (rom_to_ram == cur_rom_to_ram) { | ||||
|             auto& section_out = ret.sections[output_section_index]; | ||||
|             uint32_t cur_section_end = cur_section.rom_addr + cur_section.size; | ||||
|             section_out.size = cur_section_end - section_out.rom_addr; | ||||
|         } | ||||
|         // Otherwise, create a new output section and advance to it.
 | ||||
|         else { | ||||
|             output_section_index++; | ||||
|             ret.sections.resize(output_section_index + 1); | ||||
|             ret.section_functions.resize(output_section_index + 1); | ||||
|             rom_to_ram = cur_rom_to_ram; | ||||
| 
 | ||||
|             auto& new_section = ret.sections[output_section_index]; | ||||
|             new_section.rom_addr = cur_section.rom_addr; | ||||
|             new_section.ram_addr = cur_section.ram_addr; | ||||
|             new_section.size = cur_section.size; | ||||
|         } | ||||
| 
 | ||||
|         // Map this section to the current output section.
 | ||||
|         input_section_to_output_section[section_index] = output_section_index; | ||||
|          | ||||
|         // Check for special section names.
 | ||||
|         bool patch_section = cur_section.name == N64Recomp::PatchSectionName; | ||||
|         bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName; | ||||
|         bool export_section = cur_section.name == N64Recomp::ExportSectionName; | ||||
|         bool event_section = cur_section.name == N64Recomp::EventSectionName; | ||||
|         bool import_section = cur_section.name.starts_with(N64Recomp::ImportSectionPrefix); | ||||
|         bool callback_section = cur_section.name.starts_with(N64Recomp::CallbackSectionPrefix); | ||||
| 
 | ||||
|         // Add the functions from the current input section to the current output section.
 | ||||
|         auto& section_out = ret.sections[output_section_index]; | ||||
|          | ||||
|         const auto& cur_section_funcs = input_context.section_functions[section_index]; | ||||
| 
 | ||||
|         // Skip the functions and relocs in this section if it's the event section, instead opting to create
 | ||||
|         // event functions from the section's functions.
 | ||||
|         if (event_section) { | ||||
|             // Create event reference symbols for any functions in the event section. Ignore functions that already
 | ||||
|             // have a symbol, since relocs from previous sections may have triggered creation of the event's reference symbol already.
 | ||||
|             for (const auto& input_func_index : cur_section_funcs) { | ||||
|                 const auto& cur_func = input_context.functions[input_func_index]; | ||||
| 
 | ||||
|                 // Check if this event already has a symbol to prevent creating a duplicate.
 | ||||
|                 N64Recomp::SymbolReference event_ref; | ||||
|                 if (!ret.find_event_symbol(cur_func.name, event_ref)) { | ||||
|                     ret.add_event_symbol(cur_func.name); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // Skip the functions and relocs in this section if it's an import section, instead opting to create
 | ||||
|         // import symbols from the section's functions.
 | ||||
|         else if (import_section) { | ||||
|             for (const auto& input_func_index : cur_section_funcs) { | ||||
|                 const auto& cur_func = input_context.functions[input_func_index]; | ||||
|                 std::string dependency_name = cur_section.name.substr(N64Recomp::ImportSectionPrefix.size()); | ||||
|                 if (!N64Recomp::validate_mod_name(dependency_name)) { | ||||
|                     fmt::print("Failed to import function {} as {} is an invalid mod name.\n", | ||||
|                         cur_func.name, dependency_name); | ||||
|                     return {}; | ||||
|                 } | ||||
| 
 | ||||
|                 size_t dependency_index; | ||||
|                 if (!ret.find_dependency(dependency_name, dependency_index)) { | ||||
|                     fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n", | ||||
|                         cur_func.name, dependency_name); | ||||
|                     return {}; | ||||
|                 } | ||||
| 
 | ||||
|                 ret.add_import_symbol(cur_func.name, dependency_index); | ||||
|             } | ||||
|         } | ||||
|         // Normal section, copy the functions and relocs over.
 | ||||
|         else { | ||||
|             for (size_t section_function_index = 0; section_function_index < cur_section_funcs.size(); section_function_index++) { | ||||
|                 size_t output_func_index = ret.functions.size(); | ||||
|                 size_t input_func_index = cur_section_funcs[section_function_index]; | ||||
|                 const auto& cur_func = input_context.functions[input_func_index]; | ||||
| 
 | ||||
|                 // If this is the patch section, create a replacement for this function.
 | ||||
|                 if (patch_section || force_patch_section) { | ||||
|                     // Find the corresponding symbol in the reference symbols.
 | ||||
|                     N64Recomp::SymbolReference cur_reference; | ||||
|                     bool original_func_exists = input_context.find_regular_reference_symbol(cur_func.name, cur_reference); | ||||
| 
 | ||||
|                     // Check that the function being patched exists in the original reference symbols.
 | ||||
|                     if (!original_func_exists) { | ||||
|                         fmt::print("Function {} is marked as a patch but doesn't exist in the original ROM.\n", cur_func.name); | ||||
|                         return {}; | ||||
|                     } | ||||
| 
 | ||||
|                     // Check that the reference symbol is actually a function.
 | ||||
|                     const auto& reference_symbol = input_context.get_reference_symbol(cur_reference); | ||||
|                     if (!reference_symbol.is_function) { | ||||
|                         fmt::print("Function {0} is marked as a patch, but {0} was a variable in the original ROM.\n", cur_func.name); | ||||
|                         return {}; | ||||
|                     } | ||||
| 
 | ||||
|                     uint32_t reference_section_vram = input_context.get_reference_section_vram(reference_symbol.section_index); | ||||
|                     uint32_t reference_section_rom = input_context.get_reference_section_rom(reference_symbol.section_index); | ||||
| 
 | ||||
|                     // Add a replacement for this function to the output context.
 | ||||
|                     ret.replacements.emplace_back( | ||||
|                         N64Recomp::FunctionReplacement { | ||||
|                             .func_index = (uint32_t)output_func_index, | ||||
|                             .original_section_vrom = reference_section_rom, | ||||
|                             .original_vram = reference_section_vram + reference_symbol.section_offset, | ||||
|                             .flags = force_patch_section ? N64Recomp::ReplacementFlags::Force : N64Recomp::ReplacementFlags{} | ||||
|                         } | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
|                 std::string name_out; | ||||
| 
 | ||||
|                 if (export_section) { | ||||
|                     ret.exported_funcs.push_back(output_func_index); | ||||
|                     // Names are required for exported funcs, so copy the input function's name if we're in the export section.
 | ||||
|                     name_out = cur_func.name; | ||||
|                 } | ||||
| 
 | ||||
|                 if (callback_section) { | ||||
|                     std::string dependency_name, event_name; | ||||
|                     if (!parse_callback_name(std::string_view{ cur_section.name }.substr(N64Recomp::CallbackSectionPrefix.size()), dependency_name, event_name)) { | ||||
|                         fmt::print("Invalid mod name or event name for callback function {}.\n", | ||||
|                             cur_func.name); | ||||
|                         return {}; | ||||
|                     } | ||||
| 
 | ||||
|                     size_t dependency_index; | ||||
|                     if (!ret.find_dependency(dependency_name, dependency_index)) { | ||||
|                         fmt::print("Failed to register callback {} to event {} from mod {} as the mod is not a registered dependency.\n", | ||||
|                             cur_func.name, event_name, dependency_name); | ||||
|                         return {}; | ||||
|                     } | ||||
| 
 | ||||
|                     size_t event_index; | ||||
|                     if (!ret.add_dependency_event(event_name, dependency_index, event_index)) { | ||||
|                         fmt::print("Internal error: Failed to register event {} for dependency {}. Please report this issue.\n", | ||||
|                             event_name, dependency_name); | ||||
|                         return {}; | ||||
|                     } | ||||
| 
 | ||||
|                     if (!ret.add_callback(event_index, output_func_index)) { | ||||
|                         fmt::print("Internal error: Failed to add callback {} to event {} in dependency {}. Please report this issue.\n", | ||||
|                             cur_func.name, event_name, dependency_name); | ||||
|                         return {}; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 ret.section_functions[output_section_index].push_back(output_func_index); | ||||
| 
 | ||||
|                 // Add this function to the output context.
 | ||||
|                 ret.functions.emplace_back( | ||||
|                     cur_func.vram, | ||||
|                     cur_func.rom, | ||||
|                     std::vector<uint32_t>{}, // words
 | ||||
|                     std::move(name_out), // name
 | ||||
|                     (uint16_t)output_section_index, | ||||
|                     false, // ignored
 | ||||
|                     false, // reimplemented
 | ||||
|                     false // stubbed
 | ||||
|                 ); | ||||
| 
 | ||||
|                 // Resize the words vector so the function has the correct size. No need to copy the words, as they aren't used when making a mod symbol file.
 | ||||
|                 ret.functions[output_func_index].words.resize(cur_func.words.size()); | ||||
|             } | ||||
| 
 | ||||
|             // Copy relocs and patch HI16/LO16/26 relocs for non-relocatable reference symbols
 | ||||
|             section_out.relocs.reserve(section_out.relocs.size() + cur_section.relocs.size()); | ||||
|             for (const auto& cur_reloc : cur_section.relocs) { | ||||
|                 // Skip null relocs.
 | ||||
|                 if (cur_reloc.type == N64Recomp::RelocType::R_MIPS_NONE) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // Reloc to a special section symbol.
 | ||||
|                 if (!input_context.is_regular_reference_section(cur_reloc.target_section)) { | ||||
|                     section_out.relocs.emplace_back(cur_reloc); | ||||
|                 } | ||||
|                 // Reloc to a reference symbol.
 | ||||
|                 else if (cur_reloc.reference_symbol) { | ||||
|                     bool is_relocatable = input_context.is_reference_section_relocatable(cur_reloc.target_section); | ||||
|                     uint32_t section_vram = input_context.get_reference_section_vram(cur_reloc.target_section); | ||||
|                     // Patch relocations to non-relocatable reference sections.
 | ||||
|                     if (!is_relocatable) { | ||||
|                         uint32_t reloc_target_address = section_vram + cur_reloc.target_section_offset; | ||||
|                         uint32_t reloc_rom_address = cur_reloc.address - cur_section.ram_addr + cur_section.rom_addr; | ||||
|                          | ||||
|                         uint32_t* reloc_word_ptr = reinterpret_cast<uint32_t*>(ret.rom.data() + reloc_rom_address); | ||||
|                         uint32_t reloc_word = byteswap(*reloc_word_ptr); | ||||
|                         switch (cur_reloc.type) { | ||||
|                             case N64Recomp::RelocType::R_MIPS_32: | ||||
|                                 // Don't patch MIPS32 relocations, as they've already been patched during elf parsing.
 | ||||
|                                 break; | ||||
|                             case N64Recomp::RelocType::R_MIPS_26: | ||||
|                                 // Don't patch MIPS26 relocations, as there may be multiple functions with the same vram. Emit the reloc instead.
 | ||||
|                                 section_out.relocs.emplace_back(cur_reloc); | ||||
|                                 break; | ||||
|                             case N64Recomp::RelocType::R_MIPS_NONE: | ||||
|                                 // Nothing to do.
 | ||||
|                                 break; | ||||
|                             case N64Recomp::RelocType::R_MIPS_HI16: | ||||
|                                 reloc_word &= 0xFFFF0000; | ||||
|                                 reloc_word |= (reloc_target_address - (int16_t)(reloc_target_address & 0xFFFF)) >> 16 & 0xFFFF; | ||||
|                                 break; | ||||
|                             case N64Recomp::RelocType::R_MIPS_LO16: | ||||
|                                 reloc_word &= 0xFFFF0000; | ||||
|                                 reloc_word |= reloc_target_address & 0xFFFF; | ||||
|                                 break; | ||||
|                             default: | ||||
|                                 fmt::print("Unsupported or unknown relocation type {} in reloc at address 0x{:08X} in section {}.\n", | ||||
|                                     (int)cur_reloc.type, cur_reloc.address, cur_section.name); | ||||
|                                 return {}; | ||||
|                         } | ||||
|                         *reloc_word_ptr = byteswap(reloc_word); | ||||
|                     } | ||||
|                     // Copy relocations to relocatable reference sections as-is.
 | ||||
|                     else { | ||||
|                         section_out.relocs.emplace_back(cur_reloc); | ||||
|                     } | ||||
|                 } | ||||
|                 // Reloc to an internal symbol.
 | ||||
|                 else { | ||||
|                     const N64Recomp::Section& target_section = input_context.sections[cur_reloc.target_section]; | ||||
|                     uint32_t output_section_offset = cur_reloc.target_section_offset + target_section.ram_addr - cur_section.ram_addr; | ||||
| 
 | ||||
|                     // Check if the target section is the event section. If so, create a reference symbol reloc
 | ||||
|                     // to the event symbol, creating the event symbol if necessary.
 | ||||
|                     if (target_section.name == N64Recomp::EventSectionName) { | ||||
|                         if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) { | ||||
|                             fmt::print("Symbol {} is an event and cannot have its address taken.\n", | ||||
|                                 cur_section.name); | ||||
|                             return {}; | ||||
|                         } | ||||
| 
 | ||||
|                         uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr; | ||||
|                         size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section); | ||||
|                         if (target_function_index == (size_t)-1) { | ||||
|                             fmt::print("Internal error: Failed to find event symbol in section {} with offset 0x{:08X} (vram 0x{:08X}). Please report this issue.\n", | ||||
|                                 target_section.name, cur_reloc.target_section_offset, target_function_vram); | ||||
|                             return {}; | ||||
|                         } | ||||
| 
 | ||||
|                         const auto& target_function = input_context.functions[target_function_index]; | ||||
| 
 | ||||
|                         // Check if this event already has a symbol to prevent creating a duplicate.
 | ||||
|                         N64Recomp::SymbolReference event_ref; | ||||
|                         if (!ret.find_event_symbol(target_function.name, event_ref)) { | ||||
|                             ret.add_event_symbol(target_function.name); | ||||
|                             // Update the event symbol reference now that the symbol was created.
 | ||||
|                             ret.find_event_symbol(target_function.name, event_ref); | ||||
|                         } | ||||
| 
 | ||||
|                         // Create a reloc to the event symbol.
 | ||||
|                         section_out.relocs.emplace_back(N64Recomp::Reloc{ | ||||
|                             .address = cur_reloc.address, | ||||
|                             .target_section_offset = output_section_offset, | ||||
|                             .symbol_index = static_cast<uint32_t>(event_ref.symbol_index), | ||||
|                             .target_section = N64Recomp::SectionEvent, | ||||
|                             .type = cur_reloc.type, | ||||
|                             .reference_symbol = true, | ||||
|                         }); | ||||
|                     } | ||||
|                     // Check if the target is an import section. If so, create a reference symbol reloc
 | ||||
|                     // to the import symbol, creating the import symbol if necessary.
 | ||||
|                     else if (target_section.name.starts_with(N64Recomp::ImportSectionPrefix)) { | ||||
|                         if (cur_reloc.type != N64Recomp::RelocType::R_MIPS_26) { | ||||
|                             fmt::print("Symbol {} is an import and cannot have its address taken.\n", | ||||
|                                 cur_section.name); | ||||
|                             return {}; | ||||
|                         } | ||||
| 
 | ||||
|                         uint32_t target_function_vram = cur_reloc.target_section_offset + target_section.ram_addr; | ||||
|                         size_t target_function_index = input_context.find_function_by_vram_section(target_function_vram, cur_reloc.target_section); | ||||
|                         if (target_function_index == (size_t)-1) { | ||||
|                             fmt::print("Internal error: Failed to find import symbol in section {} with offset 0x{:08X} (vram 0x{:08X}). Please report this issue.\n", | ||||
|                                 target_section.name, cur_reloc.target_section_offset, target_function_vram); | ||||
|                             return {}; | ||||
|                         } | ||||
| 
 | ||||
|                         const auto& target_function = input_context.functions[target_function_index]; | ||||
| 
 | ||||
|                         // Find the dependency that this import belongs to.
 | ||||
|                         std::string dependency_name = target_section.name.substr(N64Recomp::ImportSectionPrefix.size()); | ||||
|                         size_t dependency_index; | ||||
|                         if (!ret.find_dependency(dependency_name, dependency_index)) { | ||||
|                             fmt::print("Failed to import function {} from mod {} as the mod is not a registered dependency.\n", | ||||
|                                 target_function.name, dependency_name); | ||||
|                             return {}; | ||||
|                         } | ||||
| 
 | ||||
|                         // Check if this event already has a symbol to prevent creating a duplicate.
 | ||||
|                         N64Recomp::SymbolReference import_ref; | ||||
|                         if (!ret.find_import_symbol(target_function.name, dependency_index, import_ref)) { | ||||
|                             ret.add_import_symbol(target_function.name, dependency_index); | ||||
|                             // Update the event symbol reference now that the symbol was created.
 | ||||
|                             ret.find_import_symbol(target_function.name, dependency_index, import_ref); | ||||
|                         } | ||||
| 
 | ||||
|                         // Create a reloc to the event symbol.
 | ||||
|                         section_out.relocs.emplace_back(N64Recomp::Reloc{ | ||||
|                             .address = cur_reloc.address, | ||||
|                             .target_section_offset = output_section_offset, | ||||
|                             .symbol_index = static_cast<uint32_t>(import_ref.symbol_index), | ||||
|                             .target_section = N64Recomp::SectionImport, | ||||
|                             .type = cur_reloc.type, | ||||
|                             .reference_symbol = true, | ||||
|                         }); | ||||
|                     } | ||||
|                     // Not an import or event section, so handle the reloc normally.
 | ||||
|                     else { | ||||
|                         uint32_t target_rom_to_ram = target_section.ram_addr - target_section.rom_addr; | ||||
|                         bool is_noload = target_section.rom_addr == (uint32_t)-1; | ||||
|                         if (!is_noload && target_rom_to_ram != cur_rom_to_ram) { | ||||
|                             fmt::print("Reloc at address 0x{:08X} in section {} points to a different section.\n", | ||||
|                                 cur_reloc.address, cur_section.name); | ||||
|                             return {}; | ||||
|                         } | ||||
|                         section_out.relocs.emplace_back(N64Recomp::Reloc{ | ||||
|                             .address = cur_reloc.address, | ||||
|                             // Use the original target section offset, this will be recalculated based on the output section afterwards.
 | ||||
|                             .target_section_offset = cur_reloc.target_section_offset, | ||||
|                             .symbol_index = 0, | ||||
|                             // Use the input section index in the reloc, this will be converted to the output section afterwards.
 | ||||
|                             .target_section = cur_reloc.target_section, | ||||
|                             .type = cur_reloc.type, | ||||
|                             .reference_symbol = false, | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Fix up every internal reloc's target section based on the input to output section mapping.
 | ||||
|     for (auto& section : ret.sections) { | ||||
|         for (auto& reloc : section.relocs) { | ||||
|             if (!reloc.reference_symbol) { | ||||
|                 uint16_t input_section_index = reloc.target_section; | ||||
|                 auto find_it = input_section_to_output_section.find(input_section_index); | ||||
|                 if (find_it == input_section_to_output_section.end()) { | ||||
|                     fmt::print("Reloc at address 0x{:08X} references section {}, which didn't get mapped to an output section\n", | ||||
|                         reloc.address, input_context.sections[input_section_index].name); | ||||
|                     return {}; | ||||
|                 } | ||||
|                 uint16_t output_section_index = find_it->second; | ||||
|                 const auto& input_section = input_context.sections[input_section_index]; | ||||
|                 const auto& output_section = ret.sections[output_section_index]; | ||||
|                 // Adjust the reloc's target section offset based on the reloc's new section.
 | ||||
|                 reloc.target_section_offset = reloc.target_section_offset + input_section.ram_addr - output_section.ram_addr; | ||||
|                 // Replace the target section with the mapped output section.
 | ||||
|                 reloc.target_section = find_it->second; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Copy the reference sections from the input context as-is for resolving reference symbol relocations.
 | ||||
|     ret.copy_reference_sections_from(input_context); | ||||
| 
 | ||||
|     good = true; | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| int main(int argc, const char** argv) { | ||||
|     if (argc != 2) { | ||||
|         fmt::print("Usage: {} [mod toml]\n", argv[0]); | ||||
|         return EXIT_SUCCESS; | ||||
|     } | ||||
| 
 | ||||
|     bool config_good; | ||||
|     ModConfig config = parse_mod_config(argv[1], config_good); | ||||
| 
 | ||||
|     if (!config_good) { | ||||
|         fmt::print(stderr, "Failed to read mod config file: {}\n", argv[1]); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     N64Recomp::Context context{}; | ||||
| 
 | ||||
|     // Import symbols from symbols files that were provided.
 | ||||
|     { | ||||
|         // Create a new temporary context to read the function reference symbol file into, since it's the same format as the recompilation symbol file.
 | ||||
|         std::vector<uint8_t> dummy_rom{}; | ||||
|         N64Recomp::Context reference_context{}; | ||||
|         if (!N64Recomp::Context::from_symbol_file(config.func_reference_syms_file_path, std::move(dummy_rom), reference_context, false)) { | ||||
|             fmt::print(stderr, "Failed to load provided function reference symbol file\n"); | ||||
|             return EXIT_FAILURE; | ||||
|         } | ||||
| 
 | ||||
|         // Use the reference context to build a reference symbol list for the actual context.
 | ||||
|         if (!context.import_reference_context(reference_context)) { | ||||
|             fmt::print(stderr, "Internal error: failed to import reference context. Please report this issue.\n"); | ||||
|             return EXIT_FAILURE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (const std::filesystem::path& cur_data_sym_path : config.data_reference_syms_file_paths) { | ||||
|         if (!context.read_data_reference_syms(cur_data_sym_path)) { | ||||
|             fmt::print(stderr, "Failed to load provided data reference symbol file: {}\n", cur_data_sym_path.string()); | ||||
|             return EXIT_FAILURE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Copy the dependencies from the config into the context.
 | ||||
|     context.add_dependencies(config.dependencies); | ||||
| 
 | ||||
|     N64Recomp::ElfParsingConfig elf_config { | ||||
|         .bss_section_suffix = {}, | ||||
|         .manually_sized_funcs = {}, | ||||
|         .relocatable_sections = {}, | ||||
|         .has_entrypoint = false, | ||||
|         .entrypoint_address = 0, | ||||
|         .use_absolute_symbols = false, | ||||
|         .unpaired_lo16_warnings = false, | ||||
|         .all_sections_relocatable = true | ||||
|     }; | ||||
|     bool dummy_found_entrypoint; | ||||
|     N64Recomp::DataSymbolMap dummy_syms_map; | ||||
|     bool elf_good = N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, false, dummy_syms_map, dummy_found_entrypoint); | ||||
| 
 | ||||
|     if (!elf_good) { | ||||
|         fmt::print(stderr, "Failed to parse mod elf\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     if (context.sections.size() == 0) { | ||||
|         fmt::print(stderr, "No sections found in mod elf\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     bool mod_context_good; | ||||
|     N64Recomp::Context mod_context = build_mod_context(context, mod_context_good); | ||||
|     std::vector<uint8_t> symbols_bin = N64Recomp::symbols_to_bin_v1(mod_context); | ||||
|     if (symbols_bin.empty()) { | ||||
|         fmt::print(stderr, "Failed to create symbol file\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     std::ofstream output_syms_file{ config.output_syms_path, std::ios::binary }; | ||||
|     output_syms_file.write(reinterpret_cast<const char*>(symbols_bin.data()), symbols_bin.size()); | ||||
| 
 | ||||
|     std::ofstream output_binary_file{ config.output_binary_path, std::ios::binary }; | ||||
|     output_binary_file.write(reinterpret_cast<const char*>(mod_context.rom.data()), mod_context.rom.size()); | ||||
| 
 | ||||
|     return EXIT_SUCCESS; | ||||
| } | ||||
							
								
								
									
										56
									
								
								include/generator.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								include/generator.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| #ifndef __GENERATOR_H__ | ||||
| #define __GENERATOR_H__ | ||||
| 
 | ||||
| #include "n64recomp.h" | ||||
| #include "operations.h" | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     struct InstructionContext { | ||||
|         int rd; | ||||
|         int rs; | ||||
|         int rt; | ||||
|         int sa; | ||||
| 
 | ||||
|         int fd; | ||||
|         int fs; | ||||
|         int ft; | ||||
| 
 | ||||
|         int cop1_cs; | ||||
| 
 | ||||
|         uint16_t imm16; | ||||
| 
 | ||||
|         bool reloc_tag_as_reference; | ||||
|         RelocType reloc_type; | ||||
|         uint32_t reloc_section_index; | ||||
|         uint32_t reloc_target_section_offset; | ||||
|     }; | ||||
| 
 | ||||
|     class Generator { | ||||
|     public: | ||||
|         virtual void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const = 0; | ||||
|         virtual void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const = 0; | ||||
|         virtual void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const = 0; | ||||
|         virtual void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const = 0; | ||||
|         virtual void emit_branch_close(std::ostream& output_file) const = 0; | ||||
|         virtual void emit_check_fr(std::ostream& output_file, int fpr) const = 0; | ||||
|         virtual void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const = 0; | ||||
|     }; | ||||
| 
 | ||||
|     class CGenerator final : Generator { | ||||
|     public: | ||||
|         CGenerator() = default; | ||||
|         void process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const final; | ||||
|         void process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const final; | ||||
|         void process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const final; | ||||
|         void emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const final; | ||||
|         void emit_branch_close(std::ostream& output_file) const final; | ||||
|         void emit_check_fr(std::ostream& output_file, int fpr) const final; | ||||
|         void emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const final; | ||||
|     private: | ||||
|         void get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_string) const; | ||||
|         void get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const; | ||||
|         void get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										556
									
								
								include/n64recomp.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										556
									
								
								include/n64recomp.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,556 @@ | |||
| #ifndef __RECOMP_PORT__ | ||||
| #define __RECOMP_PORT__ | ||||
| 
 | ||||
| #include <span> | ||||
| #include <string_view> | ||||
| #include <cstdint> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include <unordered_map> | ||||
| #include <unordered_set> | ||||
| #include <filesystem> | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| inline uint32_t byteswap(uint32_t val) { | ||||
|     return _byteswap_ulong(val); | ||||
| } | ||||
| #else | ||||
| constexpr uint32_t byteswap(uint32_t val) { | ||||
|     return __builtin_bswap32(val); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     struct Function { | ||||
|         uint32_t vram; | ||||
|         uint32_t rom; | ||||
|         std::vector<uint32_t> words; | ||||
|         std::string name; | ||||
|         uint16_t section_index; | ||||
|         bool ignored; | ||||
|         bool reimplemented; | ||||
|         bool stubbed; | ||||
|         std::unordered_map<int32_t, std::string> function_hooks; | ||||
| 
 | ||||
|         Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, uint16_t section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false) | ||||
|                 : vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {} | ||||
|         Function() = default; | ||||
|     }; | ||||
| 
 | ||||
|     enum class RelocType : uint8_t { | ||||
|         R_MIPS_NONE = 0, | ||||
|         R_MIPS_16, | ||||
|         R_MIPS_32, | ||||
|         R_MIPS_REL32, | ||||
|         R_MIPS_26, | ||||
|         R_MIPS_HI16, | ||||
|         R_MIPS_LO16, | ||||
|         R_MIPS_GPREL16, | ||||
|     }; | ||||
| 
 | ||||
|     struct Reloc { | ||||
|         uint32_t address; | ||||
|         uint32_t target_section_offset; | ||||
|         uint32_t symbol_index; // Only used for reference symbols and special section symbols
 | ||||
|         uint16_t target_section; | ||||
|         RelocType type; | ||||
|         bool reference_symbol; | ||||
|     }; | ||||
| 
 | ||||
|     // Special section indices.
 | ||||
|     constexpr uint16_t SectionAbsolute = (uint16_t)-2; | ||||
|     constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods
 | ||||
|     constexpr uint16_t SectionEvent = (uint16_t)-4; | ||||
| 
 | ||||
|     // Special section names.
 | ||||
|     constexpr std::string_view PatchSectionName = ".recomp_patch"; | ||||
|     constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch"; | ||||
|     constexpr std::string_view ExportSectionName = ".recomp_export"; | ||||
|     constexpr std::string_view EventSectionName = ".recomp_event"; | ||||
|     constexpr std::string_view ImportSectionPrefix = ".recomp_import."; | ||||
|     constexpr std::string_view CallbackSectionPrefix = ".recomp_callback."; | ||||
| 
 | ||||
|     // Special mod names.
 | ||||
|     constexpr std::string_view ModSelf = "."; | ||||
|     constexpr std::string_view ModBaseRecomp = "*"; | ||||
| 
 | ||||
|     struct Section { | ||||
|         uint32_t rom_addr = 0; | ||||
|         uint32_t ram_addr = 0; | ||||
|         uint32_t size = 0; | ||||
|         uint32_t bss_size = 0; // not populated when using a symbol toml
 | ||||
|         std::vector<uint32_t> function_addrs; // only used by the CLI (to find the size of static functions)
 | ||||
|         std::vector<Reloc> relocs; | ||||
|         std::string name; | ||||
|         uint16_t bss_section_index = (uint16_t)-1; | ||||
|         bool executable = false; | ||||
|         bool relocatable = false; // TODO is this needed? relocs being non-empty should be an equivalent check.
 | ||||
|         bool has_mips32_relocs = false; | ||||
|     }; | ||||
| 
 | ||||
|     struct ReferenceSection { | ||||
|         uint32_t rom_addr; | ||||
|         uint32_t ram_addr; | ||||
|         uint32_t size; | ||||
|         bool relocatable; | ||||
|     }; | ||||
| 
 | ||||
|     struct ReferenceSymbol { | ||||
|         std::string name; | ||||
|         uint16_t section_index; | ||||
|         uint32_t section_offset; | ||||
|         bool is_function; | ||||
|     }; | ||||
| 
 | ||||
|     struct ElfParsingConfig { | ||||
|         std::string bss_section_suffix; | ||||
|         // Functions with manual size overrides
 | ||||
|         std::unordered_map<std::string, size_t> manually_sized_funcs; | ||||
|         // The section names that were specified as relocatable
 | ||||
|         std::unordered_set<std::string> relocatable_sections; | ||||
|         bool has_entrypoint; | ||||
|         int32_t entrypoint_address; | ||||
|         bool use_absolute_symbols; | ||||
|         bool unpaired_lo16_warnings; | ||||
|         bool all_sections_relocatable; | ||||
|     }; | ||||
|      | ||||
|     struct DataSymbol { | ||||
|         uint32_t vram; | ||||
|         std::string name; | ||||
| 
 | ||||
|         DataSymbol(uint32_t vram, std::string&& name) : vram(vram), name(std::move(name)) {} | ||||
|     }; | ||||
| 
 | ||||
|     using DataSymbolMap = std::unordered_map<uint16_t, std::vector<DataSymbol>>; | ||||
| 
 | ||||
|     extern const std::unordered_set<std::string> reimplemented_funcs; | ||||
|     extern const std::unordered_set<std::string> ignored_funcs; | ||||
|     extern const std::unordered_set<std::string> renamed_funcs; | ||||
| 
 | ||||
|     struct Dependency { | ||||
|         uint8_t major_version; | ||||
|         uint8_t minor_version; | ||||
|         uint8_t patch_version; | ||||
|         std::string mod_id; | ||||
|     }; | ||||
| 
 | ||||
|     struct ImportSymbol { | ||||
|         ReferenceSymbol base; | ||||
|         size_t dependency_index; | ||||
|     }; | ||||
| 
 | ||||
|     struct DependencyEvent { | ||||
|         size_t dependency_index; | ||||
|         std::string event_name; | ||||
|     }; | ||||
| 
 | ||||
|     struct EventSymbol { | ||||
|         ReferenceSymbol base; | ||||
|     }; | ||||
| 
 | ||||
|     struct Callback { | ||||
|         size_t function_index; | ||||
|         size_t dependency_event_index; | ||||
|     }; | ||||
| 
 | ||||
|     struct SymbolReference { | ||||
|         // Reference symbol section index, or one of the special section indices such as SectionImport.
 | ||||
|         uint16_t section_index; | ||||
|         size_t symbol_index; | ||||
|     }; | ||||
| 
 | ||||
|     enum class ReplacementFlags : uint32_t { | ||||
|         Force = 1 << 0, | ||||
|     }; | ||||
|     inline ReplacementFlags operator&(ReplacementFlags lhs, ReplacementFlags rhs) { return ReplacementFlags(uint32_t(lhs) & uint32_t(rhs)); } | ||||
|     inline ReplacementFlags operator|(ReplacementFlags lhs, ReplacementFlags rhs) { return ReplacementFlags(uint32_t(lhs) | uint32_t(rhs)); } | ||||
| 
 | ||||
|     struct FunctionReplacement { | ||||
|         uint32_t func_index; | ||||
|         uint32_t original_section_vrom; | ||||
|         uint32_t original_vram; | ||||
|         ReplacementFlags flags; | ||||
|     }; | ||||
| 
 | ||||
|     class Context { | ||||
|     private: | ||||
|         //// Reference symbols (used for populating relocations for patches)
 | ||||
|         // A list of the sections that contain the reference symbols.
 | ||||
|         std::vector<ReferenceSection> reference_sections; | ||||
|         // A list of the reference symbols.
 | ||||
|         std::vector<ReferenceSymbol> reference_symbols; | ||||
|         // Mapping of symbol name to reference symbol index.
 | ||||
|         std::unordered_map<std::string, SymbolReference> reference_symbols_by_name; | ||||
|     public: | ||||
|         std::vector<Section> sections; | ||||
|         std::vector<Function> functions; | ||||
|         // A list of the list of each function (by index in `functions`) in a given section
 | ||||
|         std::vector<std::vector<size_t>> section_functions; | ||||
|         // A mapping of vram address to every function with that address.
 | ||||
|         std::unordered_map<uint32_t, std::vector<size_t>> functions_by_vram; | ||||
|         // A mapping of bss section index to the corresponding non-bss section index.
 | ||||
|         std::unordered_map<uint16_t, uint16_t> bss_section_to_section; | ||||
|         // The target ROM being recompiled, TODO move this outside of the context to avoid making a copy for mod contexts.
 | ||||
|         // Used for reading relocations and for the output binary feature.
 | ||||
|         std::vector<uint8_t> rom; | ||||
| 
 | ||||
|         //// Only used by the CLI, TODO move this to a struct in the internal headers.
 | ||||
|         // A mapping of function name to index in the functions vector
 | ||||
|         std::unordered_map<std::string, size_t> functions_by_name; | ||||
| 
 | ||||
|         //// Mod dependencies and their symbols
 | ||||
|          | ||||
|         //// Imported values
 | ||||
|         // List of dependencies.
 | ||||
|         std::vector<Dependency> dependencies; | ||||
|         // Mapping of dependency name to dependency index.
 | ||||
|         std::unordered_map<std::string, size_t> dependencies_by_name; | ||||
|         // List of symbols imported from dependencies.
 | ||||
|         std::vector<ImportSymbol> import_symbols; | ||||
|         // List of events imported from dependencies.
 | ||||
|         std::vector<DependencyEvent> dependency_events; | ||||
|         // Mappings of dependency event name to the index in dependency_events, all indexed by dependency.
 | ||||
|         std::vector<std::unordered_map<std::string, size_t>> dependency_events_by_name; | ||||
|         // Mappings of dependency import name to index in import_symbols, all indexed by dependency.
 | ||||
|         std::vector<std::unordered_map<std::string, size_t>> dependency_imports_by_name; | ||||
| 
 | ||||
|         //// Exported values
 | ||||
|         // List of function replacements, which contains the original function to replace and the function index to replace it with.
 | ||||
|         std::vector<FunctionReplacement> replacements; | ||||
|         // Indices of every exported function.
 | ||||
|         std::vector<size_t> exported_funcs; | ||||
|         // List of callbacks, which contains the function for the callback and the dependency event it attaches to.
 | ||||
|         std::vector<Callback> callbacks; | ||||
|         // List of symbols from events, which contains the names of events that this context provides.
 | ||||
|         std::vector<EventSymbol> event_symbols; | ||||
| 
 | ||||
|         // Imports sections and function symbols from a provided context into this context's reference sections and reference functions.
 | ||||
|         bool import_reference_context(const Context& reference_context); | ||||
|         // Reads a data symbol file and adds its contents into this context's reference data symbols.
 | ||||
|         bool read_data_reference_syms(const std::filesystem::path& data_syms_file_path); | ||||
| 
 | ||||
|         static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, Context& out, bool with_relocs); | ||||
|         static bool from_elf_file(const std::filesystem::path& elf_file_path, Context& out, const ElfParsingConfig& flags, bool for_dumping_context, DataSymbolMap& data_syms_out, bool& found_entrypoint_out); | ||||
| 
 | ||||
|         Context() = default; | ||||
| 
 | ||||
|         bool add_dependency(const std::string& id, uint8_t major_version, uint8_t minor_version, uint8_t patch_version) { | ||||
|             if (dependencies_by_name.contains(id)) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             size_t dependency_index = dependencies.size(); | ||||
|             dependencies.emplace_back(N64Recomp::Dependency { | ||||
|                 .major_version = major_version, | ||||
|                 .minor_version = minor_version, | ||||
|                 .patch_version = patch_version, | ||||
|                 .mod_id = id | ||||
|             }); | ||||
| 
 | ||||
|             dependencies_by_name.emplace(id, dependency_index); | ||||
|             dependency_events_by_name.resize(dependencies.size()); | ||||
|             dependency_imports_by_name.resize(dependencies.size()); | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool add_dependencies(const std::vector<Dependency>& new_dependencies) { | ||||
|             dependencies.reserve(dependencies.size() + new_dependencies.size()); | ||||
|             dependencies_by_name.reserve(dependencies_by_name.size() + new_dependencies.size()); | ||||
| 
 | ||||
|             // Check if any of the dependencies already exist and fail if so.
 | ||||
|             for (const Dependency& dep : new_dependencies) { | ||||
|                 if (dependencies_by_name.contains(dep.mod_id)) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             for (const Dependency& dep : new_dependencies) { | ||||
|                 size_t dependency_index = dependencies.size(); | ||||
|                 dependencies.emplace_back(dep); | ||||
|                 dependencies_by_name.emplace(dep.mod_id, dependency_index); | ||||
|             } | ||||
| 
 | ||||
|             dependency_events_by_name.resize(dependencies.size()); | ||||
|             dependency_imports_by_name.resize(dependencies.size()); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool find_dependency(const std::string& mod_id, size_t& dependency_index) { | ||||
|             auto find_it = dependencies_by_name.find(mod_id); | ||||
|             if (find_it == dependencies_by_name.end()) { | ||||
|                 return false; | ||||
|             } | ||||
|             dependency_index = find_it->second; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         size_t find_function_by_vram_section(uint32_t vram, size_t section_index) const { | ||||
|             auto find_it = functions_by_vram.find(vram); | ||||
|             if (find_it == functions_by_vram.end()) { | ||||
|                 return (size_t)-1; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t function_index : find_it->second) { | ||||
|                 if (functions[function_index].section_index == section_index) { | ||||
|                     return function_index; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return (size_t)-1; | ||||
|         } | ||||
| 
 | ||||
|         bool has_reference_symbols() const { | ||||
|             return !reference_symbols.empty() || !import_symbols.empty() || !event_symbols.empty(); | ||||
|         } | ||||
| 
 | ||||
|         bool is_regular_reference_section(uint16_t section_index) const { | ||||
|             return section_index != SectionImport && section_index != SectionEvent; | ||||
|         } | ||||
| 
 | ||||
|         bool find_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { | ||||
|             auto find_sym_it = reference_symbols_by_name.find(symbol_name); | ||||
| 
 | ||||
|             // Check if the symbol was found.
 | ||||
|             if (find_sym_it == reference_symbols_by_name.end()) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             ref_out = find_sym_it->second; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool reference_symbol_exists(const std::string& symbol_name) const { | ||||
|             SymbolReference dummy_ref; | ||||
|             return find_reference_symbol(symbol_name, dummy_ref); | ||||
|         } | ||||
| 
 | ||||
|         bool find_regular_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { | ||||
|             SymbolReference ref_found; | ||||
|             if (!find_reference_symbol(symbol_name, ref_found)) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             // Ignore reference symbols in special sections.
 | ||||
|             if (!is_regular_reference_section(ref_found.section_index)) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             ref_out = ref_found; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         const ReferenceSymbol& get_reference_symbol(uint16_t section_index, size_t symbol_index) const { | ||||
|             if (section_index == SectionImport) { | ||||
|                 return import_symbols[symbol_index].base; | ||||
|             } | ||||
|             else if (section_index == SectionEvent) { | ||||
|                 return event_symbols[symbol_index].base; | ||||
|             } | ||||
|             return reference_symbols[symbol_index]; | ||||
|         } | ||||
| 
 | ||||
|         size_t num_regular_reference_symbols() { | ||||
|             return reference_symbols.size(); | ||||
|         } | ||||
| 
 | ||||
|         const ReferenceSymbol& get_regular_reference_symbol(size_t index) const { | ||||
|             return reference_symbols[index]; | ||||
|         } | ||||
| 
 | ||||
|         const ReferenceSymbol& get_reference_symbol(const SymbolReference& ref) const { | ||||
|             return get_reference_symbol(ref.section_index, ref.symbol_index); | ||||
|         } | ||||
| 
 | ||||
|         bool is_reference_section_relocatable(uint16_t section_index) const { | ||||
|             if (section_index == SectionAbsolute) { | ||||
|                 return false; | ||||
|             } | ||||
|             else if (section_index == SectionImport || section_index == SectionEvent) { | ||||
|                 return true; | ||||
|             } | ||||
|             return reference_sections[section_index].relocatable; | ||||
|         } | ||||
| 
 | ||||
|         bool add_reference_symbol(const std::string& symbol_name, uint16_t section_index, uint32_t vram, bool is_function) { | ||||
|             uint32_t section_vram; | ||||
| 
 | ||||
|             if (section_index == SectionAbsolute) { | ||||
|                 section_vram = 0; | ||||
|             } | ||||
|             else if (section_index < reference_sections.size()) { | ||||
|                 section_vram = reference_sections[section_index].ram_addr; | ||||
|             } | ||||
|             // Invalid section index.
 | ||||
|             else { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so.
 | ||||
|             reference_symbols_by_name.emplace(symbol_name, N64Recomp::SymbolReference{ | ||||
|                 .section_index = section_index, | ||||
|                 .symbol_index = reference_symbols.size() | ||||
|             }); | ||||
| 
 | ||||
|             reference_symbols.emplace_back(N64Recomp::ReferenceSymbol{ | ||||
|                 .name = symbol_name, | ||||
|                 .section_index = section_index, | ||||
|                 .section_offset = vram - section_vram, | ||||
|                 .is_function = is_function | ||||
|             }); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         void add_import_symbol(const std::string& symbol_name, size_t dependency_index) { | ||||
|             // TODO Check if dependency_imports_by_name[dependency_index] already contains the name and show a conflict error if so.
 | ||||
|             dependency_imports_by_name[dependency_index][symbol_name] = import_symbols.size(); | ||||
|             import_symbols.emplace_back( | ||||
|                 N64Recomp::ImportSymbol { | ||||
|                     .base = N64Recomp::ReferenceSymbol { | ||||
|                         .name = symbol_name, | ||||
|                         .section_index = N64Recomp::SectionImport, | ||||
|                         .section_offset = 0, | ||||
|                         .is_function = true | ||||
|                     }, | ||||
|                     .dependency_index = dependency_index, | ||||
|                 } | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         bool find_import_symbol(const std::string& symbol_name, size_t dependency_index, SymbolReference& ref_out) const { | ||||
|             if (dependency_index >= dependencies.size()) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             auto find_it = dependency_imports_by_name[dependency_index].find(symbol_name); | ||||
|             if (find_it == dependency_imports_by_name[dependency_index].end()) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             ref_out.section_index = SectionImport; | ||||
|             ref_out.symbol_index = find_it->second; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         void add_event_symbol(const std::string& symbol_name) { | ||||
|             // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so.
 | ||||
|             reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { | ||||
|                 .section_index = N64Recomp::SectionEvent, | ||||
|                 .symbol_index = event_symbols.size() | ||||
|             }; | ||||
|             event_symbols.emplace_back( | ||||
|                 N64Recomp::EventSymbol { | ||||
|                     .base = N64Recomp::ReferenceSymbol { | ||||
|                         .name = symbol_name, | ||||
|                         .section_index = N64Recomp::SectionEvent, | ||||
|                         .section_offset = 0, | ||||
|                         .is_function = true | ||||
|                     } | ||||
|                 } | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         bool find_event_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { | ||||
|             SymbolReference ref_found; | ||||
|             if (!find_reference_symbol(symbol_name, ref_found)) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             // Ignore reference symbols that aren't in the event section.
 | ||||
|             if (ref_found.section_index != SectionEvent) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             ref_out = ref_found; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool add_dependency_event(const std::string& event_name, size_t dependency_index, size_t& dependency_event_index) { | ||||
|             if (dependency_index >= dependencies.size()) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             // Prevent adding the same event to a dependency twice. This isn't an error, since a mod could register
 | ||||
|             // multiple callbacks to the same event.
 | ||||
|             auto find_it = dependency_events_by_name[dependency_index].find(event_name); | ||||
|             if (find_it != dependency_events_by_name[dependency_index].end()) { | ||||
|                 dependency_event_index = find_it->second; | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             dependency_event_index = dependency_events.size(); | ||||
|             dependency_events.emplace_back(DependencyEvent{ | ||||
|                 .dependency_index = dependency_index, | ||||
|                 .event_name = event_name | ||||
|             }); | ||||
|             dependency_events_by_name[dependency_index][event_name] = dependency_event_index; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         bool add_callback(size_t dependency_event_index, size_t function_index) { | ||||
|             callbacks.emplace_back(Callback{ | ||||
|                 .function_index = function_index, | ||||
|                 .dependency_event_index = dependency_event_index | ||||
|             }); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         uint32_t get_reference_section_vram(uint16_t section_index) const { | ||||
|             if (section_index == N64Recomp::SectionAbsolute) { | ||||
|                 return 0; | ||||
|             } | ||||
|             else if (!is_regular_reference_section(section_index)) { | ||||
|                 return 0; | ||||
|             } | ||||
|             else { | ||||
|                 return reference_sections[section_index].ram_addr; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         uint32_t get_reference_section_rom(uint16_t section_index) const { | ||||
|             if (section_index == N64Recomp::SectionAbsolute) { | ||||
|                 return (uint32_t)-1; | ||||
|             } | ||||
|             else if (!is_regular_reference_section(section_index)) { | ||||
|                 return (uint32_t)-1; | ||||
|             } | ||||
|             else { | ||||
|                 return reference_sections[section_index].rom_addr; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         void copy_reference_sections_from(const Context& rhs) { | ||||
|             reference_sections = rhs.reference_sections; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     bool recompile_function(const Context& context, const Function& func, std::ofstream& output_file, std::span<std::vector<uint32_t>> static_funcs, bool tag_reference_relocs); | ||||
| 
 | ||||
|     enum class ModSymbolsError { | ||||
|         Good, | ||||
|         NotASymbolFile, | ||||
|         UnknownSymbolFileVersion, | ||||
|         CorruptSymbolFile, | ||||
|         FunctionOutOfBounds, | ||||
|     }; | ||||
| 
 | ||||
|     ModSymbolsError parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& context_out); | ||||
|     std::vector<uint8_t> symbols_to_bin_v1(const Context& mod_context); | ||||
| 
 | ||||
|     inline bool validate_mod_name(std::string_view str) { | ||||
|         // Disallow mod names with a colon in them, since you can't specify that in a dependency string orin callbacks.
 | ||||
|         for (char c : str) { | ||||
|             if (c == ':') { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     inline bool validate_mod_name(const std::string& str) { | ||||
|         return validate_mod_name(std::string_view{str}); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										200
									
								
								include/operations.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								include/operations.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,200 @@ | |||
| #ifndef __OPERATIONS_H__ | ||||
| #define __OPERATIONS_H__ | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| #include "rabbitizer.hpp" | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     using InstrId = rabbitizer::InstrId::UniqueId; | ||||
|     using Cop0Reg = rabbitizer::Registers::Cpu::Cop0; | ||||
| 
 | ||||
|     enum class StoreOpType { | ||||
|         SD, | ||||
|         SDL, | ||||
|         SDR, | ||||
|         SW, | ||||
|         SWL, | ||||
|         SWR, | ||||
|         SH, | ||||
|         SB, | ||||
|         SDC1, | ||||
|         SWC1 | ||||
|     }; | ||||
| 
 | ||||
|     enum class UnaryOpType { | ||||
|         None, | ||||
|         ToS32, | ||||
|         ToU32, | ||||
|         ToS64, | ||||
|         ToU64, | ||||
|         NegateS32, | ||||
|         NegateS64, | ||||
|         Lui, | ||||
|         Mask5, // Mask to 5 bits
 | ||||
|         Mask6, // Mask to 5 bits
 | ||||
|         ToInt32, // Functionally equivalent to ToS32, only exists for parity with old codegen
 | ||||
|         Negate, | ||||
|         AbsFloat, | ||||
|         AbsDouble, | ||||
|         SqrtFloat, | ||||
|         SqrtDouble, | ||||
|         ConvertSFromW, | ||||
|         ConvertWFromS, | ||||
|         ConvertDFromW, | ||||
|         ConvertWFromD, | ||||
|         ConvertDFromS, | ||||
|         ConvertSFromD, | ||||
|         ConvertDFromL, | ||||
|         ConvertLFromD, | ||||
|         ConvertSFromL, | ||||
|         ConvertLFromS, | ||||
|         TruncateWFromS, | ||||
|         TruncateWFromD, | ||||
|         RoundWFromS, | ||||
|         RoundWFromD, | ||||
|         CeilWFromS, | ||||
|         CeilWFromD, | ||||
|         FloorWFromS, | ||||
|         FloorWFromD | ||||
|     }; | ||||
| 
 | ||||
|     enum class BinaryOpType { | ||||
|         // Addition/subtraction
 | ||||
|         Add32, | ||||
|         Sub32, | ||||
|         Add64, | ||||
|         Sub64, | ||||
|         // Float arithmetic
 | ||||
|         AddFloat, | ||||
|         AddDouble, | ||||
|         SubFloat, | ||||
|         SubDouble, | ||||
|         MulFloat, | ||||
|         MulDouble, | ||||
|         DivFloat, | ||||
|         DivDouble, | ||||
|         // Bitwise
 | ||||
|         And64, | ||||
|         Or64, | ||||
|         Nor64, | ||||
|         Xor64, | ||||
|         Sll32, | ||||
|         Sll64, | ||||
|         Srl32, | ||||
|         Srl64, | ||||
|         Sra32, | ||||
|         Sra64, | ||||
|         // Comparisons
 | ||||
|         Equal, | ||||
|         NotEqual, | ||||
|         Less, | ||||
|         LessEq, | ||||
|         Greater, | ||||
|         GreaterEq, | ||||
|         // Loads
 | ||||
|         LD, | ||||
|         LW, | ||||
|         LWU, | ||||
|         LH, | ||||
|         LHU, | ||||
|         LB, | ||||
|         LBU, | ||||
|         LDL, | ||||
|         LDR, | ||||
|         LWL, | ||||
|         LWR, | ||||
|         // Fixed result
 | ||||
|         True, | ||||
|         False, | ||||
| 
 | ||||
|         COUNT, | ||||
|     }; | ||||
| 
 | ||||
|     enum class Operand { | ||||
|         Rd, // GPR
 | ||||
|         Rs, // GPR
 | ||||
|         Rt, // GPR
 | ||||
|         Fd, // FPR
 | ||||
|         Fs, // FPR
 | ||||
|         Ft, // FPR
 | ||||
|         FdDouble, // Double float in fd FPR
 | ||||
|         FsDouble, // Double float in fs FPR
 | ||||
|         FtDouble, // Double float in ft FPR
 | ||||
|         // Raw low 32-bit values of FPRs with handling for mips3 float mode behavior
 | ||||
|         FdU32L, | ||||
|         FsU32L, | ||||
|         FtU32L, | ||||
|         // Raw high 32-bit values of FPRs with handling for mips3 float mode behavior
 | ||||
|         FdU32H, | ||||
|         FsU32H, | ||||
|         FtU32H, | ||||
|         // Raw 64-bit values of FPRs
 | ||||
|         FdU64, | ||||
|         FsU64, | ||||
|         FtU64, | ||||
|         ImmU16, // 16-bit immediate, unsigned
 | ||||
|         ImmS16, // 16-bit immediate, signed
 | ||||
|         Sa, // Shift amount
 | ||||
|         Sa32, // Shift amount plus 32
 | ||||
|         Cop1cs, // Coprocessor 1 Condition Signal
 | ||||
|         Hi, | ||||
|         Lo, | ||||
|         Zero, | ||||
| 
 | ||||
|         Base = Rs, // Alias for Rs for loads
 | ||||
|     }; | ||||
| 
 | ||||
|     struct StoreOp { | ||||
|         StoreOpType type; | ||||
|         Operand value_input; | ||||
|     }; | ||||
| 
 | ||||
|     struct UnaryOp { | ||||
|         UnaryOpType operation; | ||||
|         Operand output; | ||||
|         Operand input; | ||||
|         // Whether the FR bit needs to be checked for odd float registers for this instruction.
 | ||||
|         bool check_fr = false; | ||||
|         // Whether the input need to be checked for being NaN.
 | ||||
|         bool check_nan = false; | ||||
|     }; | ||||
| 
 | ||||
|     struct BinaryOperands { | ||||
|         // Operation to apply to each operand before applying the binary operation to them.
 | ||||
|         UnaryOpType operand_operations[2]; | ||||
|         // The source of the input operands.
 | ||||
|         Operand operands[2]; | ||||
|     }; | ||||
| 
 | ||||
|     struct BinaryOp { | ||||
|         // The type of binary operation this represents.
 | ||||
|         BinaryOpType type; | ||||
|         // The output operand.
 | ||||
|         Operand output; | ||||
|         // The input operands.
 | ||||
|         BinaryOperands operands; | ||||
|         // Whether the FR bit needs to be checked for odd float registers for this instruction.
 | ||||
|         bool check_fr = false; | ||||
|         // Whether the inputs need to be checked for being NaN.
 | ||||
|         bool check_nan = false; | ||||
|     }; | ||||
| 
 | ||||
|     struct ConditionalBranchOp { | ||||
|         // The type of binary operation to use for this compare
 | ||||
|         BinaryOpType comparison; | ||||
|         // The input operands.
 | ||||
|         BinaryOperands operands; | ||||
|         // Whether this jump should link for returns.
 | ||||
|         bool link; | ||||
|         // Whether this jump has "likely" behavior (doesn't execute the delay slot if skipped).
 | ||||
|         bool likely; | ||||
|     }; | ||||
| 
 | ||||
|     extern const std::unordered_map<InstrId, UnaryOp> unary_ops; | ||||
|     extern const std::unordered_map<InstrId, BinaryOp> binary_ops; | ||||
|     extern const std::unordered_map<InstrId, ConditionalBranchOp> conditional_branch_ops; | ||||
|     extern const std::unordered_map<InstrId, StoreOp> store_ops; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,236 +0,0 @@ | |||
| #ifndef __RECOMP_PORT__ | ||||
| #define __RECOMP_PORT__ | ||||
| 
 | ||||
| #include <span> | ||||
| #include <string_view> | ||||
| #include <cstdint> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include <unordered_map> | ||||
| #include <span> | ||||
| #include <unordered_set> | ||||
| #include <filesystem> | ||||
| #include "rabbitizer.hpp" | ||||
| #include "elfio/elfio.hpp" | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| inline uint32_t byteswap(uint32_t val) { | ||||
|     return _byteswap_ulong(val); | ||||
| } | ||||
| #else | ||||
| constexpr uint32_t byteswap(uint32_t val) { | ||||
|     return __builtin_bswap32(val); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| namespace RecompPort { | ||||
| 
 | ||||
|     // Potential argument types for function declarations
 | ||||
|     enum class FunctionArgType { | ||||
|         u32, | ||||
|         s32, | ||||
|     }; | ||||
| 
 | ||||
|     // Mapping of function name to argument types
 | ||||
|     using DeclaredFunctionMap = std::unordered_map<std::string, std::vector<FunctionArgType>>; | ||||
| 
 | ||||
|     struct InstructionPatch { | ||||
|         std::string func_name; | ||||
|         int32_t vram; | ||||
|         uint32_t value; | ||||
|     }; | ||||
| 
 | ||||
|     struct FunctionHook { | ||||
|         std::string func_name; | ||||
|         int32_t before_vram; | ||||
|         std::string text; | ||||
|     }; | ||||
| 
 | ||||
|     struct FunctionSize { | ||||
|         std::string func_name; | ||||
|         uint32_t size_bytes; | ||||
| 
 | ||||
|         FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {} | ||||
|     }; | ||||
| 
 | ||||
|     struct ManualFunction { | ||||
|         std::string func_name; | ||||
|         std::string section_name; | ||||
|         uint32_t vram; | ||||
|         uint32_t 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 { | ||||
|         int32_t entrypoint; | ||||
|         int32_t functions_per_output_file; | ||||
|         bool has_entrypoint; | ||||
|         bool uses_mips3_float_mode; | ||||
|         bool single_file_output; | ||||
|         bool use_absolute_symbols; | ||||
|         bool unpaired_lo16_warnings; | ||||
|         std::filesystem::path elf_path; | ||||
|         std::filesystem::path symbols_file_path; | ||||
|         std::filesystem::path func_reference_syms_file_path; | ||||
|         std::vector<std::filesystem::path> data_reference_syms_file_paths; | ||||
|         std::filesystem::path rom_file_path; | ||||
|         std::filesystem::path output_func_path; | ||||
|         std::filesystem::path relocatable_sections_path; | ||||
|         std::filesystem::path output_binary_path; | ||||
|         std::vector<std::string> stubbed_funcs; | ||||
|         std::vector<std::string> ignored_funcs; | ||||
|         DeclaredFunctionMap declared_funcs; | ||||
|         std::vector<InstructionPatch> instruction_patches; | ||||
|         std::vector<FunctionHook> function_hooks; | ||||
|         std::vector<FunctionSize> manual_func_sizes; | ||||
|         std::vector<ManualFunction> manual_functions; | ||||
|         std::string bss_section_suffix; | ||||
|         std::string recomp_include; | ||||
| 
 | ||||
|         Config(const char* path); | ||||
|         bool good() { return !bad; } | ||||
|     private: | ||||
|         bool bad; | ||||
|     }; | ||||
| 
 | ||||
|     struct JumpTable { | ||||
|         uint32_t vram; | ||||
|         uint32_t addend_reg; | ||||
|         uint32_t rom; | ||||
|         uint32_t lw_vram; | ||||
|         uint32_t addu_vram; | ||||
|         uint32_t jr_vram; | ||||
|         std::vector<uint32_t> entries; | ||||
| 
 | ||||
|         JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, std::vector<uint32_t>&& entries) | ||||
|                 : vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), entries(std::move(entries)) {} | ||||
|     }; | ||||
| 
 | ||||
|     struct AbsoluteJump { | ||||
|         uint32_t jump_target; | ||||
|         uint32_t instruction_vram; | ||||
| 
 | ||||
|         AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {} | ||||
|     }; | ||||
| 
 | ||||
|     struct Function { | ||||
|         uint32_t vram; | ||||
|         uint32_t rom; | ||||
|         std::vector<uint32_t> words; | ||||
|         std::string name; | ||||
|         ELFIO::Elf_Half section_index; | ||||
|         bool ignored; | ||||
|         bool reimplemented; | ||||
|         bool stubbed; | ||||
|         std::unordered_map<int32_t, std::string> function_hooks; | ||||
| 
 | ||||
|         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) {} | ||||
|         Function() = default; | ||||
|     }; | ||||
| 
 | ||||
|     enum class RelocType : uint8_t { | ||||
|         R_MIPS_NONE = 0, | ||||
|         R_MIPS_16, | ||||
|         R_MIPS_32, | ||||
|         R_MIPS_REL32, | ||||
|         R_MIPS_26, | ||||
|         R_MIPS_HI16, | ||||
|         R_MIPS_LO16, | ||||
|         R_MIPS_GPREL16, | ||||
|     }; | ||||
| 
 | ||||
|     struct Reloc { | ||||
|         uint32_t address; | ||||
|         uint32_t section_offset; | ||||
|         uint32_t symbol_index; | ||||
|         uint32_t target_section; | ||||
|         RelocType type; | ||||
|         bool reference_symbol; | ||||
|     }; | ||||
| 
 | ||||
|     constexpr uint16_t SectionSelf = (uint16_t)-1; | ||||
|     constexpr uint16_t SectionAbsolute = (uint16_t)-2; | ||||
|     struct Section { | ||||
|         ELFIO::Elf_Xword rom_addr = 0; | ||||
|         ELFIO::Elf64_Addr ram_addr = 0; | ||||
|         ELFIO::Elf_Xword size = 0; | ||||
|         std::vector<uint32_t> function_addrs; | ||||
|         std::vector<Reloc> relocs; | ||||
|         std::string name; | ||||
|         ELFIO::Elf_Half bss_section_index = (ELFIO::Elf_Half)-1; | ||||
|         bool executable = false; | ||||
|         bool relocatable = false; | ||||
|         bool has_mips32_relocs = false; | ||||
|     }; | ||||
| 
 | ||||
|     struct FunctionStats { | ||||
|         std::vector<JumpTable> jump_tables; | ||||
|         std::vector<AbsoluteJump> absolute_jumps; | ||||
|     }; | ||||
| 
 | ||||
|     struct ReferenceSection { | ||||
|         uint32_t rom_addr; | ||||
|         uint32_t ram_addr; | ||||
|         uint32_t size; | ||||
|         bool relocatable; | ||||
|     }; | ||||
| 
 | ||||
|     struct ReferenceSymbol { | ||||
|         uint16_t section_index; | ||||
|         uint32_t section_offset; | ||||
|         bool is_function; | ||||
|     }; | ||||
| 
 | ||||
|     struct Context { | ||||
|         // ROM address of each section
 | ||||
|         std::vector<Section> sections; | ||||
|         std::vector<Function> functions; | ||||
|         std::unordered_map<uint32_t, std::vector<size_t>> functions_by_vram; | ||||
|         // A mapping of function name to index in the functions vector
 | ||||
|         std::unordered_map<std::string, size_t> functions_by_name; | ||||
|         std::vector<uint8_t> rom; | ||||
|         // A list of the list of each function (by index in `functions`) in a given section
 | ||||
|         std::vector<std::vector<size_t>> section_functions; | ||||
|         // The section names that were specified as relocatable
 | ||||
|         std::unordered_set<std::string> relocatable_sections; | ||||
|         // Functions with manual size overrides
 | ||||
|         std::unordered_map<std::string, size_t> manually_sized_funcs; | ||||
| 
 | ||||
|         //// Reference symbols (used for populating relocations for patches)
 | ||||
|         // A list of the sections that contain the reference symbols.
 | ||||
|         std::vector<ReferenceSection> reference_sections; | ||||
|         // A list of the reference symbols.
 | ||||
|         std::vector<ReferenceSymbol> reference_symbols; | ||||
|         // Name of every reference symbol in the same order as `reference_symbols`.
 | ||||
|         std::vector<std::string> reference_symbol_names; | ||||
|         // Mapping of symbol name to reference symbol index.
 | ||||
|         std::unordered_map<std::string, size_t> reference_symbols_by_name; | ||||
|         int executable_section_count; | ||||
| 
 | ||||
|         Context(const ELFIO::elfio& elf_file) { | ||||
|             sections.resize(elf_file.sections.size()); | ||||
|             section_functions.resize(elf_file.sections.size()); | ||||
|             functions.reserve(1024); | ||||
|             functions_by_vram.reserve(functions.capacity()); | ||||
|             functions_by_name.reserve(functions.capacity()); | ||||
|             rom.reserve(8 * 1024 * 1024); | ||||
|             executable_section_count = 0; | ||||
|         } | ||||
| 
 | ||||
|         // Imports sections and function symbols from a provided context into this context's reference sections and reference functions.
 | ||||
|         void import_reference_context(const Context& reference_context); | ||||
|         // Reads a data symbol file and adds its contents into this context's reference data symbols.
 | ||||
|         bool read_data_reference_syms(const std::filesystem::path& data_syms_file_path); | ||||
| 
 | ||||
|         static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, Context& out, bool with_relocs); | ||||
| 
 | ||||
|         Context() = default; | ||||
|     }; | ||||
| 
 | ||||
|     bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats); | ||||
|     bool recompile_function(const Context& context, const Config& config, const Function& func, std::ofstream& output_file, std::span<std::vector<uint32_t>> static_funcs, bool write_header); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
|  | @ -4,7 +4,8 @@ | |||
| #include "rabbitizer.hpp" | ||||
| #include "fmt/format.h" | ||||
| 
 | ||||
| #include "recomp_port.h" | ||||
| #include "n64recomp.h" | ||||
| #include "analysis.h" | ||||
| 
 | ||||
| extern "C" const char* RabbitizerRegister_getNameGpr(uint8_t regValue); | ||||
| 
 | ||||
|  | @ -49,7 +50,7 @@ struct RegState { | |||
| using InstrId = rabbitizer::InstrId::UniqueId; | ||||
| using RegId = rabbitizer::Registers::Cpu::GprO32; | ||||
| 
 | ||||
| bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPort::Function& func, RecompPort::FunctionStats& stats, | ||||
| bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const N64Recomp::Function& func, N64Recomp::FunctionStats& stats, | ||||
|     RegState reg_states[32], std::vector<RegState>& stack_states) { | ||||
|     // Temporary register state for tracking the register being operated on
 | ||||
|     RegState temp{}; | ||||
|  | @ -219,8 +220,8 @@ bool analyze_instruction(const rabbitizer::InstructionCpu& instr, const RecompPo | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool RecompPort::analyze_function(const RecompPort::Context& context, const RecompPort::Function& func, | ||||
|     const std::vector<rabbitizer::InstructionCpu>& instructions, RecompPort::FunctionStats& stats) { | ||||
| bool N64Recomp::analyze_function(const N64Recomp::Context& context, const N64Recomp::Function& func, | ||||
|     const std::vector<rabbitizer::InstructionCpu>& instructions, N64Recomp::FunctionStats& stats) { | ||||
|     // Create a state to track each register (r0 won't be used)
 | ||||
|     RegState reg_states[32] {}; | ||||
|     std::vector<RegState> stack_states{}; | ||||
|  |  | |||
							
								
								
									
										38
									
								
								src/analysis.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/analysis.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| #ifndef __RECOMP_ANALYSIS_H__ | ||||
| #define __RECOMP_ANALYSIS_H__ | ||||
| 
 | ||||
| #include <cstdint> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "n64recomp.h" | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     struct JumpTable { | ||||
|         uint32_t vram; | ||||
|         uint32_t addend_reg; | ||||
|         uint32_t rom; | ||||
|         uint32_t lw_vram; | ||||
|         uint32_t addu_vram; | ||||
|         uint32_t jr_vram; | ||||
|         std::vector<uint32_t> entries; | ||||
| 
 | ||||
|         JumpTable(uint32_t vram, uint32_t addend_reg, uint32_t rom, uint32_t lw_vram, uint32_t addu_vram, uint32_t jr_vram, std::vector<uint32_t>&& entries) | ||||
|                 : vram(vram), addend_reg(addend_reg), rom(rom), lw_vram(lw_vram), addu_vram(addu_vram), jr_vram(jr_vram), entries(std::move(entries)) {} | ||||
|     }; | ||||
| 
 | ||||
|     struct AbsoluteJump { | ||||
|         uint32_t jump_target; | ||||
|         uint32_t instruction_vram; | ||||
| 
 | ||||
|         AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {} | ||||
|     }; | ||||
| 
 | ||||
|     struct FunctionStats { | ||||
|         std::vector<JumpTable> jump_tables; | ||||
|         std::vector<AbsoluteJump> absolute_jumps; | ||||
|     }; | ||||
| 
 | ||||
|     bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										485
									
								
								src/cgenerator.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										485
									
								
								src/cgenerator.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,485 @@ | |||
| #include <cassert> | ||||
| #include <fstream> | ||||
| 
 | ||||
| #include "fmt/format.h" | ||||
| #include "fmt/ostream.h" | ||||
| 
 | ||||
| #include "generator.h" | ||||
| 
 | ||||
| struct BinaryOpFields { std::string func_string; std::string infix_string; }; | ||||
| 
 | ||||
| std::vector<BinaryOpFields> c_op_fields = []() { | ||||
|     std::vector<BinaryOpFields> ret{}; | ||||
|     ret.resize(static_cast<size_t>(N64Recomp::BinaryOpType::COUNT)); | ||||
|     std::vector<char> ops_setup{}; | ||||
|     ops_setup.resize(static_cast<size_t>(N64Recomp::BinaryOpType::COUNT)); | ||||
| 
 | ||||
|     auto setup_op = [&ret, &ops_setup](N64Recomp::BinaryOpType op_type, const std::string& func_string, const std::string& infix_string) { | ||||
|         size_t index = static_cast<size_t>(op_type); | ||||
|         // Prevent setting up an operation twice.
 | ||||
|         assert(ops_setup[index] == false && "Operation already setup!"); | ||||
|         ops_setup[index] = true; | ||||
|         ret[index] = { func_string, infix_string }; | ||||
|     }; | ||||
| 
 | ||||
|     setup_op(N64Recomp::BinaryOpType::Add32,     "ADD32",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::Sub32,     "SUB32",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::Add64,     "",       "+"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Sub64,     "",       "-"); | ||||
|     setup_op(N64Recomp::BinaryOpType::And64,     "",       "&"); | ||||
|     setup_op(N64Recomp::BinaryOpType::AddFloat,  "",       "+"); | ||||
|     setup_op(N64Recomp::BinaryOpType::AddDouble, "",       "+"); | ||||
|     setup_op(N64Recomp::BinaryOpType::SubFloat,  "",       "-"); | ||||
|     setup_op(N64Recomp::BinaryOpType::SubDouble, "",       "-"); | ||||
|     setup_op(N64Recomp::BinaryOpType::MulFloat,  "MUL_S",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::MulDouble, "MUL_D",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::DivFloat,  "DIV_S",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::DivDouble, "DIV_D",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::Or64,      "",       "|"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Nor64,     "~",      "|"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Xor64,     "",       "^"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Sll32,     "S32",    "<<"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Sll64,     "",       "<<"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Srl32,     "S32",    ">>"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Srl64,     "",       ">>"); | ||||
|     setup_op(N64Recomp::BinaryOpType::Sra32,     "S32",    ">>"); // Arithmetic aspect will be taken care of by unary op for first operand.
 | ||||
|     setup_op(N64Recomp::BinaryOpType::Sra64,     "",       ">>"); // Arithmetic aspect will be taken care of by unary op for first operand.
 | ||||
|     setup_op(N64Recomp::BinaryOpType::Equal,     "",       "=="); | ||||
|     setup_op(N64Recomp::BinaryOpType::NotEqual,  "",       "!="); | ||||
|     setup_op(N64Recomp::BinaryOpType::Less,      "",       "<"); | ||||
|     setup_op(N64Recomp::BinaryOpType::LessEq,    "",       "<="); | ||||
|     setup_op(N64Recomp::BinaryOpType::Greater,   "",       ">"); | ||||
|     setup_op(N64Recomp::BinaryOpType::GreaterEq, "",       ">="); | ||||
|     setup_op(N64Recomp::BinaryOpType::LD,        "LD",     ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LW,        "MEM_W",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LWU,       "MEM_WU", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LH,        "MEM_H",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LHU,       "MEM_HU", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LB,        "MEM_B",  ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LBU,       "MEM_BU", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LDL,       "do_ldl", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LDR,       "do_ldr", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LWL,       "do_lwl", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::LWR,       "do_lwr", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::True,      "", ""); | ||||
|     setup_op(N64Recomp::BinaryOpType::False,     "", ""); | ||||
| 
 | ||||
|     // Ensure every operation has been setup.
 | ||||
|     for (char is_set : ops_setup) { | ||||
|         assert(is_set && "Operation has not been setup!"); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| }(); | ||||
| 
 | ||||
| std::string gpr_to_string(int gpr_index) { | ||||
|     if (gpr_index == 0) { | ||||
|         return "0"; | ||||
|     } | ||||
|     return fmt::format("ctx->r{}", gpr_index); | ||||
| } | ||||
| 
 | ||||
| std::string fpr_to_string(int fpr_index) { | ||||
|     return fmt::format("ctx->f{}.fl", fpr_index); | ||||
| } | ||||
| 
 | ||||
| std::string fpr_double_to_string(int fpr_index) { | ||||
|     return fmt::format("ctx->f{}.d", fpr_index); | ||||
| } | ||||
| 
 | ||||
| std::string fpr_u32l_to_string(int fpr_index) { | ||||
|     if (fpr_index & 1) { | ||||
|         return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index); | ||||
|     } | ||||
|     else { | ||||
|         return fmt::format("ctx->f{}.u32l", fpr_index); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string fpr_u64_to_string(int fpr_index) { | ||||
|     return fmt::format("ctx->f{}.u64", fpr_index); | ||||
| } | ||||
| 
 | ||||
| std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { | ||||
|     switch (context.reloc_type) { | ||||
|         case N64Recomp::RelocType::R_MIPS_HI16: | ||||
|             return fmt::format("{}RELOC_HI16({}, {:#X})", | ||||
|                 context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); | ||||
|         case N64Recomp::RelocType::R_MIPS_LO16: | ||||
|             return fmt::format("{}RELOC_LO16({}, {:#X})", | ||||
|                 context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); | ||||
|         default: | ||||
|             throw std::runtime_error(fmt::format("Unexpected reloc type {}\n", static_cast<int>(context.reloc_type))); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string signed_reloc(const N64Recomp::InstructionContext& context) { | ||||
|     return "(int16_t)" + unsigned_reloc(context); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::get_operand_string(Operand operand, UnaryOpType operation, const InstructionContext& context, std::string& operand_string) const { | ||||
|     switch (operand) { | ||||
|         case Operand::Rd: | ||||
|             operand_string = gpr_to_string(context.rd); | ||||
|             break; | ||||
|         case Operand::Rs: | ||||
|             operand_string = gpr_to_string(context.rs); | ||||
|             break; | ||||
|         case Operand::Rt: | ||||
|             operand_string = gpr_to_string(context.rt); | ||||
|             break; | ||||
|         case Operand::Fd: | ||||
|             operand_string = fpr_to_string(context.fd); | ||||
|             break; | ||||
|         case Operand::Fs: | ||||
|             operand_string = fpr_to_string(context.fs); | ||||
|             break; | ||||
|         case Operand::Ft: | ||||
|             operand_string = fpr_to_string(context.ft); | ||||
|             break; | ||||
|         case Operand::FdDouble: | ||||
|             operand_string = fpr_double_to_string(context.fd); | ||||
|             break; | ||||
|         case Operand::FsDouble: | ||||
|             operand_string = fpr_double_to_string(context.fs); | ||||
|             break; | ||||
|         case Operand::FtDouble: | ||||
|             operand_string = fpr_double_to_string(context.ft); | ||||
|             break; | ||||
|         case Operand::FdU32L: | ||||
|             operand_string = fpr_u32l_to_string(context.fd); | ||||
|             break; | ||||
|         case Operand::FsU32L: | ||||
|             operand_string = fpr_u32l_to_string(context.fs); | ||||
|             break; | ||||
|         case Operand::FtU32L: | ||||
|             operand_string = fpr_u32l_to_string(context.ft); | ||||
|             break; | ||||
|         case Operand::FdU32H: | ||||
|             assert(false); | ||||
|             break; | ||||
|         case Operand::FsU32H: | ||||
|             assert(false); | ||||
|             break; | ||||
|         case Operand::FtU32H: | ||||
|             assert(false); | ||||
|             break; | ||||
|         case Operand::FdU64: | ||||
|             operand_string = fpr_u64_to_string(context.fd); | ||||
|             break; | ||||
|         case Operand::FsU64: | ||||
|             operand_string = fpr_u64_to_string(context.fs); | ||||
|             break; | ||||
|         case Operand::FtU64: | ||||
|             operand_string = fpr_u64_to_string(context.ft); | ||||
|             break; | ||||
|         case Operand::ImmU16: | ||||
|             if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) { | ||||
|                 operand_string = unsigned_reloc(context); | ||||
|             } | ||||
|             else { | ||||
|                 operand_string = fmt::format("{:#X}", context.imm16); | ||||
|             } | ||||
|             break; | ||||
|         case Operand::ImmS16: | ||||
|             if (context.reloc_type != N64Recomp::RelocType::R_MIPS_NONE) { | ||||
|                 operand_string = signed_reloc(context); | ||||
|             } | ||||
|             else { | ||||
|                 operand_string = fmt::format("{:#X}", (int16_t)context.imm16); | ||||
|             } | ||||
|             break; | ||||
|         case Operand::Sa: | ||||
|             operand_string = std::to_string(context.sa); | ||||
|             break; | ||||
|         case Operand::Sa32: | ||||
|             operand_string = fmt::format("({} + 32)", context.sa); | ||||
|             break; | ||||
|         case Operand::Cop1cs: | ||||
|             operand_string = fmt::format("c1cs"); | ||||
|             break; | ||||
|         case Operand::Hi: | ||||
|             operand_string = "hi"; | ||||
|             break; | ||||
|         case Operand::Lo: | ||||
|             operand_string = "lo"; | ||||
|             break; | ||||
|         case Operand::Zero: | ||||
|             operand_string = "0"; | ||||
|             break; | ||||
|     } | ||||
|     switch (operation) { | ||||
|         case UnaryOpType::None: | ||||
|             break; | ||||
|         case UnaryOpType::ToS32: | ||||
|             operand_string = "S32(" + operand_string + ")";  | ||||
|             break; | ||||
|         case UnaryOpType::ToU32: | ||||
|             operand_string = "U32(" + operand_string + ")";  | ||||
|             break; | ||||
|         case UnaryOpType::ToS64: | ||||
|             operand_string = "SIGNED(" + operand_string + ")";  | ||||
|             break; | ||||
|         case UnaryOpType::ToU64: | ||||
|             // Nothing to do here, they're already U64
 | ||||
|             break; | ||||
|         case UnaryOpType::NegateS32: | ||||
|             assert(false); | ||||
|             break; | ||||
|         case UnaryOpType::NegateS64: | ||||
|             assert(false); | ||||
|             break; | ||||
|         case UnaryOpType::Lui: | ||||
|             operand_string = "S32(" + operand_string + " << 16)";  | ||||
|             break; | ||||
|         case UnaryOpType::Mask5: | ||||
|             operand_string = "(" + operand_string + " & 31)"; | ||||
|             break; | ||||
|         case UnaryOpType::Mask6: | ||||
|             operand_string = "(" + operand_string + " & 63)"; | ||||
|             break; | ||||
|         case UnaryOpType::ToInt32: | ||||
|             operand_string = "(int32_t)" + operand_string;  | ||||
|             break; | ||||
|         case UnaryOpType::Negate: | ||||
|             operand_string = "-" + operand_string; | ||||
|             break; | ||||
|         case UnaryOpType::AbsFloat: | ||||
|             operand_string = "fabsf(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::AbsDouble: | ||||
|             operand_string = "fabs(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::SqrtFloat: | ||||
|             operand_string = "sqrtf(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::SqrtDouble: | ||||
|             operand_string = "sqrt(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertSFromW: | ||||
|             operand_string = "CVT_S_W(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertWFromS: | ||||
|             operand_string = "CVT_W_S(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertDFromW: | ||||
|             operand_string = "CVT_D_W(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertWFromD: | ||||
|             operand_string = "CVT_W_D(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertDFromS: | ||||
|             operand_string = "CVT_D_S(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertSFromD: | ||||
|             operand_string = "CVT_S_D(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertDFromL: | ||||
|             operand_string = "CVT_D_L(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertLFromD: | ||||
|             operand_string = "CVT_L_D(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertSFromL: | ||||
|             operand_string = "CVT_S_L(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::ConvertLFromS: | ||||
|             operand_string = "CVT_L_S(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::TruncateWFromS: | ||||
|             operand_string = "TRUNC_W_S(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::TruncateWFromD: | ||||
|             operand_string = "TRUNC_W_D(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::RoundWFromS: | ||||
|             operand_string = "lroundf(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::RoundWFromD: | ||||
|             operand_string = "lround(" + operand_string + ")"; | ||||
|             break; | ||||
|         case UnaryOpType::CeilWFromS: | ||||
|             operand_string = "S32(ceilf(" + operand_string + "))"; | ||||
|             break; | ||||
|         case UnaryOpType::CeilWFromD: | ||||
|             operand_string = "S32(ceil(" + operand_string + "))"; | ||||
|             break; | ||||
|         case UnaryOpType::FloorWFromS: | ||||
|             operand_string = "S32(floorf(" + operand_string + "))"; | ||||
|             break; | ||||
|         case UnaryOpType::FloorWFromD: | ||||
|             operand_string = "S32(floor(" + operand_string + "))"; | ||||
|             break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::get_notation(BinaryOpType op_type, std::string& func_string, std::string& infix_string) const { | ||||
|     func_string = c_op_fields[static_cast<size_t>(op_type)].func_string; | ||||
|     infix_string = c_op_fields[static_cast<size_t>(op_type)].infix_string; | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::get_binary_expr_string(BinaryOpType type, const BinaryOperands& operands, const InstructionContext& ctx, const std::string& output, std::string& expr_string) const { | ||||
|     thread_local std::string input_a{}; | ||||
|     thread_local std::string input_b{}; | ||||
|     thread_local std::string func_string{}; | ||||
|     thread_local std::string infix_string{}; | ||||
|     bool is_infix; | ||||
|     get_operand_string(operands.operands[0], operands.operand_operations[0], ctx, input_a); | ||||
|     get_operand_string(operands.operands[1], operands.operand_operations[1], ctx, input_b); | ||||
|     get_notation(type, func_string, infix_string); | ||||
|      | ||||
|     // These cases aren't strictly necessary and are just here for parity with the old recompiler output.
 | ||||
|     if (type == BinaryOpType::Less && !((operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) || (operands.operands[0] == Operand::Fs || operands.operands[0] == Operand::FsDouble))) { | ||||
|         expr_string = fmt::format("{} {} {} ? 1 : 0", input_a, infix_string, input_b); | ||||
|     } | ||||
|     else if (type == BinaryOpType::Equal && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { | ||||
|         expr_string = input_a; | ||||
|     } | ||||
|     else if (type == BinaryOpType::NotEqual && operands.operands[1] == Operand::Zero && operands.operand_operations[1] == UnaryOpType::None) { | ||||
|         expr_string = "!" + input_a; | ||||
|     } | ||||
|     // End unnecessary cases.
 | ||||
| 
 | ||||
|     // TODO encode these ops to avoid needing special handling.
 | ||||
|     else if (type == BinaryOpType::LWL || type == BinaryOpType::LWR || type == BinaryOpType::LDL || type == BinaryOpType::LDR) { | ||||
|         expr_string = fmt::format("{}(rdram, {}, {}, {})", func_string, output, input_a, input_b); | ||||
|     } | ||||
|     else if (!func_string.empty() && !infix_string.empty()) { | ||||
|         expr_string = fmt::format("{}({} {} {})", func_string, input_a, infix_string, input_b); | ||||
|     } | ||||
|     else if (!func_string.empty()) { | ||||
|         expr_string = fmt::format("{}({}, {})", func_string, input_a, input_b); | ||||
|     } | ||||
|     else if (!infix_string.empty()) { | ||||
|         expr_string = fmt::format("{} {} {}", input_a, infix_string, input_b); | ||||
|     } | ||||
|     else { | ||||
|         // Handle special cases
 | ||||
|         if (type == BinaryOpType::True) { | ||||
|             expr_string = "1"; | ||||
|         } | ||||
|         else if (type == BinaryOpType::False) { | ||||
|             expr_string = "0"; | ||||
|         } | ||||
|         assert(false && "Binary operation must have either a function or infix!"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::emit_branch_condition(std::ostream& output_file, const ConditionalBranchOp& op, const InstructionContext& ctx) const { | ||||
|     // Thread local variables to prevent allocations when possible.
 | ||||
|     // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
 | ||||
|     thread_local std::string expr_string{}; | ||||
|     get_binary_expr_string(op.comparison, op.operands, ctx, "", expr_string); | ||||
|     fmt::print(output_file, "if ({}) {{\n", expr_string); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::emit_branch_close(std::ostream& output_file) const { | ||||
|     fmt::print(output_file, "    }}\n"); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::emit_check_fr(std::ostream& output_file, int fpr) const { | ||||
|     fmt::print(output_file, "CHECK_FR(ctx, {});\n    ", fpr); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::emit_check_nan(std::ostream& output_file, int fpr, bool is_double) const { | ||||
|     fmt::print(output_file, "NAN_CHECK(ctx->f{}.{}); ", fpr, is_double ? "d" : "fl"); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::process_binary_op(std::ostream& output_file, const BinaryOp& op, const InstructionContext& ctx) const { | ||||
|     // Thread local variables to prevent allocations when possible.
 | ||||
|     // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
 | ||||
|     thread_local std::string output{}; | ||||
|     thread_local std::string expression{}; | ||||
|     get_operand_string(op.output, UnaryOpType::None, ctx, output); | ||||
|     get_binary_expr_string(op.type, op.operands, ctx, output, expression); | ||||
|     fmt::print(output_file, "{} = {};\n", output, expression); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::process_unary_op(std::ostream& output_file, const UnaryOp& op, const InstructionContext& ctx) const { | ||||
|     // Thread local variables to prevent allocations when possible.
 | ||||
|     // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
 | ||||
|     thread_local std::string output{}; | ||||
|     thread_local std::string input{}; | ||||
|     bool is_infix; | ||||
|     get_operand_string(op.output, UnaryOpType::None, ctx, output); | ||||
|     get_operand_string(op.input, op.operation, ctx, input); | ||||
|     fmt::print(output_file, "{} = {};\n", output, input); | ||||
| } | ||||
| 
 | ||||
| void N64Recomp::CGenerator::process_store_op(std::ostream& output_file, const StoreOp& op, const InstructionContext& ctx) const { | ||||
|     // Thread local variables to prevent allocations when possible.
 | ||||
|     // TODO these thread locals probably don't actually help right now, so figure out a better way to prevent allocations.
 | ||||
|     thread_local std::string base_str{}; | ||||
|     thread_local std::string imm_str{}; | ||||
|     thread_local std::string value_input{}; | ||||
|     bool is_infix; | ||||
|     get_operand_string(Operand::Base, UnaryOpType::None, ctx, base_str); | ||||
|     get_operand_string(Operand::ImmS16, UnaryOpType::None, ctx, imm_str); | ||||
|     get_operand_string(op.value_input, UnaryOpType::None, ctx, value_input); | ||||
| 
 | ||||
|     enum class StoreSyntax { | ||||
|         Func, | ||||
|         FuncWithRdram, | ||||
|         Assignment, | ||||
|     }; | ||||
| 
 | ||||
|     StoreSyntax syntax; | ||||
|     std::string func_text; | ||||
| 
 | ||||
|     switch (op.type) { | ||||
|         case StoreOpType::SD: | ||||
|             func_text = "SD"; | ||||
|             syntax = StoreSyntax::Func; | ||||
|             break; | ||||
|         case StoreOpType::SDL: | ||||
|             func_text = "do_sdl"; | ||||
|             syntax = StoreSyntax::FuncWithRdram; | ||||
|             break; | ||||
|         case StoreOpType::SDR: | ||||
|             func_text = "do_sdr"; | ||||
|             syntax = StoreSyntax::FuncWithRdram; | ||||
|             break; | ||||
|         case StoreOpType::SW: | ||||
|             func_text = "MEM_W"; | ||||
|             syntax = StoreSyntax::Assignment; | ||||
|             break; | ||||
|         case StoreOpType::SWL: | ||||
|             func_text = "do_swl"; | ||||
|             syntax = StoreSyntax::FuncWithRdram; | ||||
|             break; | ||||
|         case StoreOpType::SWR: | ||||
|             func_text = "do_swr"; | ||||
|             syntax = StoreSyntax::FuncWithRdram; | ||||
|             break; | ||||
|         case StoreOpType::SH: | ||||
|             func_text = "MEM_H"; | ||||
|             syntax = StoreSyntax::Assignment; | ||||
|             break; | ||||
|         case StoreOpType::SB: | ||||
|             func_text = "MEM_B"; | ||||
|             syntax = StoreSyntax::Assignment; | ||||
|             break; | ||||
|         case StoreOpType::SDC1: | ||||
|             func_text = "SD"; | ||||
|             syntax = StoreSyntax::Func; | ||||
|             break; | ||||
|         case StoreOpType::SWC1: | ||||
|             func_text = "MEM_W"; | ||||
|             syntax = StoreSyntax::Assignment; | ||||
|             break; | ||||
|         default: | ||||
|             throw std::runtime_error("Unhandled store op"); | ||||
|     } | ||||
| 
 | ||||
|     switch (syntax) { | ||||
|         case StoreSyntax::Func: | ||||
|             fmt::print(output_file, "{}({}, {}, {});\n", func_text, value_input, imm_str, base_str); | ||||
|             break; | ||||
|         case StoreSyntax::FuncWithRdram: | ||||
|             fmt::print(output_file, "{}(rdram, {}, {}, {});\n", func_text, imm_str, base_str, value_input); | ||||
|             break; | ||||
|         case StoreSyntax::Assignment: | ||||
|             fmt::print(output_file, "{}({}, {}) = {};\n", func_text, imm_str, base_str, value_input); | ||||
|             break; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										190
									
								
								src/config.cpp
									
										
									
									
									
								
							
							
						
						
									
										190
									
								
								src/config.cpp
									
										
									
									
									
								
							|  | @ -1,8 +1,9 @@ | |||
| #include <source_location> | ||||
| #include <iostream> | ||||
| 
 | ||||
| #include <toml++/toml.hpp> | ||||
| #include "fmt/format.h" | ||||
| #include "recomp_port.h" | ||||
| #include "config.h" | ||||
| #include "n64recomp.h" | ||||
| 
 | ||||
| std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) { | ||||
|     if (!child.empty()) { | ||||
|  | @ -11,8 +12,8 @@ std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, c | |||
|     return child; | ||||
| } | ||||
| 
 | ||||
| std::vector<RecompPort::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) { | ||||
|     std::vector<RecompPort::ManualFunction> ret; | ||||
| std::vector<N64Recomp::ManualFunction> get_manual_funcs(const toml::array* manual_funcs_array) { | ||||
|     std::vector<N64Recomp::ManualFunction> ret; | ||||
| 
 | ||||
|     // Reserve room for all the funcs in the map.
 | ||||
|     ret.reserve(manual_funcs_array->size()); | ||||
|  | @ -103,70 +104,8 @@ std::vector<std::string> get_ignored_funcs(const toml::table* patches_data) { | |||
|     return ignored_funcs; | ||||
| } | ||||
| 
 | ||||
| std::unordered_map<std::string, RecompPort::FunctionArgType> arg_type_map{ | ||||
|     {"u32", RecompPort::FunctionArgType::u32}, | ||||
|     {"s32", RecompPort::FunctionArgType::s32}, | ||||
| }; | ||||
| 
 | ||||
| std::vector<RecompPort::FunctionArgType> parse_args(const toml::array* args_in) { | ||||
|     std::vector<RecompPort::FunctionArgType> ret(args_in->size()); | ||||
| 
 | ||||
|     args_in->for_each([&ret](auto&& el) { | ||||
|         if constexpr (toml::is_string<decltype(el)>) { | ||||
|             const std::string& arg_str = *el; | ||||
| 
 | ||||
|             // Check if the argument type string is valid.
 | ||||
|             auto type_find = arg_type_map.find(arg_str); | ||||
|             if (type_find == arg_type_map.end()) { | ||||
|                 // It's not, so throw an error (and make it look like a normal toml one).
 | ||||
|                 throw toml::parse_error(("Invalid argument type: " + arg_str).c_str(), el.source()); | ||||
|             } | ||||
|             ret.push_back(type_find->second); | ||||
|         } | ||||
|         else { | ||||
|             throw toml::parse_error("Invalid function argument entry", el.source()); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| RecompPort::DeclaredFunctionMap get_declared_funcs(const toml::table* patches_data) { | ||||
|     RecompPort::DeclaredFunctionMap declared_funcs{}; | ||||
| 
 | ||||
|     // Check if the func array exists.
 | ||||
|     const toml::node_view funcs_data = (*patches_data)["func"]; | ||||
| 
 | ||||
|     if (funcs_data.is_array()) { | ||||
|         const toml::array* funcs_array = funcs_data.as_array(); | ||||
| 
 | ||||
|         // Reserve room for all the funcs in the map.
 | ||||
|         declared_funcs.reserve(funcs_array->size()); | ||||
| 
 | ||||
|         // Gather the funcs and place them into the map.
 | ||||
|         funcs_array->for_each([&declared_funcs](auto&& el) { | ||||
|             if constexpr (toml::is_table<decltype(el)>) { | ||||
|                 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; | ||||
| } | ||||
| 
 | ||||
| std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_data) { | ||||
|     std::vector<RecompPort::FunctionSize> func_sizes{}; | ||||
| std::vector<N64Recomp::FunctionSize> get_func_sizes(const toml::table* patches_data) { | ||||
|     std::vector<N64Recomp::FunctionSize> func_sizes{}; | ||||
| 
 | ||||
|     // Check if the func size array exists.
 | ||||
|     const toml::node_view funcs_data = (*patches_data)["function_sizes"]; | ||||
|  | @ -204,8 +143,8 @@ std::vector<RecompPort::FunctionSize> get_func_sizes(const toml::table* patches_ | |||
|     return func_sizes; | ||||
| } | ||||
| 
 | ||||
| std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::table* patches_data) { | ||||
|     std::vector<RecompPort::InstructionPatch> ret; | ||||
| std::vector<N64Recomp::InstructionPatch> get_instruction_patches(const toml::table* patches_data) { | ||||
|     std::vector<N64Recomp::InstructionPatch> ret; | ||||
| 
 | ||||
|     // Check if the instruction patch array exists.
 | ||||
|     const toml::node_view insn_patch_data = (*patches_data)["instruction"]; | ||||
|  | @ -233,7 +172,7 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta | |||
|                     throw toml::parse_error("Instruction patch is not word-aligned", el.source()); | ||||
|                 } | ||||
| 
 | ||||
|                 ret.push_back(RecompPort::InstructionPatch{ | ||||
|                 ret.push_back(N64Recomp::InstructionPatch{ | ||||
|                     .func_name = func_name.value(), | ||||
|                     .vram = (int32_t)vram.value(), | ||||
|                     .value = value.value(), | ||||
|  | @ -248,8 +187,8 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) { | ||||
|     std::vector<RecompPort::FunctionHook> ret; | ||||
| std::vector<N64Recomp::FunctionHook> get_function_hooks(const toml::table* patches_data) { | ||||
|     std::vector<N64Recomp::FunctionHook> ret; | ||||
| 
 | ||||
|     // Check if the function hook array exists.
 | ||||
|     const toml::node_view func_hook_data = (*patches_data)["hook"]; | ||||
|  | @ -277,7 +216,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc | |||
|                     throw toml::parse_error("before_vram is not word-aligned", el.source()); | ||||
|                 } | ||||
| 
 | ||||
|                 ret.push_back(RecompPort::FunctionHook{ | ||||
|                 ret.push_back(N64Recomp::FunctionHook{ | ||||
|                     .func_name = func_name.value(), | ||||
|                     .before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0, | ||||
|                     .text = text.value(), | ||||
|  | @ -292,7 +231,7 @@ std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patc | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| RecompPort::Config::Config(const char* path) { | ||||
| N64Recomp::Config::Config(const char* path) { | ||||
|     // Start this config out as bad so that it has to finish parsing without errors to be good.
 | ||||
|     entrypoint = 0; | ||||
|     bad = true; | ||||
|  | @ -438,16 +377,13 @@ RecompPort::Config::Config(const char* path) { | |||
|             // 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); | ||||
| 
 | ||||
|             // Fonction hooks (optional)
 | ||||
|             // Function hooks (optional)
 | ||||
|             function_hooks = get_function_hooks(table); | ||||
|         } | ||||
| 
 | ||||
|  | @ -472,6 +408,25 @@ RecompPort::Config::Config(const char* path) { | |||
|             const toml::array* array = data_reference_syms_file_data.as_array(); | ||||
|             data_reference_syms_file_paths = get_data_syms_paths(array, basedir); | ||||
|         } | ||||
| 
 | ||||
|         // Control whether the recompiler emits exported symbol data.
 | ||||
|         std::optional<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) { | ||||
|         std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin <<  "):\n" << err.description() << std::endl; | ||||
|  | @ -482,27 +437,27 @@ RecompPort::Config::Config(const char* path) { | |||
|     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 }, | ||||
| const std::unordered_map<std::string, N64Recomp::RelocType> reloc_type_name_map { | ||||
|     { "R_MIPS_NONE", N64Recomp::RelocType::R_MIPS_NONE }, | ||||
|     { "R_MIPS_16", N64Recomp::RelocType::R_MIPS_16 }, | ||||
|     { "R_MIPS_32", N64Recomp::RelocType::R_MIPS_32 }, | ||||
|     { "R_MIPS_REL32", N64Recomp::RelocType::R_MIPS_REL32 }, | ||||
|     { "R_MIPS_26", N64Recomp::RelocType::R_MIPS_26 }, | ||||
|     { "R_MIPS_HI16", N64Recomp::RelocType::R_MIPS_HI16 }, | ||||
|     { "R_MIPS_LO16", N64Recomp::RelocType::R_MIPS_LO16 }, | ||||
|     { "R_MIPS_GPREL16", N64Recomp::RelocType::R_MIPS_GPREL16 }, | ||||
| }; | ||||
| 
 | ||||
| RecompPort::RelocType reloc_type_from_name(const std::string& reloc_type_name) { | ||||
| N64Recomp::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; | ||||
|     return N64Recomp::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, bool with_relocs) { | ||||
|     RecompPort::Context ret{}; | ||||
| bool N64Recomp::Context::from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector<uint8_t>&& rom, N64Recomp::Context& out, bool with_relocs) { | ||||
|     N64Recomp::Context ret{}; | ||||
| 
 | ||||
|     try { | ||||
|         const toml::table config_data = toml::parse_file(symbol_file_path.u8string()); | ||||
|  | @ -526,7 +481,7 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f | |||
|                     throw toml::parse_error("Section entry missing required field(s)", el.source()); | ||||
|                 } | ||||
| 
 | ||||
|                 size_t section_index = ret.sections.size(); | ||||
|                 uint16_t section_index = (uint16_t)ret.sections.size(); | ||||
| 
 | ||||
|                 Section& section = ret.sections.emplace_back(Section{}); | ||||
|                 section.rom_addr = rom_addr.value(); | ||||
|  | @ -625,7 +580,7 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f | |||
| 
 | ||||
|                                 Reloc cur_reloc{}; | ||||
|                                 cur_reloc.address = vram.value(); | ||||
|                                 cur_reloc.section_offset = target_vram.value() - section.ram_addr; | ||||
|                                 cur_reloc.target_section_offset = target_vram.value() - section.ram_addr; | ||||
|                                 cur_reloc.symbol_index = (uint32_t)-1; | ||||
|                                 cur_reloc.target_section = section_index; | ||||
|                                 cur_reloc.type = reloc_type; | ||||
|  | @ -656,15 +611,14 @@ bool RecompPort::Context::from_symbol_file(const std::filesystem::path& symbol_f | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void RecompPort::Context::import_reference_context(const RecompPort::Context& reference_context) { | ||||
| bool N64Recomp::Context::import_reference_context(const N64Recomp::Context& reference_context) { | ||||
|     reference_sections.resize(reference_context.sections.size()); | ||||
|     reference_symbols.reserve(reference_context.functions.size()); | ||||
|     reference_symbol_names.reserve(reference_context.functions.size()); | ||||
| 
 | ||||
|     // Copy the reference context's sections into the real context's reference sections. 
 | ||||
|     for (size_t section_index = 0; section_index < reference_context.sections.size(); section_index++) { | ||||
|         const RecompPort::Section& section_in = reference_context.sections[section_index]; | ||||
|         RecompPort::ReferenceSection& section_out = reference_sections[section_index]; | ||||
|         const N64Recomp::Section& section_in = reference_context.sections[section_index]; | ||||
|         N64Recomp::ReferenceSection& section_out = reference_sections[section_index]; | ||||
| 
 | ||||
|         section_out.rom_addr = section_in.rom_addr; | ||||
|         section_out.ram_addr = section_in.ram_addr; | ||||
|  | @ -673,22 +627,17 @@ void RecompPort::Context::import_reference_context(const RecompPort::Context& re | |||
|     } | ||||
| 
 | ||||
|     // Copy the functions from the reference context into the reference context's function map.
 | ||||
|     for (const RecompPort::Function& func_in: reference_context.functions) { | ||||
|         const RecompPort::Section& func_section = reference_context.sections[func_in.section_index]; | ||||
| 
 | ||||
|         reference_symbols_by_name.emplace(func_in.name, reference_symbols.size()); | ||||
| 
 | ||||
|         reference_symbols.emplace_back(RecompPort::ReferenceSymbol{ | ||||
|             .section_index = func_in.section_index, | ||||
|             .section_offset = func_in.vram - static_cast<uint32_t>(func_section.ram_addr), | ||||
|             .is_function = true | ||||
|         }); | ||||
|         reference_symbol_names.emplace_back(func_in.name); | ||||
|     for (const N64Recomp::Function& func_in: reference_context.functions) { | ||||
|         if (!add_reference_symbol(func_in.name, func_in.section_index, func_in.vram, true)) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| // Reads a data symbol file and adds its contents into this context's reference data symbols.
 | ||||
| bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) { | ||||
| bool N64Recomp::Context::read_data_reference_syms(const std::filesystem::path& data_syms_file_path) { | ||||
|     try { | ||||
|         const toml::table data_syms_file_data = toml::parse_file(data_syms_file_path.u8string()); | ||||
|         const toml::node_view data_sections_value = data_syms_file_data["section"]; | ||||
|  | @ -719,7 +668,7 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& | |||
| 
 | ||||
|                 uint16_t ref_section_index; | ||||
|                 if (!rom_addr.has_value()) { | ||||
|                     ref_section_index = RecompPort::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol
 | ||||
|                     ref_section_index = N64Recomp::SectionAbsolute; // Non-relocatable bss section or absolute symbols, mark this as an absolute symbol
 | ||||
|                 } | ||||
|                 else if (rom_addr.value() > 0xFFFFFFFF) { | ||||
|                     throw toml::parse_error("Section has invalid ROM address", el.source()); | ||||
|  | @ -731,7 +680,7 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& | |||
|                         ref_section_index = find_section_it->second; | ||||
|                     } | ||||
|                     else { | ||||
|                         ref_section_index = RecompPort::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable.
 | ||||
|                         ref_section_index = N64Recomp::SectionAbsolute; // Not in the function symbol reference file, so this section can be treated as non-relocatable.
 | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  | @ -741,10 +690,10 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& | |||
|                     .size = 0, | ||||
|                     .relocatable = 0 | ||||
|                 }; | ||||
|                 const ReferenceSection& ref_section = ref_section_index == RecompPort::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index]; | ||||
|                 const ReferenceSection& ref_section = ref_section_index == N64Recomp::SectionAbsolute ? dummy_absolute_section : this->reference_sections[ref_section_index]; | ||||
| 
 | ||||
|                 // Sanity check this section against the matching one in the function reference symbol file if one exists.
 | ||||
|                 if (ref_section_index != RecompPort::SectionAbsolute) { | ||||
|                 if (ref_section_index != N64Recomp::SectionAbsolute) { | ||||
|                     if (ref_section.ram_addr != vram_addr.value()) { | ||||
|                         throw toml::parse_error("Section vram address differs from matching ROM address section in the function symbol reference file", el.source()); | ||||
|                     } | ||||
|  | @ -772,16 +721,9 @@ bool RecompPort::Context::read_data_reference_syms(const std::filesystem::path& | |||
|                             throw toml::parse_error("Reference data symbol entry is missing required field(s)", data_sym_el.source()); | ||||
|                         } | ||||
| 
 | ||||
|                         this->reference_symbols_by_name.emplace(name.value(), reference_symbols.size()); | ||||
| 
 | ||||
|                         this->reference_symbols.emplace_back( | ||||
|                             ReferenceSymbol { | ||||
|                                 .section_index = ref_section_index, | ||||
|                                 .section_offset = vram_addr.value() - ref_section_vram, | ||||
|                                 .is_function = false | ||||
|                             } | ||||
|                         ); | ||||
|                         this->reference_symbol_names.emplace_back(name.value()); | ||||
|                         if (!this->add_reference_symbol(name.value(), ref_section_index, vram_addr.value(), false)) { | ||||
|                             throw toml::parse_error("Internal error: Failed to add reference symbol to context. Please report this issue.", data_sym_el.source()); | ||||
|                         } | ||||
|                     } | ||||
|                     else { | ||||
|                         throw toml::parse_error("Invalid data symbol entry", data_sym_el.source()); | ||||
|  |  | |||
							
								
								
									
										71
									
								
								src/config.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/config.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| #ifndef __RECOMP_CONFIG_H__ | ||||
| #define __RECOMP_CONFIG_H__ | ||||
| 
 | ||||
| #include <cstdint> | ||||
| #include <filesystem> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     struct InstructionPatch { | ||||
|         std::string func_name; | ||||
|         int32_t vram; | ||||
|         uint32_t value; | ||||
|     }; | ||||
| 
 | ||||
|     struct FunctionHook { | ||||
|         std::string func_name; | ||||
|         int32_t before_vram; | ||||
|         std::string text; | ||||
|     }; | ||||
| 
 | ||||
|     struct FunctionSize { | ||||
|         std::string func_name; | ||||
|         uint32_t size_bytes; | ||||
| 
 | ||||
|         FunctionSize(const std::string& func_name, uint32_t size_bytes) : func_name(std::move(func_name)), size_bytes(size_bytes) {} | ||||
|     }; | ||||
| 
 | ||||
|     struct ManualFunction { | ||||
|         std::string func_name; | ||||
|         std::string section_name; | ||||
|         uint32_t vram; | ||||
|         uint32_t 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 { | ||||
|         int32_t entrypoint; | ||||
|         int32_t functions_per_output_file; | ||||
|         bool has_entrypoint; | ||||
|         bool uses_mips3_float_mode; | ||||
|         bool single_file_output; | ||||
|         bool use_absolute_symbols; | ||||
|         bool unpaired_lo16_warnings; | ||||
|         bool allow_exports; | ||||
|         bool strict_patch_mode; | ||||
|         std::filesystem::path elf_path; | ||||
|         std::filesystem::path symbols_file_path; | ||||
|         std::filesystem::path func_reference_syms_file_path; | ||||
|         std::vector<std::filesystem::path> data_reference_syms_file_paths; | ||||
|         std::filesystem::path rom_file_path; | ||||
|         std::filesystem::path output_func_path; | ||||
|         std::filesystem::path relocatable_sections_path; | ||||
|         std::filesystem::path output_binary_path; | ||||
|         std::vector<std::string> stubbed_funcs; | ||||
|         std::vector<std::string> ignored_funcs; | ||||
|         std::vector<InstructionPatch> instruction_patches; | ||||
|         std::vector<FunctionHook> function_hooks; | ||||
|         std::vector<FunctionSize> manual_func_sizes; | ||||
|         std::vector<ManualFunction> manual_functions; | ||||
|         std::string bss_section_suffix; | ||||
|         std::string recomp_include; | ||||
| 
 | ||||
|         Config(const char* path); | ||||
|         bool good() { return !bad; } | ||||
|     private: | ||||
|         bool bad; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										597
									
								
								src/elf.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										597
									
								
								src/elf.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,597 @@ | |||
| #include <optional> | ||||
| 
 | ||||
| #include "fmt/format.h" | ||||
| // #include "fmt/ostream.h"
 | ||||
| 
 | ||||
| #include "n64recomp.h" | ||||
| #include "elfio/elfio.hpp" | ||||
| 
 | ||||
| bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, const N64Recomp::ElfParsingConfig& elf_config, bool dumping_context, std::unordered_map<uint16_t, std::vector<N64Recomp::DataSymbol>>& data_syms) { | ||||
|     bool found_entrypoint_func = false; | ||||
|     ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section }; | ||||
| 
 | ||||
|     std::unordered_map<uint16_t, uint16_t> bss_section_to_target_section{}; | ||||
| 
 | ||||
|     // Create a mapping of bss section to the corresponding non-bss section. This is only used when dumping context in order
 | ||||
|     // for patches and mods to correctly relocate symbols in bss. This mapping only matters for relocatable sections.
 | ||||
|     if (dumping_context) { | ||||
|         // Process bss and reloc sections
 | ||||
|         for (size_t cur_section_index = 0; cur_section_index < context.sections.size(); cur_section_index++) { | ||||
|             const N64Recomp::Section& cur_section = context.sections[cur_section_index]; | ||||
|             // Check if a bss section was found that corresponds with this section.
 | ||||
|             if (cur_section.bss_section_index != (uint16_t)-1) { | ||||
|                 bss_section_to_target_section[cur_section.bss_section_index] = cur_section_index; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (int sym_index = 0; sym_index < symbols.get_symbols_num(); sym_index++) { | ||||
|         std::string   name; | ||||
|         ELFIO::Elf64_Addr    value; | ||||
|         ELFIO::Elf_Xword     size; | ||||
|         unsigned char bind; | ||||
|         unsigned char type; | ||||
|         ELFIO::Elf_Half      section_index; | ||||
|         unsigned char other; | ||||
|         bool ignored = false; | ||||
|         bool reimplemented = false; | ||||
|         bool recorded_symbol = false; | ||||
| 
 | ||||
|         // Read symbol properties
 | ||||
|         symbols.get_symbol(sym_index, name, value, size, bind, type, | ||||
|             section_index, other); | ||||
| 
 | ||||
|         if (section_index == ELFIO::SHN_ABS && elf_config.use_absolute_symbols) { | ||||
|             uint32_t vram = static_cast<uint32_t>(value); | ||||
|             context.functions_by_vram[vram].push_back(context.functions.size()); | ||||
| 
 | ||||
|             context.functions.emplace_back( | ||||
|                 vram, | ||||
|                 0, | ||||
|                 std::vector<uint32_t>{}, | ||||
|                 std::move(name), | ||||
|                 0, | ||||
|                 true, | ||||
|                 reimplemented, | ||||
|                 false | ||||
|             ); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         if (section_index < context.sections.size()) {         | ||||
|             // Check if this symbol is the entrypoint
 | ||||
|             if (elf_config.has_entrypoint && value == elf_config.entrypoint_address && type == ELFIO::STT_FUNC) { | ||||
|                 if (found_entrypoint_func) { | ||||
|                     fmt::print(stderr, "Ambiguous entrypoint: {}\n", name); | ||||
|                     return false; | ||||
|                 } | ||||
|                 found_entrypoint_func = true; | ||||
|                 fmt::print("Found entrypoint, original name: {}\n", name); | ||||
|                 size = 0x50; // dummy size for entrypoints, should cover them all
 | ||||
|                 name = "recomp_entrypoint"; | ||||
|             } | ||||
| 
 | ||||
|             // Check if this symbol has a size override
 | ||||
|             auto size_find = elf_config.manually_sized_funcs.find(name); | ||||
|             if (size_find != elf_config.manually_sized_funcs.end()) { | ||||
|                 size = size_find->second; | ||||
|                 type = ELFIO::STT_FUNC; | ||||
|             } | ||||
| 
 | ||||
|             if (!dumping_context) { | ||||
|                 if (N64Recomp::reimplemented_funcs.contains(name)) { | ||||
|                     reimplemented = true; | ||||
|                     name = name + "_recomp"; | ||||
|                     ignored = true; | ||||
|                 } else if (N64Recomp::ignored_funcs.contains(name)) { | ||||
|                     name = name + "_recomp"; | ||||
|                     ignored = true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             auto& section = context.sections[section_index]; | ||||
| 
 | ||||
|             // Check if this symbol is a function or has no type (like a regular glabel would)
 | ||||
|             // Symbols with no type have a dummy entry created so that their symbol can be looked up for function calls
 | ||||
|             if (ignored || type == ELFIO::STT_FUNC || type == ELFIO::STT_NOTYPE || type == ELFIO::STT_OBJECT) { | ||||
|                 if (!dumping_context) { | ||||
|                     if (N64Recomp::renamed_funcs.contains(name)) { | ||||
|                         name = name + "_recomp"; | ||||
|                         ignored = false; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (section_index < context.sections.size()) { | ||||
|                     auto section_offset = value - elf_file.sections[section_index]->get_address(); | ||||
|                     const uint32_t* words = reinterpret_cast<const uint32_t*>(elf_file.sections[section_index]->get_data() + section_offset); | ||||
|                     uint32_t vram = static_cast<uint32_t>(value); | ||||
|                     uint32_t num_instructions = type == ELFIO::STT_FUNC ? size / 4 : 0; | ||||
|                     uint32_t rom_address = static_cast<uint32_t>(section_offset + section.rom_addr); | ||||
| 
 | ||||
|                     section.function_addrs.push_back(vram); | ||||
|                     context.functions_by_vram[vram].push_back(context.functions.size()); | ||||
| 
 | ||||
|                     // Find the entrypoint by rom address in case it doesn't have vram as its value
 | ||||
|                     if (elf_config.has_entrypoint && rom_address == 0x1000 && type == ELFIO::STT_FUNC) { | ||||
|                         vram = elf_config.entrypoint_address; | ||||
|                         found_entrypoint_func = true; | ||||
|                         name = "recomp_entrypoint"; | ||||
|                         if (size == 0) { | ||||
|                             num_instructions = 0x50 / 4; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     // Suffix local symbols to prevent name conflicts.
 | ||||
|                     if (bind == ELFIO::STB_LOCAL) { | ||||
|                         name = fmt::format("{}_{:08X}", name, rom_address); | ||||
|                     } | ||||
|                      | ||||
|                     if (num_instructions > 0) { | ||||
|                         context.section_functions[section_index].push_back(context.functions.size());             | ||||
|                         recorded_symbol = true; | ||||
|                     } | ||||
|                     context.functions_by_name[name] = context.functions.size(); | ||||
| 
 | ||||
|                     std::vector<uint32_t> insn_words(num_instructions); | ||||
|                     insn_words.assign(words, words + num_instructions); | ||||
| 
 | ||||
|                     context.functions.emplace_back( | ||||
|                         vram, | ||||
|                         rom_address, | ||||
|                         std::move(insn_words), | ||||
|                         name, | ||||
|                         section_index, | ||||
|                         ignored, | ||||
|                         reimplemented | ||||
|                     ); | ||||
|                 } else { | ||||
|                     // TODO is this case needed anymore?
 | ||||
|                     uint32_t vram = static_cast<uint32_t>(value); | ||||
|                     section.function_addrs.push_back(vram); | ||||
|                     context.functions_by_vram[vram].push_back(context.functions.size()); | ||||
|                     context.functions.emplace_back( | ||||
|                         vram, | ||||
|                         0, | ||||
|                         std::vector<uint32_t>{}, | ||||
|                         name, | ||||
|                         section_index, | ||||
|                         ignored, | ||||
|                         reimplemented | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // The symbol wasn't detected as a function, so add it to the data symbols if the context is being dumped.
 | ||||
|         if (!recorded_symbol && dumping_context && !name.empty()) { | ||||
|             uint32_t vram = static_cast<uint32_t>(value); | ||||
| 
 | ||||
|             // Place this symbol in the absolute symbol list if it's in the absolute section.
 | ||||
|             uint16_t target_section_index = section_index; | ||||
|             if (section_index == ELFIO::SHN_ABS) { | ||||
|                 target_section_index = N64Recomp::SectionAbsolute; | ||||
|             } | ||||
|             else if (section_index >= context.sections.size()) { | ||||
|                 fmt::print("Symbol \"{}\" not in a valid section ({})\n", name, section_index); | ||||
|             } | ||||
| 
 | ||||
|             // Move this symbol into the corresponding non-bss section if it's in a bss section.
 | ||||
|             auto find_bss_it = bss_section_to_target_section.find(target_section_index); | ||||
|             if (find_bss_it != bss_section_to_target_section.end()) { | ||||
|                 target_section_index = find_bss_it->second; | ||||
|             } | ||||
| 
 | ||||
|             data_syms[target_section_index].emplace_back( | ||||
|                 vram, | ||||
|                 std::move(name) | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return found_entrypoint_func; | ||||
| } | ||||
| 
 | ||||
| struct SegmentEntry { | ||||
|     ELFIO::Elf64_Off data_offset; | ||||
|     ELFIO::Elf64_Addr physical_address; | ||||
|     ELFIO::Elf_Xword memory_size; | ||||
| }; | ||||
| 
 | ||||
| std::optional<size_t> get_segment(const std::vector<SegmentEntry>& segments, ELFIO::Elf_Xword section_size, ELFIO::Elf64_Off section_offset) { | ||||
|     // A linear search is safest even if the segment list is sorted, as there may be overlapping segments
 | ||||
|     for (size_t i = 0; i < segments.size(); i++) { | ||||
|         const auto& segment = segments[i]; | ||||
| 
 | ||||
|         // Check that the section's data in the elf file is within bounds of the segment's data
 | ||||
|         if (section_offset >= segment.data_offset && section_offset + section_size <= segment.data_offset + segment.memory_size) { | ||||
|             return i; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return std::nullopt; | ||||
| } | ||||
| 
 | ||||
| ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) { | ||||
|     ELFIO::section* symtab_section = nullptr; | ||||
|     std::vector<SegmentEntry> segments{}; | ||||
|     segments.resize(elf_file.segments.size()); | ||||
|     bool has_reference_symbols = context.has_reference_symbols(); | ||||
| 
 | ||||
|     // Copy the data for each segment into the segment entry list
 | ||||
|     for (size_t segment_index = 0; segment_index < elf_file.segments.size(); segment_index++) { | ||||
|         const auto& segment = *elf_file.segments[segment_index]; | ||||
|         segments[segment_index].data_offset = segment.get_offset(); | ||||
|         segments[segment_index].physical_address = segment.get_physical_address(); | ||||
|         segments[segment_index].memory_size = segment.get_file_size(); | ||||
|     } | ||||
| 
 | ||||
|     //// Sort the segments by physical address
 | ||||
|     //std::sort(segments.begin(), segments.end(),
 | ||||
|     //    [](const SegmentEntry& lhs, const SegmentEntry& rhs) {
 | ||||
|     //        return lhs.data_offset < rhs.data_offset;
 | ||||
|     //    }
 | ||||
|     //);
 | ||||
| 
 | ||||
|     std::unordered_map<std::string, ELFIO::section*> reloc_sections_by_name; | ||||
|     std::unordered_map<std::string, ELFIO::section*> bss_sections_by_name; | ||||
| 
 | ||||
|     // First pass over the sections to find the load addresses and track the minimum load address value. This mimics the objcopy raw binary output behavior.
 | ||||
|     uint32_t min_load_address = (uint32_t)-1; | ||||
|     for (const auto& section : elf_file.sections) { | ||||
|         auto& section_out = context.sections[section->get_index()]; | ||||
|         ELFIO::Elf_Word type = section->get_type(); | ||||
|         ELFIO::Elf_Xword flags = section->get_flags(); | ||||
|         ELFIO::Elf_Xword section_size = section->get_size(); | ||||
| 
 | ||||
|         // Check if this section will end up in the ROM. It must not be a nobits (NOLOAD) type, must have the alloc flag set and must have a nonzero size. 
 | ||||
|         if (type != ELFIO::SHT_NOBITS && (flags & ELFIO::SHF_ALLOC) && section_size != 0) { | ||||
|             std::optional<size_t> segment_index = get_segment(segments, section_size, section->get_offset()); | ||||
|             if (!segment_index.has_value()) { | ||||
|                 fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section->get_name()); | ||||
|                 return nullptr; | ||||
|             } | ||||
| 
 | ||||
|             const SegmentEntry& segment = segments[segment_index.value()]; | ||||
|             // Calculate the load address of the section based on that of the segment.
 | ||||
|             // This will get modified afterwards in the next pass to offset by the minimum load address.
 | ||||
|             section_out.rom_addr = segment.physical_address + (section->get_offset() - segment.data_offset); | ||||
|             // Track the minimum load address.
 | ||||
|             min_load_address = std::min(min_load_address, section_out.rom_addr); | ||||
|         } | ||||
|         else { | ||||
|             // Otherwise mark this section as having an invalid rom address
 | ||||
|             section_out.rom_addr = (uint32_t)-1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Iterate over every section to record rom addresses and find the symbol table
 | ||||
|     for (const auto& section : elf_file.sections) { | ||||
|         auto& section_out = context.sections[section->get_index()]; | ||||
|         //fmt::print("  {}: {} @ 0x{:08X}, 0x{:08X}\n", section->get_index(), section->get_name(), section->get_address(), context.rom.size());
 | ||||
|         // Set the rom address of this section to the current accumulated ROM size
 | ||||
|         section_out.ram_addr = section->get_address(); | ||||
|         section_out.size = section->get_size(); | ||||
|         ELFIO::Elf_Word type = section->get_type(); | ||||
|         std::string section_name = section->get_name(); | ||||
| 
 | ||||
|         // Check if this section is the symbol table and record it if so
 | ||||
|         if (type == ELFIO::SHT_SYMTAB) { | ||||
|             symtab_section = section.get(); | ||||
|         } | ||||
| 
 | ||||
|         if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(section_name)) { | ||||
|             section_out.relocatable = true; | ||||
|         } | ||||
|          | ||||
|         // Check if this section is a reloc section
 | ||||
|         if (type == ELFIO::SHT_REL) { | ||||
|             // If it is, determine the name of the section it relocates
 | ||||
|             if (!section_name.starts_with(".rel")) { | ||||
|                 fmt::print(stderr, "Could not determine corresponding section for reloc section {}\n", section_name.c_str()); | ||||
|                 return nullptr; | ||||
|             } | ||||
|              | ||||
|             // FIXME This should be using SH_INFO to create a reloc section to target section mapping instead of using the name.
 | ||||
|             std::string reloc_target_section = section_name.substr(strlen(".rel")); | ||||
| 
 | ||||
|             // If this reloc section is for a section that has been marked as relocatable, record it in the reloc section lookup.
 | ||||
|             // Alternatively, if this recompilation uses reference symbols then record all reloc sections.
 | ||||
|             bool section_is_relocatable = elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(reloc_target_section); | ||||
|             if (has_reference_symbols || section_is_relocatable) { | ||||
|                 reloc_sections_by_name[reloc_target_section] = section.get(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // If the section is bss (SHT_NOBITS) and ends with the bss suffix, add it to the bss section map
 | ||||
|         if (type == ELFIO::SHT_NOBITS && section_name.ends_with(elf_config.bss_section_suffix)) { | ||||
|             std::string bss_target_section = section_name.substr(0, section_name.size() - elf_config.bss_section_suffix.size()); | ||||
| 
 | ||||
|             // If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup
 | ||||
|             if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(bss_target_section)) { | ||||
|                 bss_sections_by_name[bss_target_section] = section.get(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // If this section was marked as being in the ROM in the previous pass, copy it into the ROM now.
 | ||||
|         if (section_out.rom_addr != (uint32_t)-1) { | ||||
|             // Adjust the section's final ROM address to account for the minimum load address.
 | ||||
|             section_out.rom_addr -= min_load_address; | ||||
|             // Resize the output rom if needed to fit this section.
 | ||||
|             size_t required_rom_size = section_out.rom_addr + section_out.size; | ||||
|             if (required_rom_size > context.rom.size()) { | ||||
|                 context.rom.resize(required_rom_size); | ||||
|             } | ||||
|             // Copy this section's data into the rom.
 | ||||
|             std::copy(section->get_data(), section->get_data() + section->get_size(), &context.rom[section_out.rom_addr]); | ||||
|         } | ||||
|         // Check if this section is marked as executable, which means it has code in it
 | ||||
|         if (section->get_flags() & ELFIO::SHF_EXECINSTR) { | ||||
|             section_out.executable = true; | ||||
|         } | ||||
|         section_out.name = section_name; | ||||
|     } | ||||
| 
 | ||||
|     if (symtab_section == nullptr) { | ||||
|         fmt::print(stderr, "No symtab section found\n"); | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     ELFIO::symbol_section_accessor symbol_accessor{ elf_file, symtab_section }; | ||||
|     auto num_syms = symbol_accessor.get_symbols_num(); | ||||
| 
 | ||||
|     // TODO make sure that a reloc section was found for every section marked as relocatable
 | ||||
| 
 | ||||
|     // Process bss and reloc sections
 | ||||
|     for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { | ||||
|         N64Recomp::Section& section_out = context.sections[section_index]; | ||||
|         // Check if a bss section was found that corresponds with this section
 | ||||
|         auto bss_find = bss_sections_by_name.find(section_out.name); | ||||
|         if (bss_find != bss_sections_by_name.end()) { | ||||
|             section_out.bss_section_index = bss_find->second->get_index(); | ||||
|             section_out.bss_size = bss_find->second->get_size(); | ||||
|             context.bss_section_to_section[section_out.bss_section_index] = section_index; | ||||
|         } | ||||
| 
 | ||||
|         // Check if this section is in the ROM and relocatable.
 | ||||
|         const ELFIO::section* elf_section = elf_file.sections[section_index]; | ||||
|         bool in_rom = (elf_section->get_type() != ELFIO::SHT_NOBITS) && (elf_section->get_flags() & ELFIO::SHF_ALLOC); | ||||
|         bool is_relocatable = section_out.relocatable || context.has_reference_symbols(); | ||||
|         if (in_rom && is_relocatable) { | ||||
|             // Check if a reloc section was found that corresponds with this section
 | ||||
|             auto reloc_find = reloc_sections_by_name.find(section_out.name); | ||||
|             if (reloc_find != reloc_sections_by_name.end()) { | ||||
|                 // Create an accessor for the reloc section
 | ||||
|                 ELFIO::relocation_section_accessor rel_accessor{ elf_file, reloc_find->second }; | ||||
|                 // Allocate space for the relocs in this section
 | ||||
|                 section_out.relocs.resize(rel_accessor.get_entries_num()); | ||||
|                 // Track whether the previous reloc was a HI16 and its previous full_immediate
 | ||||
|                 bool prev_hi = false; | ||||
|                 // Track whether the previous reloc was a LO16
 | ||||
|                 bool prev_lo = false; | ||||
|                 uint32_t prev_hi_immediate = 0; | ||||
|                 uint32_t prev_hi_symbol = std::numeric_limits<uint32_t>::max(); | ||||
| 
 | ||||
|                 for (size_t i = 0; i < section_out.relocs.size(); i++) { | ||||
|                     // Get the current reloc
 | ||||
|                     ELFIO::Elf64_Addr rel_offset; | ||||
|                     ELFIO::Elf_Word rel_symbol; | ||||
|                     unsigned int rel_type; | ||||
|                     ELFIO::Elf_Sxword bad_rel_addend; // Addends aren't encoded in the reloc, so ignore this one
 | ||||
|                     rel_accessor.get_entry(i, rel_offset, rel_symbol, rel_type, bad_rel_addend); | ||||
| 
 | ||||
|                     N64Recomp::Reloc& reloc_out = section_out.relocs[i]; | ||||
| 
 | ||||
|                     // Get the real full_immediate by extracting the immediate from the instruction
 | ||||
|                     uint32_t reloc_rom_addr = section_out.rom_addr + rel_offset - section_out.ram_addr; | ||||
|                     uint32_t reloc_rom_word = byteswap(*reinterpret_cast<const uint32_t*>(context.rom.data() + reloc_rom_addr)); | ||||
|                     //context.rom section_out.rom_addr;
 | ||||
| 
 | ||||
|                     reloc_out.address = rel_offset; | ||||
|                     reloc_out.symbol_index = rel_symbol; | ||||
|                     reloc_out.type = static_cast<N64Recomp::RelocType>(rel_type); | ||||
| 
 | ||||
|                     std::string       rel_symbol_name; | ||||
|                     ELFIO::Elf64_Addr rel_symbol_value; | ||||
|                     ELFIO::Elf_Xword  rel_symbol_size; | ||||
|                     unsigned char     rel_symbol_bind; | ||||
|                     unsigned char     rel_symbol_type; | ||||
|                     ELFIO::Elf_Half   rel_symbol_section_index; | ||||
|                     unsigned char     rel_symbol_other; | ||||
| 
 | ||||
|                     bool found_rel_symbol = symbol_accessor.get_symbol( | ||||
|                         rel_symbol, rel_symbol_name, rel_symbol_value, rel_symbol_size, rel_symbol_bind, rel_symbol_type, rel_symbol_section_index, rel_symbol_other); | ||||
| 
 | ||||
|                     uint32_t rel_section_vram = 0; | ||||
|                     uint32_t rel_symbol_offset = 0; | ||||
| 
 | ||||
|                     // Remap relocations from the current section's bss section to itself.
 | ||||
|                     // TODO Do this for any bss section and not just the current section's bss section?
 | ||||
|                     if (rel_symbol_section_index == section_out.bss_section_index) { | ||||
|                         rel_symbol_section_index = section_index; | ||||
|                     } | ||||
| 
 | ||||
|                     // Check if the symbol is undefined and to know whether to look for it in the reference symbols.
 | ||||
|                     if (rel_symbol_section_index == ELFIO::SHN_UNDEF) { | ||||
|                         // Undefined sym, check the reference symbols.
 | ||||
|                         N64Recomp::SymbolReference sym_ref; | ||||
|                         if (!context.find_reference_symbol(rel_symbol_name, sym_ref)) { | ||||
|                             fmt::print(stderr, "Undefined symbol: {}, not found in input or reference symbols!\n", | ||||
|                                 rel_symbol_name); | ||||
|                             return nullptr; | ||||
|                         } | ||||
|                          | ||||
|                         reloc_out.reference_symbol = true; | ||||
|                         // Replace the reloc's symbol index with the index into the reference symbol array.
 | ||||
|                         rel_section_vram = 0; | ||||
|                         reloc_out.target_section = sym_ref.section_index; | ||||
|                         reloc_out.symbol_index = sym_ref.symbol_index; | ||||
|                         const auto& reference_symbol = context.get_reference_symbol(reloc_out.target_section, reloc_out.symbol_index); | ||||
|                         rel_symbol_offset = reference_symbol.section_offset; | ||||
| 
 | ||||
|                         bool target_section_relocatable = context.is_reference_section_relocatable(reloc_out.target_section); | ||||
| 
 | ||||
|                         if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32 && target_section_relocatable) { | ||||
|                             fmt::print(stderr, "Cannot reference {} in a statically initialized variable as it's defined in a relocatable section!\n", | ||||
|                                 rel_symbol_name); | ||||
|                             return nullptr; | ||||
|                         } | ||||
|                     } | ||||
|                     else if (rel_symbol_section_index == ELFIO::SHN_ABS) { | ||||
|                         reloc_out.reference_symbol = false; | ||||
|                         reloc_out.target_section = N64Recomp::SectionAbsolute; | ||||
|                         rel_section_vram = 0; | ||||
|                     } | ||||
|                     else { | ||||
|                         reloc_out.reference_symbol = false; | ||||
|                         reloc_out.target_section = rel_symbol_section_index; | ||||
|                         // Handle special sections.
 | ||||
|                         if (rel_symbol_section_index >= context.sections.size()) { | ||||
|                             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; | ||||
|                         } | ||||
|                         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)
 | ||||
|                     if (reloc_out.type == N64Recomp::RelocType::R_MIPS_LO16) { | ||||
|                         uint32_t rel_immediate = reloc_rom_word & 0xFFFF; | ||||
|                         uint32_t full_immediate = (prev_hi_immediate << 16) + (int16_t)rel_immediate; | ||||
|                         reloc_out.target_section_offset = full_immediate + rel_symbol_offset - rel_section_vram; | ||||
|                         if (prev_hi) { | ||||
|                             if (prev_hi_symbol != rel_symbol) { | ||||
|                                 fmt::print(stderr, "Paired HI16 and LO16 relocations have different symbols\n" | ||||
|                                                     "  LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", | ||||
|                                     i, section_out.name, reloc_out.symbol_index, reloc_out.address); | ||||
|                                 return nullptr; | ||||
|                             } | ||||
| 
 | ||||
|                             // Set the previous HI16 relocs' relocated address.
 | ||||
|                             section_out.relocs[i - 1].target_section_offset = reloc_out.target_section_offset; | ||||
|                         } | ||||
|                         else { | ||||
|                             // Orphaned LO16 reloc warnings.
 | ||||
|                             if (elf_config.unpaired_lo16_warnings) { | ||||
|                                 if (prev_lo) { | ||||
|                                     // Don't warn if multiple LO16 in a row reference the same symbol, as some linkers will use this behavior.
 | ||||
|                                     if (prev_hi_symbol != rel_symbol) { | ||||
|                                         fmt::print(stderr, "[WARN] LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X} follows LO16 with different symbol\n", | ||||
|                                             i, section_out.name, reloc_out.symbol_index, reloc_out.address); | ||||
|                                     } | ||||
|                                 } | ||||
|                                 else { | ||||
|                                     fmt::print(stderr, "[WARN] Unpaired LO16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", | ||||
|                                         i, section_out.name, reloc_out.symbol_index, reloc_out.address); | ||||
|                                 } | ||||
|                             } | ||||
|                             // Even though this is an orphaned LO16 reloc, the previous calculation for the addend still follows the MIPS System V ABI documentation:
 | ||||
|                             // "R_MIPS_LO16 entries without an R_MIPS_HI16 entry immediately preceding are orphaned and the previously defined
 | ||||
|                             // R_MIPS_HI16 is used for computing the addend."
 | ||||
|                             // Therefore, nothing needs to be done to the section_offset member.
 | ||||
|                         } | ||||
|                         prev_lo = true; | ||||
|                     } else { | ||||
|                         if (prev_hi) { | ||||
|                             // This is an invalid elf as the MIPS System V ABI documentation states:
 | ||||
|                             // "Each relocation type of R_MIPS_HI16 must have an associated R_MIPS_LO16 entry
 | ||||
|                             // immediately following it in the list of relocations."
 | ||||
|                             fmt::print(stderr, "Unpaired HI16 reloc index {} in section {} referencing symbol {} with offset 0x{:08X}\n", | ||||
|                                 i - 1, section_out.name, section_out.relocs[i - 1].symbol_index, section_out.relocs[i - 1].address); | ||||
|                             return nullptr; | ||||
|                         } | ||||
|                         prev_lo = false; | ||||
|                     } | ||||
| 
 | ||||
|                     if (reloc_out.type == N64Recomp::RelocType::R_MIPS_HI16) { | ||||
|                         uint32_t rel_immediate = reloc_rom_word & 0xFFFF; | ||||
|                         prev_hi = true; | ||||
|                         prev_hi_immediate = rel_immediate; | ||||
|                         prev_hi_symbol = rel_symbol; | ||||
|                     } else { | ||||
|                         prev_hi = false; | ||||
|                     } | ||||
| 
 | ||||
|                     if (reloc_out.type == N64Recomp::RelocType::R_MIPS_32) { | ||||
|                         // The reloc addend is just the existing word before relocation, so the section offset can just be the symbol's section offset.
 | ||||
|                         // Incorporating the addend will be handled at load-time.
 | ||||
|                         reloc_out.target_section_offset = rel_symbol_offset; | ||||
|                         // TODO set section_out.has_mips32_relocs to true if this section should emit its mips32 relocs (mainly for TLB mapping).
 | ||||
| 
 | ||||
|                         if (reloc_out.reference_symbol) { | ||||
|                             uint32_t reloc_target_section_addr = context.get_reference_section_vram(reloc_out.target_section); | ||||
|                             // Patch the word in the ROM to incorporate the symbol's value.
 | ||||
|                             uint32_t updated_reloc_word = reloc_rom_word + reloc_target_section_addr + reloc_out.target_section_offset; | ||||
|                             *reinterpret_cast<uint32_t*>(context.rom.data() + reloc_rom_addr) = byteswap(updated_reloc_word); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if (reloc_out.type == N64Recomp::RelocType::R_MIPS_26) { | ||||
|                         uint32_t rel_immediate = (reloc_rom_word & 0x3FFFFFF) << 2; | ||||
|                         if (reloc_out.reference_symbol) { | ||||
|                             // Reference symbol relocs have their section offset already calculated, so don't apply the R_MIPS26 rule for the upper 4 bits.
 | ||||
|                             // TODO Find a way to unify this with the else case.
 | ||||
|                             reloc_out.target_section_offset = rel_immediate + rel_symbol_offset - rel_section_vram; | ||||
|                         } | ||||
|                         else { | ||||
|                             reloc_out.target_section_offset = rel_immediate + rel_symbol_offset + (section_out.ram_addr & 0xF0000000) - rel_section_vram; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Sort this section's relocs by address, which allows for binary searching and more efficient iteration during recompilation.
 | ||||
|             // This is safe to do as the entire full_immediate in present in relocs due to the pairing that was done earlier, so the HI16 does not
 | ||||
|             // need to directly preceed the matching LO16 anymore.
 | ||||
|             std::sort(section_out.relocs.begin(), section_out.relocs.end(),  | ||||
|                 [](const N64Recomp::Reloc& a, const N64Recomp::Reloc& b) { | ||||
|                     return a.address < b.address; | ||||
|                 } | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return symtab_section; | ||||
| } | ||||
| 
 | ||||
| static void setup_context_for_elf(N64Recomp::Context& context, const ELFIO::elfio& elf_file) { | ||||
|     context.sections.resize(elf_file.sections.size()); | ||||
|     context.section_functions.resize(elf_file.sections.size()); | ||||
|     context.functions.reserve(1024); | ||||
|     context.functions_by_vram.reserve(context.functions.capacity()); | ||||
|     context.functions_by_name.reserve(context.functions.capacity()); | ||||
|     context.rom.reserve(8 * 1024 * 1024); | ||||
| } | ||||
| 
 | ||||
| bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_path, Context& out, const ElfParsingConfig& elf_config, bool for_dumping_context, DataSymbolMap& data_syms_out, bool& found_entrypoint_out) { | ||||
|     ELFIO::elfio elf_file; | ||||
| 
 | ||||
|     if (!elf_file.load(elf_file_path.string())) { | ||||
|         fmt::print("Elf file not found\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (elf_file.get_class() != ELFIO::ELFCLASS32) { | ||||
|         fmt::print("Incorrect elf class\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { | ||||
|         fmt::print("Incorrect endianness\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     setup_context_for_elf(out, elf_file); | ||||
| 
 | ||||
|     // Read all of the sections in the elf and look for the symbol table section
 | ||||
|     ELFIO::section* symtab_section = read_sections(out, elf_config, elf_file); | ||||
| 
 | ||||
|     // If no symbol table was found then exit
 | ||||
|     if (symtab_section == nullptr) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Read all of the symbols in the elf and look for the entrypoint function
 | ||||
|     found_entrypoint_out = read_symbols(out, elf_file, symtab_section, elf_config, for_dumping_context, data_syms_out); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
							
								
								
									
										1422
									
								
								src/main.cpp
									
										
									
									
									
								
							
							
						
						
									
										1422
									
								
								src/main.cpp
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										736
									
								
								src/mod_symbols.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										736
									
								
								src/mod_symbols.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,736 @@ | |||
| #include <cstring> | ||||
| 
 | ||||
| #include "n64recomp.h" | ||||
| 
 | ||||
| struct FileHeader { | ||||
|     char magic[8]; // N64RSYMS
 | ||||
|     uint32_t version; | ||||
| }; | ||||
| 
 | ||||
| struct FileSubHeaderV1 { | ||||
|     uint32_t num_sections; | ||||
|     uint32_t num_dependencies; | ||||
|     uint32_t num_imports; | ||||
|     uint32_t num_dependency_events; | ||||
|     uint32_t num_replacements; | ||||
|     uint32_t num_exports; | ||||
|     uint32_t num_callbacks; | ||||
|     uint32_t num_provided_events; | ||||
|     uint32_t string_data_size; | ||||
| }; | ||||
| 
 | ||||
| struct SectionHeaderV1 { | ||||
|     uint32_t flags; | ||||
|     uint32_t file_offset; | ||||
|     uint32_t vram; | ||||
|     uint32_t rom_size; | ||||
|     uint32_t bss_size; | ||||
|     uint32_t num_funcs; | ||||
|     uint32_t num_relocs; | ||||
| }; | ||||
| 
 | ||||
| struct FuncV1 { | ||||
|     uint32_t section_offset; | ||||
|     uint32_t size; | ||||
| }; | ||||
| 
 | ||||
| // Local section flag, if set then the reloc is pointing to a section within the mod and the vrom is the section index.
 | ||||
| constexpr uint32_t SectionSelfVromFlagV1 = 0x80000000; | ||||
| 
 | ||||
| // Special sections
 | ||||
| constexpr uint32_t SectionImportVromV1 = 0xFFFFFFFE; | ||||
| constexpr uint32_t SectionEventVromV1 = 0xFFFFFFFD; | ||||
| 
 | ||||
| struct RelocV1 { | ||||
|     uint32_t section_offset; | ||||
|     uint32_t type; | ||||
|     uint32_t target_section_offset_or_index; // If this reloc references a special section (see above), this indicates the section's symbol index instead
 | ||||
|     uint32_t target_section_vrom; | ||||
| }; | ||||
| 
 | ||||
| struct DependencyV1 { | ||||
|     uint8_t major_version; | ||||
|     uint8_t minor_version; | ||||
|     uint8_t patch_version; | ||||
|     uint8_t reserved; | ||||
|     uint32_t mod_id_start; | ||||
|     uint32_t mod_id_size; | ||||
| }; | ||||
| 
 | ||||
| struct ImportV1 { | ||||
|     uint32_t name_start; | ||||
|     uint32_t name_size; | ||||
|     uint32_t dependency; | ||||
| }; | ||||
| 
 | ||||
| struct DependencyEventV1 { | ||||
|     uint32_t name_start; | ||||
|     uint32_t name_size; | ||||
|     uint32_t dependency; | ||||
| }; | ||||
| 
 | ||||
| struct ReplacementV1 { | ||||
|     uint32_t func_index; | ||||
|     uint32_t original_section_vrom; | ||||
|     uint32_t original_vram; | ||||
|     uint32_t flags; // force
 | ||||
| }; | ||||
| 
 | ||||
| struct ExportV1 { | ||||
|     uint32_t func_index; | ||||
|     uint32_t name_start; // offset into the string data
 | ||||
|     uint32_t name_size; | ||||
| }; | ||||
| 
 | ||||
| struct CallbackV1 { | ||||
|     uint32_t dependency_event_index; | ||||
|     uint32_t function_index; | ||||
| }; | ||||
| 
 | ||||
| struct EventV1 { | ||||
|     uint32_t name_start; | ||||
|     uint32_t name_size; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| const T* reinterpret_data(std::span<const char> data, size_t& offset, size_t count = 1) { | ||||
|     if (offset + (sizeof(T) * count) > data.size()) { | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     size_t original_offset = offset; | ||||
|     offset += sizeof(T) * count; | ||||
|     return reinterpret_cast<const T*>(data.data() + original_offset); | ||||
| } | ||||
| 
 | ||||
| bool check_magic(const FileHeader* header) { | ||||
|     static const char good_magic[] = {'N','6','4','R','S','Y','M','S'}; | ||||
|     static_assert(sizeof(good_magic) == sizeof(FileHeader::magic)); | ||||
| 
 | ||||
|     return memcmp(header->magic, good_magic, sizeof(good_magic)) == 0; | ||||
| } | ||||
| 
 | ||||
| static inline uint32_t round_up_4(uint32_t value) { | ||||
|     return (value + 3) & (~3); | ||||
| } | ||||
| 
 | ||||
| bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, N64Recomp::Context& mod_context) { | ||||
|     size_t offset = sizeof(FileHeader); | ||||
|     const FileSubHeaderV1* subheader = reinterpret_data<FileSubHeaderV1>(data, offset); | ||||
|     if (subheader == nullptr) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     size_t num_sections = subheader->num_sections; | ||||
|     size_t num_dependencies = subheader->num_dependencies; | ||||
|     size_t num_imports = subheader->num_imports; | ||||
|     size_t num_dependency_events = subheader->num_dependency_events; | ||||
|     size_t num_replacements = subheader->num_replacements; | ||||
|     size_t num_exports = subheader->num_exports; | ||||
|     size_t num_callbacks = subheader->num_callbacks; | ||||
|     size_t num_provided_events = subheader->num_provided_events; | ||||
|     size_t string_data_size = subheader->string_data_size; | ||||
| 
 | ||||
|     if (string_data_size & 0b11) { | ||||
|         printf("String data size of %zu is not a multiple of 4\n", string_data_size); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     const char* string_data = reinterpret_data<char>(data, offset, string_data_size); | ||||
|     if (string_data == nullptr) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // TODO add proper creation methods for the remaining vectors and change these to reserves instead.
 | ||||
|     mod_context.sections.resize(num_sections); // Add method
 | ||||
|     mod_context.dependencies.reserve(num_dependencies); | ||||
|     mod_context.dependencies_by_name.reserve(num_dependencies);  | ||||
|     mod_context.import_symbols.reserve(num_imports); | ||||
|     mod_context.dependency_events.reserve(num_dependency_events); | ||||
|     mod_context.replacements.resize(num_replacements); // Add method
 | ||||
|     mod_context.exported_funcs.resize(num_exports); // Add method
 | ||||
|     mod_context.callbacks.reserve(num_callbacks); | ||||
|     mod_context.event_symbols.reserve(num_provided_events); | ||||
| 
 | ||||
|     for (size_t section_index = 0; section_index < num_sections; section_index++) { | ||||
|         const SectionHeaderV1* section_header = reinterpret_data<SectionHeaderV1>(data, offset); | ||||
|         if (section_header == nullptr) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         N64Recomp::Section& cur_section = mod_context.sections[section_index]; | ||||
| 
 | ||||
|         cur_section.rom_addr = section_header->file_offset; | ||||
|         cur_section.ram_addr = section_header->vram; | ||||
|         cur_section.size = section_header->rom_size; | ||||
|         cur_section.bss_size = section_header->bss_size; | ||||
|         cur_section.name = "mod_section_" + std::to_string(section_index); | ||||
|         cur_section.relocatable = true; | ||||
|         uint32_t num_funcs = section_header->num_funcs; | ||||
|         uint32_t num_relocs = section_header->num_relocs; | ||||
| 
 | ||||
| 
 | ||||
|         const FuncV1* funcs = reinterpret_data<FuncV1>(data, offset, num_funcs); | ||||
|         if (funcs == nullptr) { | ||||
|             printf("Failed to read funcs (count: %d)\n", num_funcs); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         const RelocV1* relocs = reinterpret_data<RelocV1>(data, offset, num_relocs); | ||||
|         if (relocs == nullptr) { | ||||
|             printf("Failed to read relocs (count: %d)\n", num_relocs); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         size_t start_func_index = mod_context.functions.size(); | ||||
|         mod_context.functions.resize(mod_context.functions.size() + num_funcs); | ||||
|         cur_section.relocs.resize(num_relocs); | ||||
| 
 | ||||
|         for (size_t func_index = 0; func_index < num_funcs; func_index++) { | ||||
|             uint32_t func_rom_addr = cur_section.rom_addr + funcs[func_index].section_offset; | ||||
|             if ((func_rom_addr & 0b11) != 0) { | ||||
|                 printf("Function %zu in section %zu file offset is not a multiple of 4\n", func_index, section_index); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             if ((funcs[func_index].size & 0b11) != 0) { | ||||
|                 printf("Function %zu in section %zu size is not a multiple of 4\n", func_index, section_index); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             N64Recomp::Function& cur_func = mod_context.functions[start_func_index + func_index]; | ||||
|             cur_func.vram = cur_section.ram_addr + funcs[func_index].section_offset; | ||||
|             cur_func.rom = cur_section.rom_addr + funcs[func_index].section_offset; | ||||
|             cur_func.words.resize(funcs[func_index].size / sizeof(uint32_t)); // Filled in later
 | ||||
|             cur_func.section_index = section_index; | ||||
|         } | ||||
| 
 | ||||
|         for (size_t reloc_index = 0; reloc_index < num_relocs; reloc_index++) { | ||||
|             N64Recomp::Reloc& cur_reloc = cur_section.relocs[reloc_index]; | ||||
|             const RelocV1& reloc_in = relocs[reloc_index]; | ||||
|             cur_reloc.address = cur_section.ram_addr + reloc_in.section_offset; | ||||
|             cur_reloc.type = static_cast<N64Recomp::RelocType>(reloc_in.type); | ||||
|             uint32_t target_section_vrom = reloc_in.target_section_vrom; | ||||
|             uint16_t reloc_target_section; | ||||
|             uint32_t reloc_target_section_offset; | ||||
|             uint32_t reloc_symbol_index; | ||||
|             if (target_section_vrom == SectionImportVromV1) { | ||||
|                 reloc_target_section = N64Recomp::SectionImport; | ||||
|                 reloc_target_section_offset = 0; // Not used for imports or reference symbols.
 | ||||
|                 reloc_symbol_index = reloc_in.target_section_offset_or_index; | ||||
|                 cur_reloc.reference_symbol = true; | ||||
|             } | ||||
|             else if (target_section_vrom == SectionEventVromV1) { | ||||
|                 reloc_target_section = N64Recomp::SectionEvent; | ||||
|                 reloc_target_section_offset = 0; // Not used for event symbols.
 | ||||
|                 reloc_symbol_index = reloc_in.target_section_offset_or_index; | ||||
|                 cur_reloc.reference_symbol = true; | ||||
|             } | ||||
|             else if (target_section_vrom & SectionSelfVromFlagV1) { | ||||
|                 reloc_target_section = static_cast<uint16_t>(target_section_vrom & ~SectionSelfVromFlagV1); | ||||
|                 reloc_target_section_offset = reloc_in.target_section_offset_or_index; | ||||
|                 reloc_symbol_index = 0; // Not used for normal relocs.
 | ||||
|                 cur_reloc.reference_symbol = false; | ||||
|                 if (reloc_target_section >= mod_context.sections.size()) { | ||||
|                     printf("Reloc %zu in section %zu references local section %u, but only %zu exist\n", | ||||
|                         reloc_index, section_index, reloc_target_section, mod_context.sections.size()); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 // TODO lookup by section index by original vrom
 | ||||
|                 auto find_section_it = sections_by_vrom.find(target_section_vrom); | ||||
|                 if (find_section_it == sections_by_vrom.end()) { | ||||
|                     printf("Reloc %zu in section %zu has a target section vrom (%08X) that doesn't match any original section\n", | ||||
|                         reloc_index, section_index, target_section_vrom); | ||||
|                     return false; | ||||
|                 } | ||||
|                 reloc_target_section = find_section_it->second; | ||||
|                 reloc_target_section_offset = reloc_in.target_section_offset_or_index; | ||||
|                 reloc_symbol_index = 0; // Not used for normal relocs.
 | ||||
|                 cur_reloc.reference_symbol = true; | ||||
|             } | ||||
|             cur_reloc.target_section = reloc_target_section; | ||||
|             cur_reloc.target_section_offset = reloc_target_section_offset; | ||||
|             cur_reloc.symbol_index = reloc_symbol_index; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const DependencyV1* dependencies = reinterpret_data<DependencyV1>(data, offset, num_dependencies); | ||||
|     if (dependencies == nullptr) { | ||||
|         printf("Failed to read dependencies (count: %zu)\n", num_dependencies); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { | ||||
|         const DependencyV1& dependency_in = dependencies[dependency_index]; | ||||
|         uint32_t mod_id_start = dependency_in.mod_id_start; | ||||
|         uint32_t mod_id_size = dependency_in.mod_id_size; | ||||
| 
 | ||||
|         if (mod_id_start + mod_id_size > string_data_size) { | ||||
|             printf("Dependency %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", | ||||
|                 dependency_index, mod_id_start, mod_id_size, string_data_size); | ||||
|         } | ||||
| 
 | ||||
|         std::string_view mod_id{ string_data + mod_id_start, string_data + mod_id_start + mod_id_size }; | ||||
|         mod_context.add_dependency(std::string{mod_id}, dependency_in.major_version, dependency_in.minor_version, dependency_in.patch_version); | ||||
|     } | ||||
| 
 | ||||
|     const ImportV1* imports = reinterpret_data<ImportV1>(data, offset, num_imports); | ||||
|     if (imports == nullptr) { | ||||
|         printf("Failed to read imports (count: %zu)\n", num_imports); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (size_t import_index = 0; import_index < num_imports; import_index++) { | ||||
|         const ImportV1& import_in = imports[import_index]; | ||||
|         uint32_t name_start = import_in.name_start; | ||||
|         uint32_t name_size = import_in.name_size; | ||||
|         uint32_t dependency_index = import_in.dependency; | ||||
| 
 | ||||
|         if (name_start + name_size > string_data_size) { | ||||
|             printf("Import %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", | ||||
|                 import_index, name_start, name_size, string_data_size); | ||||
|         } | ||||
| 
 | ||||
|         if (dependency_index >= num_dependencies) { | ||||
|             printf("Import %zu belongs to dependency %u, but only %zu dependencies were specified\n", | ||||
|                 import_index, dependency_index, num_dependencies); | ||||
|         } | ||||
| 
 | ||||
|         std::string_view import_name{ string_data + name_start, string_data + name_start + name_size }; | ||||
| 
 | ||||
|         mod_context.add_import_symbol(std::string{import_name}, dependency_index); | ||||
|     } | ||||
| 
 | ||||
|     const DependencyEventV1* dependency_events = reinterpret_data<DependencyEventV1>(data, offset, num_dependency_events); | ||||
|     if (dependency_events == nullptr) { | ||||
|         printf("Failed to read dependency events (count: %zu)\n", num_dependency_events); | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { | ||||
|         const DependencyEventV1& dependency_event_in = dependency_events[dependency_event_index]; | ||||
|         uint32_t name_start = dependency_event_in.name_start; | ||||
|         uint32_t name_size = dependency_event_in.name_size; | ||||
|         uint32_t dependency_index = dependency_event_in.dependency; | ||||
| 
 | ||||
|         if (name_start + name_size > string_data_size) { | ||||
|             printf("Dependency event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", | ||||
|                 dependency_event_index, name_start, name_size, string_data_size); | ||||
|         } | ||||
| 
 | ||||
|         std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size }; | ||||
| 
 | ||||
|         size_t dummy_dependency_event_index; | ||||
|         mod_context.add_dependency_event(std::string{dependency_event_name}, dependency_index, dummy_dependency_event_index); | ||||
|     } | ||||
| 
 | ||||
|     const ReplacementV1* replacements = reinterpret_data<ReplacementV1>(data, offset, num_replacements); | ||||
|     if (replacements == nullptr) { | ||||
|         printf("Failed to read replacements (count: %zu)\n", num_replacements); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (size_t replacement_index = 0; replacement_index < num_replacements; replacement_index++) { | ||||
|         N64Recomp::FunctionReplacement& cur_replacement = mod_context.replacements[replacement_index]; | ||||
| 
 | ||||
|         cur_replacement.func_index = replacements[replacement_index].func_index; | ||||
|         cur_replacement.original_section_vrom = replacements[replacement_index].original_section_vrom; | ||||
|         cur_replacement.original_vram = replacements[replacement_index].original_vram; | ||||
|         cur_replacement.flags = static_cast<N64Recomp::ReplacementFlags>(replacements[replacement_index].flags); | ||||
|     } | ||||
| 
 | ||||
|     const ExportV1* exports = reinterpret_data<ExportV1>(data, offset, num_exports); | ||||
|     if (exports == nullptr) { | ||||
|         printf("Failed to read exports (count: %zu)\n", num_exports); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (size_t export_index = 0; export_index < num_exports; export_index++) { | ||||
|         const ExportV1& export_in = exports[export_index]; | ||||
|         uint32_t func_index = export_in.func_index; | ||||
|         uint32_t name_start = export_in.name_start; | ||||
|         uint32_t name_size = export_in.name_size; | ||||
| 
 | ||||
|         if (func_index >= mod_context.functions.size()) { | ||||
|             printf("Export %zu has a function index of %u, but the symbol file only has %zu functions\n", | ||||
|                 export_index, func_index, mod_context.functions.size()); | ||||
|         } | ||||
| 
 | ||||
|         if (name_start + name_size > string_data_size) { | ||||
|             printf("Export %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", | ||||
|                 export_index, name_start, name_size, string_data_size); | ||||
|         } | ||||
| 
 | ||||
|         // Add the function to the exported function list.
 | ||||
|         mod_context.exported_funcs[export_index] = func_index; | ||||
|     } | ||||
| 
 | ||||
|     const CallbackV1* callbacks = reinterpret_data<CallbackV1>(data, offset, num_callbacks); | ||||
|     if (callbacks == nullptr) { | ||||
|         printf("Failed to read callbacks (count: %zu)\n", num_callbacks); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) { | ||||
|         const CallbackV1& callback_in = callbacks[callback_index]; | ||||
|         uint32_t dependency_event_index = callback_in.dependency_event_index; | ||||
|         uint32_t function_index = callback_in.function_index; | ||||
| 
 | ||||
|         if (dependency_event_index >= num_dependency_events) { | ||||
|             printf("Callback %zu is connected to dependency event %u, but only %zu dependency events were specified\n", | ||||
|                 callback_index, dependency_event_index, num_dependency_events); | ||||
|         } | ||||
| 
 | ||||
|         if (function_index >= mod_context.functions.size()) { | ||||
|             printf("Callback %zu uses function %u, but only %zu functions were specified\n", | ||||
|                 callback_index, function_index, mod_context.functions.size()); | ||||
|         } | ||||
| 
 | ||||
|         if (!mod_context.add_callback(dependency_event_index, function_index)) { | ||||
|             printf("Failed to add callback %zu\n", callback_index); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const EventV1* events = reinterpret_data<EventV1>(data, offset, num_provided_events); | ||||
|     if (events == nullptr) { | ||||
|         printf("Failed to read events (count: %zu)\n", num_provided_events); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (size_t event_index = 0; event_index < num_provided_events; event_index++) { | ||||
|         const EventV1& event_in = events[event_index]; | ||||
|         uint32_t name_start = event_in.name_start; | ||||
|         uint32_t name_size = event_in.name_size; | ||||
| 
 | ||||
|         if (name_start + name_size > string_data_size) { | ||||
|             printf("Event %zu has a name start of %u and size of %u, which extend beyond the string data's total size of %zu\n", | ||||
|                 event_index, name_start, name_size, string_data_size); | ||||
|         } | ||||
| 
 | ||||
|         std::string_view import_name{ string_data + name_start, string_data + name_start + name_size }; | ||||
| 
 | ||||
|         mod_context.add_event_symbol(std::string{import_name}); | ||||
|     } | ||||
| 
 | ||||
|     return offset == data.size(); | ||||
| } | ||||
| 
 | ||||
| N64Recomp::ModSymbolsError N64Recomp::parse_mod_symbols(std::span<const char> data, std::span<const uint8_t> binary, const std::unordered_map<uint32_t, uint16_t>& sections_by_vrom, const Context& reference_context, Context& mod_context_out) { | ||||
|     size_t offset = 0; | ||||
|     mod_context_out = {}; | ||||
|     const FileHeader* header = reinterpret_data<FileHeader>(data, offset); | ||||
| 
 | ||||
|     mod_context_out.import_reference_context(reference_context); | ||||
| 
 | ||||
|     if (header == nullptr) { | ||||
|         return ModSymbolsError::NotASymbolFile; | ||||
|     } | ||||
| 
 | ||||
|     if (!check_magic(header)) { | ||||
|         return ModSymbolsError::NotASymbolFile; | ||||
|     } | ||||
| 
 | ||||
|     bool valid = false; | ||||
| 
 | ||||
|     switch (header->version) { | ||||
|         case 1: | ||||
|             valid = parse_v1(data, sections_by_vrom, mod_context_out); | ||||
|             break; | ||||
|         default: | ||||
|             return ModSymbolsError::UnknownSymbolFileVersion; | ||||
|     } | ||||
| 
 | ||||
|     if (!valid) { | ||||
|         mod_context_out = {}; | ||||
|         return ModSymbolsError::CorruptSymbolFile; | ||||
|     } | ||||
| 
 | ||||
|     // Fill in the words for each function.
 | ||||
|     for (auto& cur_func : mod_context_out.functions) { | ||||
|         if (cur_func.rom + cur_func.words.size() * sizeof(cur_func.words[0]) > binary.size()) { | ||||
|             mod_context_out = {}; | ||||
|             return ModSymbolsError::FunctionOutOfBounds; | ||||
|         } | ||||
|         const uint32_t* func_rom = reinterpret_cast<const uint32_t*>(binary.data() + cur_func.rom); | ||||
|         for (size_t word_index = 0; word_index < cur_func.words.size(); word_index++) { | ||||
|             cur_func.words[word_index] = func_rom[word_index]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ModSymbolsError::Good; | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| void vec_put(std::vector<uint8_t>& vec, const T* data) { | ||||
|     size_t start_size = vec.size(); | ||||
|     vec.resize(vec.size() + sizeof(T)); | ||||
|     memcpy(vec.data() + start_size, data, sizeof(T)); | ||||
| } | ||||
| 
 | ||||
| void vec_put(std::vector<uint8_t>& vec, const std::string& data) { | ||||
|     size_t start_size = vec.size(); | ||||
|     vec.resize(vec.size() + data.size()); | ||||
|     memcpy(vec.data() + start_size, data.data(), data.size()); | ||||
| } | ||||
| 
 | ||||
| std::vector<uint8_t> N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& context) { | ||||
|     std::vector<uint8_t> ret{}; | ||||
|     ret.reserve(1024); | ||||
| 
 | ||||
|     const static FileHeader header { | ||||
|         .magic = {'N', '6', '4', 'R', 'S', 'Y', 'M', 'S'}, | ||||
|         .version = 1 | ||||
|     }; | ||||
| 
 | ||||
|     vec_put(ret, &header); | ||||
| 
 | ||||
|     size_t num_dependencies = context.dependencies.size(); | ||||
|     size_t num_imported_funcs = context.import_symbols.size(); | ||||
|     size_t num_dependency_events = context.dependency_events.size(); | ||||
| 
 | ||||
|     size_t num_exported_funcs = context.exported_funcs.size(); | ||||
|     size_t num_events = context.event_symbols.size(); | ||||
|     size_t num_callbacks = context.callbacks.size(); | ||||
|     size_t num_provided_events = context.event_symbols.size(); | ||||
| 
 | ||||
|     FileSubHeaderV1 sub_header { | ||||
|         .num_sections = static_cast<uint32_t>(context.sections.size()), | ||||
|         .num_dependencies = static_cast<uint32_t>(num_dependencies), | ||||
|         .num_imports = static_cast<uint32_t>(num_imported_funcs), | ||||
|         .num_dependency_events = static_cast<uint32_t>(num_dependency_events), | ||||
|         .num_replacements = static_cast<uint32_t>(context.replacements.size()), | ||||
|         .num_exports = static_cast<uint32_t>(num_exported_funcs), | ||||
|         .num_callbacks = static_cast<uint32_t>(num_callbacks), | ||||
|         .num_provided_events = static_cast<uint32_t>(num_provided_events), | ||||
|         .string_data_size = 0, | ||||
|     }; | ||||
| 
 | ||||
|     // Record the sub-header offset so the string data size can be filled in later.
 | ||||
|     size_t sub_header_offset = ret.size(); | ||||
|     vec_put(ret, &sub_header); | ||||
| 
 | ||||
|     // Build the string data from the exports and imports.
 | ||||
|     size_t strings_start = ret.size(); | ||||
| 
 | ||||
|     // Track the start of every dependency's name in the string data.
 | ||||
|     std::vector<uint32_t> dependency_name_positions{}; | ||||
|     dependency_name_positions.resize(num_dependencies); | ||||
|     for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { | ||||
|         const Dependency& dependency = context.dependencies[dependency_index]; | ||||
| 
 | ||||
|         dependency_name_positions[dependency_index] = static_cast<uint32_t>(ret.size() - strings_start); | ||||
|         vec_put(ret, dependency.mod_id); | ||||
|     } | ||||
| 
 | ||||
|     // Track the start of every imported function's name in the string data.
 | ||||
|     std::vector<uint32_t> imported_func_name_positions{}; | ||||
|     imported_func_name_positions.resize(num_imported_funcs); | ||||
|     for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { | ||||
|         const ImportSymbol& imported_func = context.import_symbols[import_index]; | ||||
| 
 | ||||
|         // Write this import's name into the strings data.
 | ||||
|         imported_func_name_positions[import_index] = static_cast<uint32_t>(ret.size() - strings_start); | ||||
|         vec_put(ret, imported_func.base.name); | ||||
|     } | ||||
| 
 | ||||
|     // Track the start of every dependency event's name in the string data.
 | ||||
|     std::vector<uint32_t> dependency_event_name_positions{}; | ||||
|     dependency_event_name_positions.resize(num_dependency_events); | ||||
|     for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { | ||||
|         const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index]; | ||||
| 
 | ||||
|         dependency_event_name_positions[dependency_event_index] = static_cast<uint32_t>(ret.size() - strings_start); | ||||
|         vec_put(ret, dependency_event.event_name); | ||||
|     } | ||||
|      | ||||
|     // Track the start of every exported function's name in the string data.
 | ||||
|     std::vector<uint32_t> exported_func_name_positions{}; | ||||
|     exported_func_name_positions.resize(num_exported_funcs); | ||||
|     for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) { | ||||
|         size_t function_index = context.exported_funcs[export_index]; | ||||
|         const Function& exported_func = context.functions[function_index]; | ||||
| 
 | ||||
|         exported_func_name_positions[export_index] = static_cast<uint32_t>(ret.size() - strings_start); | ||||
|         vec_put(ret, exported_func.name); | ||||
|     } | ||||
| 
 | ||||
|     // Track the start of every provided event's name in the string data.
 | ||||
|     std::vector<uint32_t> event_name_positions{}; | ||||
|     event_name_positions.resize(num_events); | ||||
|     for (size_t event_index = 0; event_index < num_events; event_index++) { | ||||
|         const EventSymbol& event_symbol = context.event_symbols[event_index]; | ||||
| 
 | ||||
|         // Write this event's name into the strings data.
 | ||||
|         event_name_positions[event_index] = static_cast<uint32_t>(ret.size() - strings_start); | ||||
|         vec_put(ret, event_symbol.base.name); | ||||
|     } | ||||
| 
 | ||||
|     // Align the data after the strings to 4 bytes.
 | ||||
|     size_t strings_size = round_up_4(ret.size() - strings_start); | ||||
|     ret.resize(strings_size + strings_start); | ||||
| 
 | ||||
|     // Fill in the string data size in the sub-header.
 | ||||
|     reinterpret_cast<FileSubHeaderV1*>(ret.data() + sub_header_offset)->string_data_size = strings_size; | ||||
| 
 | ||||
|     for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { | ||||
|         const Section& cur_section = context.sections[section_index]; | ||||
|         SectionHeaderV1 section_out { | ||||
|             .file_offset = cur_section.rom_addr, | ||||
|             .vram = cur_section.ram_addr, | ||||
|             .rom_size = cur_section.size, | ||||
|             .bss_size = cur_section.bss_size, | ||||
|             .num_funcs = static_cast<uint32_t>(context.section_functions[section_index].size()), | ||||
|             .num_relocs = static_cast<uint32_t>(cur_section.relocs.size()) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, §ion_out); | ||||
| 
 | ||||
|         for (size_t func_index : context.section_functions[section_index]) { | ||||
|             const Function& cur_func = context.functions[func_index]; | ||||
|             FuncV1 func_out { | ||||
|                 .section_offset = cur_func.vram - cur_section.ram_addr, | ||||
|                 .size = (uint32_t)(cur_func.words.size() * sizeof(cur_func.words[0]))  | ||||
|             }; | ||||
| 
 | ||||
|             vec_put(ret, &func_out); | ||||
|         } | ||||
| 
 | ||||
|         for (size_t reloc_index = 0; reloc_index < cur_section.relocs.size(); reloc_index++) { | ||||
|             const Reloc& cur_reloc = cur_section.relocs[reloc_index]; | ||||
|             uint32_t target_section_vrom; | ||||
|             uint32_t target_section_offset_or_index = cur_reloc.target_section_offset; | ||||
|             if (cur_reloc.target_section == SectionAbsolute) { | ||||
|                 printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already. Please report this issue.\n", | ||||
|                     reloc_index, section_index); | ||||
|                 return {}; | ||||
|             } | ||||
|             else if (cur_reloc.target_section == SectionImport) { | ||||
|                 target_section_vrom = SectionImportVromV1; | ||||
|                 target_section_offset_or_index = cur_reloc.symbol_index; | ||||
|             } | ||||
|             else if (cur_reloc.target_section == SectionEvent) { | ||||
|                 target_section_vrom = SectionEventVromV1; | ||||
|                 target_section_offset_or_index = cur_reloc.symbol_index; | ||||
|             } | ||||
|             else if (cur_reloc.reference_symbol) { | ||||
|                 target_section_vrom = context.get_reference_section_rom(cur_reloc.target_section); | ||||
|             } | ||||
|             else { | ||||
|                 if (cur_reloc.target_section >= context.sections.size()) { | ||||
|                     printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist. Please report this issue.\n", | ||||
|                         reloc_index, section_index, cur_reloc.target_section, context.sections.size()); | ||||
|                     return {}; | ||||
|                 } | ||||
|                 target_section_vrom = SectionSelfVromFlagV1 | cur_reloc.target_section; | ||||
|             } | ||||
|             RelocV1 reloc_out { | ||||
|                 .section_offset = cur_reloc.address - cur_section.ram_addr, | ||||
|                 .type = static_cast<uint32_t>(cur_reloc.type), | ||||
|                 .target_section_offset_or_index = target_section_offset_or_index, | ||||
|                 .target_section_vrom = target_section_vrom | ||||
|             }; | ||||
| 
 | ||||
|             vec_put(ret, &reloc_out); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Write the dependencies.
 | ||||
|     for (size_t dependency_index = 0; dependency_index < num_dependencies; dependency_index++) { | ||||
|         const Dependency& dependency = context.dependencies[dependency_index]; | ||||
| 
 | ||||
|         DependencyV1 dependency_out { | ||||
|             .major_version = dependency.major_version, | ||||
|             .minor_version = dependency.minor_version, | ||||
|             .patch_version = dependency.patch_version, | ||||
|             .mod_id_start = dependency_name_positions[dependency_index], | ||||
|             .mod_id_size = static_cast<uint32_t>(dependency.mod_id.size()) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &dependency_out); | ||||
|     } | ||||
| 
 | ||||
|     // Write the imported functions.
 | ||||
|     for (size_t import_index = 0; import_index < num_imported_funcs; import_index++) { | ||||
|         // Get the index of the reference symbol for this import.
 | ||||
|         const ImportSymbol& imported_func = context.import_symbols[import_index]; | ||||
| 
 | ||||
|         ImportV1 import_out { | ||||
|             .name_start = imported_func_name_positions[import_index], | ||||
|             .name_size = static_cast<uint32_t>(imported_func.base.name.size()), | ||||
|             .dependency = static_cast<uint32_t>(imported_func.dependency_index) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &import_out); | ||||
|     } | ||||
| 
 | ||||
|     // Write the dependency events.
 | ||||
|     for (size_t dependency_event_index = 0; dependency_event_index < num_dependency_events; dependency_event_index++) { | ||||
|         const DependencyEvent& dependency_event = context.dependency_events[dependency_event_index]; | ||||
| 
 | ||||
|         DependencyEventV1 dependency_event_out { | ||||
|             .name_start = dependency_event_name_positions[dependency_event_index], | ||||
|             .name_size = static_cast<uint32_t>(dependency_event.event_name.size()), | ||||
|             .dependency = static_cast<uint32_t>(dependency_event.dependency_index) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &dependency_event_out); | ||||
|     } | ||||
| 
 | ||||
|     // Write the function replacements.
 | ||||
|     for (const FunctionReplacement& cur_replacement : context.replacements) { | ||||
|         uint32_t flags = 0; | ||||
|         if ((cur_replacement.flags & ReplacementFlags::Force) == ReplacementFlags::Force) { | ||||
|             flags |= 0x1; | ||||
|         } | ||||
| 
 | ||||
|         ReplacementV1 replacement_out { | ||||
|             .func_index = cur_replacement.func_index, | ||||
|             .original_section_vrom = cur_replacement.original_section_vrom, | ||||
|             .original_vram = cur_replacement.original_vram, | ||||
|             .flags = flags | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &replacement_out); | ||||
|     }; | ||||
| 
 | ||||
|     // Write the exported functions.
 | ||||
|     for (size_t export_index = 0; export_index < num_exported_funcs; export_index++) { | ||||
|         size_t function_index = context.exported_funcs[export_index]; | ||||
|         const Function& exported_func = context.functions[function_index]; | ||||
| 
 | ||||
|         ExportV1 export_out { | ||||
|             .func_index = static_cast<uint32_t>(function_index), | ||||
|             .name_start = exported_func_name_positions[export_index], | ||||
|             .name_size = static_cast<uint32_t>(exported_func.name.size()) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &export_out); | ||||
|     } | ||||
| 
 | ||||
|     // Write the callbacks.
 | ||||
|     for (size_t callback_index = 0; callback_index < num_callbacks; callback_index++) { | ||||
|         const Callback& callback = context.callbacks[callback_index]; | ||||
| 
 | ||||
|         CallbackV1 callback_out { | ||||
|             .dependency_event_index = static_cast<uint32_t>(callback.dependency_event_index), | ||||
|             .function_index = static_cast<uint32_t>(callback.function_index) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &callback_out); | ||||
|     } | ||||
| 
 | ||||
|     // Write the provided events.
 | ||||
|     for (size_t event_index = 0; event_index < num_events; event_index++) { | ||||
|         const EventSymbol& event_symbol = context.event_symbols[event_index]; | ||||
| 
 | ||||
|         EventV1 event_out { | ||||
|             .name_start = event_name_positions[event_index], | ||||
|             .name_size = static_cast<uint32_t>(event_symbol.base.name.size()) | ||||
|         }; | ||||
| 
 | ||||
|         vec_put(ret, &event_out); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
							
								
								
									
										180
									
								
								src/operations.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/operations.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,180 @@ | |||
| #include "operations.h" | ||||
| 
 | ||||
| namespace N64Recomp { | ||||
|     const std::unordered_map<InstrId, UnaryOp> unary_ops { | ||||
|         { InstrId::cpu_lui,  { UnaryOpType::Lui,  Operand::Rt, Operand::ImmU16 } }, | ||||
|         { InstrId::cpu_mthi, { UnaryOpType::None, Operand::Hi, Operand::Rs } }, | ||||
|         { InstrId::cpu_mtlo, { UnaryOpType::None, Operand::Lo, Operand::Rs } }, | ||||
|         { InstrId::cpu_mfhi, { UnaryOpType::None, Operand::Rd, Operand::Hi } }, | ||||
|         { InstrId::cpu_mflo, { UnaryOpType::None, Operand::Rd, Operand::Lo } }, | ||||
|         { InstrId::cpu_mtc1, { UnaryOpType::None, Operand::FsU32L, Operand::Rt } }, | ||||
|         { InstrId::cpu_mfc1, { UnaryOpType::ToInt32, Operand::Rt, Operand::FsU32L } }, | ||||
|         // Float operations
 | ||||
|         { InstrId::cpu_mov_s,     { UnaryOpType::None,           Operand::Fd,       Operand::Fs,       true } }, | ||||
|         { InstrId::cpu_mov_d,     { UnaryOpType::None,           Operand::FdDouble, Operand::FsDouble, true } }, | ||||
|         { InstrId::cpu_neg_s,     { UnaryOpType::Negate,         Operand::Fd,       Operand::Fs,       true, true } }, | ||||
|         { InstrId::cpu_neg_d,     { UnaryOpType::Negate,         Operand::FdDouble, Operand::FsDouble, true, true } }, | ||||
|         { InstrId::cpu_abs_s,     { UnaryOpType::AbsFloat,       Operand::Fd,       Operand::Fs,       true, true } }, | ||||
|         { InstrId::cpu_abs_d,     { UnaryOpType::AbsDouble,      Operand::FdDouble, Operand::FsDouble, true, true } }, | ||||
|         { InstrId::cpu_sqrt_s,    { UnaryOpType::SqrtFloat,      Operand::Fd,       Operand::Fs,       true, true } }, | ||||
|         { InstrId::cpu_sqrt_d,    { UnaryOpType::SqrtDouble,     Operand::FdDouble, Operand::FsDouble, true, true } }, | ||||
|         { InstrId::cpu_cvt_s_w,   { UnaryOpType::ConvertSFromW,  Operand::Fd,       Operand::FsU32L,   true } }, | ||||
|         { InstrId::cpu_cvt_w_s,   { UnaryOpType::ConvertWFromS,  Operand::FdU32L,   Operand::Fs,       true } }, | ||||
|         { InstrId::cpu_cvt_d_w,   { UnaryOpType::ConvertDFromW,  Operand::FdDouble, Operand::FsU32L,   true } }, | ||||
|         { InstrId::cpu_cvt_w_d,   { UnaryOpType::ConvertWFromD,  Operand::FdU32L,   Operand::FsDouble, true } }, | ||||
|         { InstrId::cpu_cvt_d_s,   { UnaryOpType::ConvertDFromS,  Operand::FdDouble, Operand::Fs,       true, true } }, | ||||
|         { InstrId::cpu_cvt_s_d,   { UnaryOpType::ConvertSFromD,  Operand::Fd,       Operand::FsDouble, true, true } }, | ||||
|         { InstrId::cpu_cvt_d_l,   { UnaryOpType::ConvertDFromL,  Operand::FdDouble, Operand::FsU64,    true } }, | ||||
|         { InstrId::cpu_cvt_l_d,   { UnaryOpType::ConvertLFromD,  Operand::FdU64,    Operand::FsDouble, true, true } }, | ||||
|         { InstrId::cpu_cvt_s_l,   { UnaryOpType::ConvertSFromL,  Operand::Fd,       Operand::FsU64,    true } }, | ||||
|         { InstrId::cpu_cvt_l_s,   { UnaryOpType::ConvertLFromS,  Operand::FdU64,    Operand::Fs,       true, true } }, | ||||
|         { InstrId::cpu_trunc_w_s, { UnaryOpType::TruncateWFromS, Operand::FdU32L,   Operand::Fs,       true } }, | ||||
|         { InstrId::cpu_trunc_w_d, { UnaryOpType::TruncateWFromD, Operand::FdU32L,   Operand::FsDouble, true } }, | ||||
|         { InstrId::cpu_round_w_s, { UnaryOpType::RoundWFromS,    Operand::FdU32L,   Operand::Fs,       true } }, | ||||
|         { InstrId::cpu_round_w_d, { UnaryOpType::RoundWFromD,    Operand::FdU32L,   Operand::FsDouble, true } }, | ||||
|         { InstrId::cpu_ceil_w_s,  { UnaryOpType::CeilWFromS,     Operand::FdU32L,   Operand::Fs,       true } }, | ||||
|         { InstrId::cpu_ceil_w_d,  { UnaryOpType::CeilWFromD,     Operand::FdU32L,   Operand::FsDouble, true } }, | ||||
|         { InstrId::cpu_floor_w_s, { UnaryOpType::FloorWFromS,    Operand::FdU32L,   Operand::Fs,       true } }, | ||||
|         { InstrId::cpu_floor_w_d, { UnaryOpType::FloorWFromD,    Operand::FdU32L,   Operand::FsDouble, true } }, | ||||
|     }; | ||||
| 
 | ||||
|     // TODO fix usage of check_nan
 | ||||
|     const std::unordered_map<InstrId, BinaryOp> binary_ops { | ||||
|         // Addition/subtraction
 | ||||
|         { InstrId::cpu_addu,   { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_add,    { BinaryOpType::Add32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_negu,   { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, // pseudo op for subu
 | ||||
|         { InstrId::cpu_subu,   { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_sub,    { BinaryOpType::Sub32, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_daddu,  { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_dadd,   { BinaryOpType::Add64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_dsubu,  { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_dsub,   { BinaryOpType::Sub64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         // Addition/subtraction (immediate)
 | ||||
|         { InstrId::cpu_addi,   { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, | ||||
|         { InstrId::cpu_addiu,  { BinaryOpType::Add32, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, | ||||
|         { InstrId::cpu_daddi,  { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, | ||||
|         { InstrId::cpu_daddiu, { BinaryOpType::Add64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, | ||||
|         // Bitwise
 | ||||
|         { InstrId::cpu_and,    { BinaryOpType::And64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None },  { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_or,     { BinaryOpType::Or64,  Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None },  { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_nor,    { BinaryOpType::Nor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None },  { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_xor,    { BinaryOpType::Xor64, Operand::Rd, {{ UnaryOpType::None, UnaryOpType::None },  { Operand::Rs, Operand::Rt }}} }, | ||||
|         // Bitwise (immediate)
 | ||||
|         { InstrId::cpu_andi,   { BinaryOpType::And64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, | ||||
|         { InstrId::cpu_ori,    { BinaryOpType::Or64,  Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, | ||||
|         { InstrId::cpu_xori,   { BinaryOpType::Xor64, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Rs, Operand::ImmU16 }}} }, | ||||
|         // Shifts
 | ||||
|         /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ | ||||
|         { InstrId::cpu_sllv,   { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, | ||||
|         { InstrId::cpu_dsllv,  { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None,  UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, | ||||
|         { InstrId::cpu_srlv,   { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, | ||||
|         { InstrId::cpu_dsrlv,  { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, | ||||
|         /* BUG Should mask after (change op to Sra32 and input op to ToS64) */ | ||||
|         { InstrId::cpu_srav,   { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::Mask5 }, { Operand::Rt, Operand::Rs }}} }, | ||||
|         { InstrId::cpu_dsrav,  { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::Mask6 }, { Operand::Rt, Operand::Rs }}} }, | ||||
|         // Shifts (immediate)
 | ||||
|         /* BUG Should mask after (change op to Sll32 and input op to ToU32) */ | ||||
|         { InstrId::cpu_sll,    { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, | ||||
|         { InstrId::cpu_dsll,   { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, | ||||
|         { InstrId::cpu_dsll32, { BinaryOpType::Sll64, Operand::Rd, {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, | ||||
|         { InstrId::cpu_srl,    { BinaryOpType::Srl32, Operand::Rd, {{ UnaryOpType::ToU32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, | ||||
|         { InstrId::cpu_dsrl,   { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, | ||||
|         { InstrId::cpu_dsrl32, { BinaryOpType::Srl64, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, | ||||
|         /* BUG should cast after (change op to Sra32 and input op to ToS64) */ | ||||
|         { InstrId::cpu_sra,    { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS32, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, | ||||
|         { InstrId::cpu_dsra,   { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa }}} }, | ||||
|         { InstrId::cpu_dsra32, { BinaryOpType::Sra64, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rt, Operand::Sa32 }}} }, | ||||
|         // Comparisons
 | ||||
|         { InstrId::cpu_slt,   { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToS64, UnaryOpType::ToS64 }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         { InstrId::cpu_sltu,  { BinaryOpType::Less, Operand::Rd, {{ UnaryOpType::ToU64, UnaryOpType::ToU64 }, { Operand::Rs, Operand::Rt }}} }, | ||||
|         // Comparisons (immediate)
 | ||||
|         { InstrId::cpu_slti,  { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, | ||||
|         { InstrId::cpu_sltiu, { BinaryOpType::Less, Operand::Rt, {{ UnaryOpType::ToU64, UnaryOpType::None }, { Operand::Rs, Operand::ImmS16 }}} }, | ||||
|         // Float arithmetic
 | ||||
|         { InstrId::cpu_add_s, { BinaryOpType::AddFloat,  Operand::Fd,       {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, | ||||
|         { InstrId::cpu_add_d, { BinaryOpType::AddDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, | ||||
|         { InstrId::cpu_sub_s, { BinaryOpType::SubFloat,  Operand::Fd,       {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, | ||||
|         { InstrId::cpu_sub_d, { BinaryOpType::SubDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, | ||||
|         { InstrId::cpu_mul_s, { BinaryOpType::MulFloat,  Operand::Fd,       {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, | ||||
|         { InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, | ||||
|         { InstrId::cpu_div_s, { BinaryOpType::DivFloat,  Operand::Fd,       {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } }, | ||||
|         { InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } }, | ||||
|         // Float comparisons TODO remaining operations and investigate ordered/unordered and default values
 | ||||
|         { InstrId::cpu_c_lt_s,  { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_nge_s, { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_olt_s, { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_ult_s, { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_lt_d,  { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_nge_d, { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_olt_d, { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_ult_d, { BinaryOpType::Less,   Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
| 
 | ||||
|         { InstrId::cpu_c_le_s,  { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_ngt_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_ole_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_ule_s, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_le_d,  { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_ngt_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_ole_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_ule_d, { BinaryOpType::LessEq, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
| 
 | ||||
|         { InstrId::cpu_c_eq_s,  { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_ueq_s, { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_ngl_s, { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_seq_s, { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } }, | ||||
|         { InstrId::cpu_c_eq_d,  { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_ueq_d, { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         { InstrId::cpu_c_ngl_d, { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         /* TODO rename to c_seq_d when fixed in rabbitizer */ | ||||
|         { InstrId::cpu_c_deq_d, { BinaryOpType::Equal,  Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } }, | ||||
|         // Loads
 | ||||
|         { InstrId::cpu_ld,   { BinaryOpType::LD,  Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lw,   { BinaryOpType::LW,  Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lwu,  { BinaryOpType::LWU, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lh,   { BinaryOpType::LH,  Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lhu,  { BinaryOpType::LHU, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lb,   { BinaryOpType::LB,  Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lbu,  { BinaryOpType::LBU, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_ldl,  { BinaryOpType::LDL, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_ldr,  { BinaryOpType::LDR, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lwl,  { BinaryOpType::LWL, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lwr,  { BinaryOpType::LWR, Operand::Rt,    {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_lwc1, { BinaryOpType::LW, Operand::FtU32L, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}} }, | ||||
|         { InstrId::cpu_ldc1, { BinaryOpType::LD, Operand::FtU64,  {{ UnaryOpType::None, UnaryOpType::None }, { Operand::ImmS16, Operand::Base }}, true } }, | ||||
|     }; | ||||
| 
 | ||||
|     const std::unordered_map<InstrId, ConditionalBranchOp> conditional_branch_ops { | ||||
|         { InstrId::cpu_beq,     { BinaryOpType::Equal,     {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Rs, Operand::Rt }},   false, false }}, | ||||
|         { InstrId::cpu_beql,    { BinaryOpType::Equal,     {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Rs, Operand::Rt }},   false, true }}, | ||||
|         { InstrId::cpu_bne,     { BinaryOpType::NotEqual,  {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Rs, Operand::Rt }},   false, false }}, | ||||
|         { InstrId::cpu_bnel,    { BinaryOpType::NotEqual,  {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Rs, Operand::Rt }},   false, true }}, | ||||
|         { InstrId::cpu_bgez,    { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, | ||||
|         { InstrId::cpu_bgezl,   { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, | ||||
|         { InstrId::cpu_bgtz,    { BinaryOpType::Greater,   {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, | ||||
|         { InstrId::cpu_bgtzl,   { BinaryOpType::Greater,   {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, | ||||
|         { InstrId::cpu_blez,    { BinaryOpType::LessEq,    {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, | ||||
|         { InstrId::cpu_blezl,   { BinaryOpType::LessEq,    {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, | ||||
|         { InstrId::cpu_bltz,    { BinaryOpType::Less,      {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, false }}, | ||||
|         { InstrId::cpu_bltzl,   { BinaryOpType::Less,      {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, false, true }}, | ||||
|         { InstrId::cpu_bgezal,  { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, false }}, | ||||
|         { InstrId::cpu_bgezall, { BinaryOpType::GreaterEq, {{ UnaryOpType::ToS64, UnaryOpType::None }, { Operand::Rs, Operand::Zero }}, true, true }}, | ||||
|         { InstrId::cpu_bc1f,    { BinaryOpType::NotEqual,  {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, | ||||
|         { InstrId::cpu_bc1fl,   { BinaryOpType::NotEqual,  {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, | ||||
|         { InstrId::cpu_bc1t,    { BinaryOpType::Equal,     {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, false }}, | ||||
|         { InstrId::cpu_bc1tl,   { BinaryOpType::Equal,     {{ UnaryOpType::None,  UnaryOpType::None }, { Operand::Cop1cs, Operand::Zero }}, false, true }}, | ||||
|     }; | ||||
| 
 | ||||
|     const std::unordered_map<InstrId, StoreOp> store_ops { | ||||
|         { InstrId::cpu_sd,   { StoreOpType::SD,   Operand::Rt }}, | ||||
|         { InstrId::cpu_sdl,  { StoreOpType::SDL,  Operand::Rt }}, | ||||
|         { InstrId::cpu_sdr,  { StoreOpType::SDR,  Operand::Rt }}, | ||||
|         { InstrId::cpu_sw,   { StoreOpType::SW,   Operand::Rt }}, | ||||
|         { InstrId::cpu_swl,  { StoreOpType::SWL,  Operand::Rt }}, | ||||
|         { InstrId::cpu_swr,  { StoreOpType::SWR,  Operand::Rt }}, | ||||
|         { InstrId::cpu_sh,   { StoreOpType::SH,   Operand::Rt }}, | ||||
|         { InstrId::cpu_sb,   { StoreOpType::SB,   Operand::Rt }}, | ||||
|         { InstrId::cpu_sdc1, { StoreOpType::SDC1, Operand::FtU64 }}, | ||||
|         { InstrId::cpu_swc1, { StoreOpType::SWC1, Operand::FtU32L }}, | ||||
|     }; | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										660
									
								
								src/symbol_lists.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										660
									
								
								src/symbol_lists.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,660 @@ | |||
| #include "n64recomp.h" | ||||
| 
 | ||||
| const std::unordered_set<std::string> N64Recomp::reimplemented_funcs{ | ||||
|     // OS initialize functions
 | ||||
|     "__osInitialize_common", | ||||
|     "osInitialize", | ||||
|     "osGetMemSize", | ||||
|     // Audio interface functions
 | ||||
|     "osAiGetLength", | ||||
|     "osAiGetStatus", | ||||
|     "osAiSetFrequency", | ||||
|     "osAiSetNextBuffer", | ||||
|     // Video interface functions
 | ||||
|     "osViSetXScale", | ||||
|     "osViSetYScale", | ||||
|     "osCreateViManager", | ||||
|     "osViBlack", | ||||
|     "osViSetSpecialFeatures", | ||||
|     "osViGetCurrentFramebuffer", | ||||
|     "osViGetNextFramebuffer", | ||||
|     "osViSwapBuffer", | ||||
|     "osViSetMode", | ||||
|     "osViSetEvent", | ||||
|     // RDP functions
 | ||||
|     "osDpSetNextBuffer", | ||||
|     // RSP functions
 | ||||
|     "osSpTaskLoad", | ||||
|     "osSpTaskStartGo", | ||||
|     "osSpTaskYield", | ||||
|     "osSpTaskYielded", | ||||
|     "__osSpSetPc", | ||||
|     // Controller functions
 | ||||
|     "osContInit", | ||||
|     "osContStartReadData", | ||||
|     "osContGetReadData", | ||||
|     "osContStartQuery", | ||||
|     "osContGetQuery", | ||||
|     "osContSetCh", | ||||
|     // EEPROM functions
 | ||||
|     "osEepromProbe", | ||||
|     "osEepromWrite", | ||||
|     "osEepromLongWrite", | ||||
|     "osEepromRead", | ||||
|     "osEepromLongRead", | ||||
|     // Rumble functions
 | ||||
|     "__osMotorAccess", | ||||
|     "osMotorInit", | ||||
|     "osMotorStart", | ||||
|     "osMotorStop", | ||||
|     // PFS functions
 | ||||
|     "osPfsInitPak", | ||||
|     "osPfsFreeBlocks", | ||||
|     "osPfsAllocateFile", | ||||
|     "osPfsDeleteFile", | ||||
|     "osPfsFileState", | ||||
|     "osPfsFindFile", | ||||
|     "osPfsReadWriteFile", | ||||
|     // Parallel interface (cartridge, DMA, etc.) functions
 | ||||
|     "osCartRomInit", | ||||
|     "osCreatePiManager", | ||||
|     "osPiStartDma", | ||||
|     "osEPiStartDma", | ||||
|     "osPiGetStatus", | ||||
|     "osEPiRawStartDma", | ||||
|     "osEPiReadIo", | ||||
|     // Flash saving functions
 | ||||
|     "osFlashInit", | ||||
|     "osFlashReadStatus", | ||||
|     "osFlashReadId", | ||||
|     "osFlashClearStatus", | ||||
|     "osFlashAllErase", | ||||
|     "osFlashAllEraseThrough", | ||||
|     "osFlashSectorErase", | ||||
|     "osFlashSectorEraseThrough", | ||||
|     "osFlashCheckEraseEnd", | ||||
|     "osFlashWriteBuffer", | ||||
|     "osFlashWriteArray", | ||||
|     "osFlashReadArray", | ||||
|     "osFlashChange", | ||||
|     // Threading functions
 | ||||
|     "osCreateThread", | ||||
|     "osStartThread", | ||||
|     "osStopThread", | ||||
|     "osDestroyThread", | ||||
|     "osSetThreadPri", | ||||
|     "osGetThreadPri", | ||||
|     "osGetThreadId", | ||||
|     // Message Queue functions
 | ||||
|     "osCreateMesgQueue", | ||||
|     "osRecvMesg", | ||||
|     "osSendMesg", | ||||
|     "osJamMesg", | ||||
|     "osSetEventMesg", | ||||
|     // Timer functions
 | ||||
|     "osGetTime", | ||||
|     "osSetTimer", | ||||
|     "osStopTimer", | ||||
|     // Voice functions
 | ||||
|     "osVoiceSetWord", | ||||
|     "osVoiceCheckWord", | ||||
|     "osVoiceStopReadData", | ||||
|     "osVoiceInit", | ||||
|     "osVoiceMaskDictionary", | ||||
|     "osVoiceStartReadData", | ||||
|     "osVoiceControlGain", | ||||
|     "osVoiceGetReadData", | ||||
|     "osVoiceClearDictionary", | ||||
|     // interrupt functions
 | ||||
|     "osSetIntMask", | ||||
|     "__osDisableInt", | ||||
|     "__osRestoreInt", | ||||
|     // TLB functions
 | ||||
|     "osVirtualToPhysical", | ||||
|     // Coprocessor 0/1 functions
 | ||||
|     "osGetCount", | ||||
|     "__osSetFpcCsr", | ||||
|     // Cache funcs
 | ||||
|     "osInvalDCache", | ||||
|     "osInvalICache", | ||||
|     "osWritebackDCache", | ||||
|     "osWritebackDCacheAll", | ||||
|     // Debug functions
 | ||||
|     "is_proutSyncPrintf", | ||||
|     "__checkHardware_msp", | ||||
|     "__checkHardware_kmc", | ||||
|     "__checkHardware_isv", | ||||
|     "__osInitialize_msp", | ||||
|     "__osInitialize_kmc", | ||||
|     "__osInitialize_isv", | ||||
|     "__osRdbSend", | ||||
|     // ido math routines
 | ||||
|     "__ull_div", | ||||
|     "__ll_div", | ||||
|     "__ll_mul", | ||||
|     "__ull_rem", | ||||
|     "__ull_to_d", | ||||
|     "__ull_to_f", | ||||
| }; | ||||
| 
 | ||||
| const std::unordered_set<std::string> N64Recomp::ignored_funcs { | ||||
|     // OS initialize functions
 | ||||
|     "__createSpeedParam", | ||||
|     "__osInitialize_common", | ||||
|     "osInitialize", | ||||
|     "osGetMemSize", | ||||
|     // Audio interface functions
 | ||||
|     "osAiGetLength", | ||||
|     "osAiGetStatus", | ||||
|     "osAiSetFrequency", | ||||
|     "osAiSetNextBuffer", | ||||
|     "__osAiDeviceBusy", | ||||
|     // Video interface functions
 | ||||
|     "osViBlack", | ||||
|     "osViFade", | ||||
|     "osViGetCurrentField", | ||||
|     "osViGetCurrentFramebuffer", | ||||
|     "osViGetCurrentLine", | ||||
|     "osViGetCurrentMode", | ||||
|     "osViGetNextFramebuffer", | ||||
|     "osViGetStatus", | ||||
|     "osViRepeatLine", | ||||
|     "osViSetEvent", | ||||
|     "osViSetMode", | ||||
|     "osViSetSpecialFeatures", | ||||
|     "osViSetXScale", | ||||
|     "osViSetYScale", | ||||
|     "osViSwapBuffer", | ||||
|     "osCreateViManager", | ||||
|     "viMgrMain", | ||||
|     "__osViInit", | ||||
|     "__osViSwapContext", | ||||
|     "__osViGetCurrentContext", | ||||
|     // RDP functions
 | ||||
|     "osDpGetCounters", | ||||
|     "osDpSetStatus", | ||||
|     "osDpGetStatus", | ||||
|     "osDpSetNextBuffer", | ||||
|     "__osDpDeviceBusy", | ||||
|     // RSP functions
 | ||||
|     "osSpTaskLoad", | ||||
|     "osSpTaskStartGo", | ||||
|     "osSpTaskYield", | ||||
|     "osSpTaskYielded", | ||||
|     "__osSpDeviceBusy", | ||||
|     "__osSpGetStatus", | ||||
|     "__osSpRawStartDma", | ||||
|     "__osSpRawReadIo", | ||||
|     "__osSpRawWriteIo", | ||||
|     "__osSpSetPc", | ||||
|     "__osSpSetStatus", | ||||
|     // Controller functions
 | ||||
|     "osContGetQuery", | ||||
|     "osContGetReadData", | ||||
|     "osContInit", | ||||
|     "osContReset", | ||||
|     "osContSetCh", | ||||
|     "osContStartQuery", | ||||
|     "osContStartReadData", | ||||
|     "__osContAddressCrc", | ||||
|     "__osContDataCrc", | ||||
|     "__osContGetInitData", | ||||
|     "__osContRamRead", | ||||
|     "__osContRamWrite", | ||||
|     "__osContChannelReset", | ||||
|     // EEPROM functions
 | ||||
|     "osEepromLongRead", | ||||
|     "osEepromLongWrite", | ||||
|     "osEepromProbe", | ||||
|     "osEepromRead", | ||||
|     "osEepromWrite", | ||||
|     "__osEepStatus", | ||||
|     // Rumble functions
 | ||||
|     "osMotorInit", | ||||
|     "osMotorStart", | ||||
|     "osMotorStop", | ||||
|     "__osMotorAccess", | ||||
|     "_MakeMotorData", | ||||
|     // Pack functions
 | ||||
|     "__osCheckId", | ||||
|     "__osCheckPackId", | ||||
|     "__osGetId", | ||||
|     "__osPfsRWInode", | ||||
|     "__osRepairPackId", | ||||
|     "__osPfsSelectBank", | ||||
|     "__osCheckPackId", | ||||
|     "ramromMain", | ||||
|     // PFS functions
 | ||||
|     "osPfsAllocateFile", | ||||
|     "osPfsChecker", | ||||
|     "osPfsDeleteFile", | ||||
|     "osPfsFileState", | ||||
|     "osPfsFindFile", | ||||
|     "osPfsFreeBlocks", | ||||
|     "osPfsGetLabel", | ||||
|     "osPfsInit", | ||||
|     "osPfsInitPak", | ||||
|     "osPfsIsPlug", | ||||
|     "osPfsNumFiles", | ||||
|     "osPfsRepairId", | ||||
|     "osPfsReadWriteFile", | ||||
|     "__osPackEepReadData", | ||||
|     "__osPackEepWriteData", | ||||
|     "__osPackRamReadData", | ||||
|     "__osPackRamWriteData", | ||||
|     "__osPackReadData", | ||||
|     "__osPackRequestData", | ||||
|     "__osPfsGetInitData", | ||||
|     "__osPfsGetOneChannelData", | ||||
|     "__osPfsGetStatus", | ||||
|     "__osPfsRequestData", | ||||
|     "__osPfsRequestOneChannel", | ||||
|     "__osPfsCreateAccessQueue", | ||||
|     "__osPfsCheckRamArea", | ||||
|     "__osPfsGetNextPage", | ||||
|     // Low level serial interface functions
 | ||||
|     "__osSiDeviceBusy", | ||||
|     "__osSiGetStatus", | ||||
|     "__osSiRawStartDma", | ||||
|     "__osSiRawReadIo", | ||||
|     "__osSiRawWriteIo", | ||||
|     "__osSiCreateAccessQueue", | ||||
|     "__osSiGetAccess", | ||||
|     "__osSiRelAccess", | ||||
|     // Parallel interface (cartridge, DMA, etc.) functions
 | ||||
|     "osCartRomInit", | ||||
|     "osLeoDiskInit", | ||||
|     "osCreatePiManager", | ||||
|     "__osDevMgrMain", | ||||
|     "osPiGetCmdQueue", | ||||
|     "osPiGetStatus", | ||||
|     "osPiReadIo", | ||||
|     "osPiStartDma", | ||||
|     "osPiWriteIo", | ||||
|     "osEPiGetDeviceType", | ||||
|     "osEPiStartDma", | ||||
|     "osEPiWriteIo", | ||||
|     "osEPiReadIo", | ||||
|     "osPiRawStartDma", | ||||
|     "osPiRawReadIo", | ||||
|     "osPiRawWriteIo", | ||||
|     "osEPiRawStartDma", | ||||
|     "osEPiRawReadIo", | ||||
|     "osEPiRawWriteIo", | ||||
|     "__osPiRawStartDma", | ||||
|     "__osPiRawReadIo", | ||||
|     "__osPiRawWriteIo", | ||||
|     "__osEPiRawStartDma", | ||||
|     "__osEPiRawReadIo", | ||||
|     "__osEPiRawWriteIo", | ||||
|     "__osPiDeviceBusy", | ||||
|     "__osPiCreateAccessQueue", | ||||
|     "__osPiGetAccess", | ||||
|     "__osPiRelAccess", | ||||
|     "__osLeoAbnormalResume", | ||||
|     "__osLeoInterrupt", | ||||
|     "__osLeoResume", | ||||
|     // Flash saving functions
 | ||||
|     "osFlashInit", | ||||
|     "osFlashReadStatus", | ||||
|     "osFlashReadId", | ||||
|     "osFlashClearStatus", | ||||
|     "osFlashAllErase", | ||||
|     "osFlashAllEraseThrough", | ||||
|     "osFlashSectorErase", | ||||
|     "osFlashSectorEraseThrough", | ||||
|     "osFlashCheckEraseEnd", | ||||
|     "osFlashWriteBuffer", | ||||
|     "osFlashWriteArray", | ||||
|     "osFlashReadArray", | ||||
|     "osFlashChange", | ||||
|     // Threading functions
 | ||||
|     "osCreateThread", | ||||
|     "osStartThread", | ||||
|     "osStopThread", | ||||
|     "osDestroyThread", | ||||
|     "osYieldThread", | ||||
|     "osSetThreadPri", | ||||
|     "osGetThreadPri", | ||||
|     "osGetThreadId", | ||||
|     "__osDequeueThread", | ||||
|     // Message Queue functions
 | ||||
|     "osCreateMesgQueue", | ||||
|     "osSendMesg", | ||||
|     "osJamMesg", | ||||
|     "osRecvMesg", | ||||
|     "osSetEventMesg", | ||||
|     // Timer functions
 | ||||
|     "osStartTimer", | ||||
|     "osSetTimer", | ||||
|     "osStopTimer", | ||||
|     "osGetTime", | ||||
|     "__osInsertTimer", | ||||
|     "__osTimerInterrupt", | ||||
|     "__osTimerServicesInit", | ||||
|     "__osSetTimerIntr", | ||||
|     // Voice functions
 | ||||
|     "osVoiceSetWord", | ||||
|     "osVoiceCheckWord", | ||||
|     "osVoiceStopReadData", | ||||
|     "osVoiceInit", | ||||
|     "osVoiceMaskDictionary", | ||||
|     "osVoiceStartReadData", | ||||
|     "osVoiceControlGain", | ||||
|     "osVoiceGetReadData", | ||||
|     "osVoiceClearDictionary", | ||||
|     "__osVoiceCheckResult", | ||||
|     "__osVoiceContRead36", | ||||
|     "__osVoiceContWrite20", | ||||
|     "__osVoiceContWrite4", | ||||
|     "__osVoiceContRead2", | ||||
|     "__osVoiceSetADConverter", | ||||
|     "__osVoiceContDataCrc", | ||||
|     "__osVoiceGetStatus", | ||||
|     "corrupted", | ||||
|     "corrupted_init", | ||||
|     // exceptasm functions
 | ||||
|     "__osExceptionPreamble", | ||||
|     "__osException", | ||||
|     "__ptExceptionPreamble", | ||||
|     "__ptException", | ||||
|     "send_mesg", | ||||
|     "handle_CpU", | ||||
|     "__osEnqueueAndYield", | ||||
|     "__osEnqueueThread", | ||||
|     "__osPopThread", | ||||
|     "__osNop", | ||||
|     "__osDispatchThread", | ||||
|     "__osCleanupThread", | ||||
|     "osGetCurrFaultedThread", | ||||
|     "osGetNextFaultedThread", | ||||
|     // interrupt functions
 | ||||
|     "osSetIntMask", | ||||
|     "osGetIntMask", | ||||
|     "__osDisableInt", | ||||
|     "__osRestoreInt", | ||||
|     "__osSetGlobalIntMask", | ||||
|     "__osResetGlobalIntMask", | ||||
|     // TLB functions
 | ||||
|     "osMapTLB", | ||||
|     "osUnmapTLB", | ||||
|     "osUnmapTLBAll", | ||||
|     "osSetTLBASID", | ||||
|     "osMapTLBRdb", | ||||
|     "osVirtualToPhysical", | ||||
|     "__osGetTLBHi", | ||||
|     "__osGetTLBLo0", | ||||
|     "__osGetTLBLo1", | ||||
|     "__osGetTLBPageMask", | ||||
|     "__osGetTLBASID", | ||||
|     "__osProbeTLB", | ||||
|     // Coprocessor 0/1 functions
 | ||||
|     "__osSetCount", | ||||
|     "osGetCount", | ||||
|     "__osSetSR", | ||||
|     "__osGetSR", | ||||
|     "__osSetCause", | ||||
|     "__osGetCause", | ||||
|     "__osSetCompare", | ||||
|     "__osGetCompare", | ||||
|     "__osSetConfig", | ||||
|     "__osGetConfig", | ||||
|     "__osSetWatchLo", | ||||
|     "__osGetWatchLo", | ||||
|     "__osSetFpcCsr", | ||||
|     // Cache funcs
 | ||||
|     "osInvalDCache", | ||||
|     "osInvalICache", | ||||
|     "osWritebackDCache", | ||||
|     "osWritebackDCacheAll", | ||||
|     // Microcodes
 | ||||
|     "rspbootTextStart", | ||||
|     "gspF3DEX2_fifoTextStart", | ||||
|     "gspS2DEX2_fifoTextStart", | ||||
|     "gspL3DEX2_fifoTextStart", | ||||
|     // Debug functions
 | ||||
|     "msp_proutSyncPrintf", | ||||
|     "__osInitialize_msp", | ||||
|     "__checkHardware_msp", | ||||
|     "kmc_proutSyncPrintf", | ||||
|     "__osInitialize_kmc", | ||||
|     "__checkHardware_kmc", | ||||
|     "isPrintfInit", | ||||
|     "is_proutSyncPrintf", | ||||
|     "__osInitialize_isv", | ||||
|     "__checkHardware_isv", | ||||
|     "__isExpJP", | ||||
|     "__isExp", | ||||
|     "__osRdbSend", | ||||
|     "__rmonSendData", | ||||
|     "__rmonWriteMem", | ||||
|     "__rmonReadWordAt", | ||||
|     "__rmonWriteWordTo", | ||||
|     "__rmonWriteMem", | ||||
|     "__rmonSetSRegs", | ||||
|     "__rmonSetVRegs", | ||||
|     "__rmonStopThread", | ||||
|     "__rmonGetThreadStatus", | ||||
|     "__rmonGetVRegs", | ||||
|     "__rmonHitSpBreak", | ||||
|     "__rmonRunThread", | ||||
|     "__rmonClearBreak", | ||||
|     "__rmonGetBranchTarget", | ||||
|     "__rmonGetSRegs", | ||||
|     "__rmonSetBreak", | ||||
|     "__rmonReadMem", | ||||
|     "__rmonRunThread", | ||||
|     "__rmonCopyWords", | ||||
|     "__rmonExecute", | ||||
|     "__rmonGetExceptionStatus", | ||||
|     "__rmonGetExeName", | ||||
|     "__rmonGetFRegisters", | ||||
|     "__rmonGetGRegisters", | ||||
|     "__rmonGetRegionCount", | ||||
|     "__rmonGetRegions", | ||||
|     "__rmonGetRegisterContents", | ||||
|     "__rmonGetTCB", | ||||
|     "__rmonHitBreak", | ||||
|     "__rmonHitCpuFault", | ||||
|     "__rmonIdleRCP", | ||||
|     "__rmonInit", | ||||
|     "__rmonIOflush", | ||||
|     "__rmonIOhandler", | ||||
|     "__rmonIOputw", | ||||
|     "__rmonListBreak", | ||||
|     "__rmonListProcesses", | ||||
|     "__rmonListThreads", | ||||
|     "__rmonLoadProgram", | ||||
|     "__rmonMaskIdleThreadInts", | ||||
|     "__rmonMemcpy", | ||||
|     "__rmonPanic", | ||||
|     "__rmonRCPrunning", | ||||
|     "__rmonRunRCP", | ||||
|     "__rmonSendFault", | ||||
|     "__rmonSendHeader", | ||||
|     "__rmonSendReply", | ||||
|     "__rmonSetComm", | ||||
|     "__rmonSetFault", | ||||
|     "__rmonSetFRegisters", | ||||
|     "__rmonSetGRegisters", | ||||
|     "__rmonSetSingleStep", | ||||
|     "__rmonStepRCP", | ||||
|     "__rmonStopUserThreads", | ||||
|     "__rmonThreadStatus", | ||||
|     "__rmon", | ||||
|     "__rmonRunThread", | ||||
|     "rmonFindFaultedThreads", | ||||
|     "rmonMain", | ||||
|     "rmonPrintf", | ||||
|     "rmonGetRcpRegister", | ||||
|     "kdebugserver", | ||||
|     "send", | ||||
| 
 | ||||
|     // ido math routines
 | ||||
|     "__ll_div", | ||||
|     "__ll_lshift", | ||||
|     "__ll_mod", | ||||
|     "__ll_mul", | ||||
|     "__ll_rem", | ||||
|     "__ll_rshift", | ||||
|     "__ull_div", | ||||
|     "__ull_divremi", | ||||
|     "__ull_rem", | ||||
|     "__ull_rshift", | ||||
|     "__d_to_ll", | ||||
|     "__f_to_ll", | ||||
|     "__d_to_ull", | ||||
|     "__f_to_ull", | ||||
|     "__ll_to_d", | ||||
|     "__ll_to_f", | ||||
|     "__ull_to_d", | ||||
|     "__ull_to_f", | ||||
|     // Setjmp/longjmp for mario party
 | ||||
|     "setjmp", | ||||
|     "longjmp" | ||||
|     // 64-bit functions for banjo
 | ||||
|     "func_8025C29C", | ||||
|     "func_8025C240", | ||||
|     "func_8025C288", | ||||
| 
 | ||||
|     // rmonregs
 | ||||
|     "LoadStoreSU", | ||||
|     "LoadStoreVU", | ||||
|     "SetUpForRCPop", | ||||
|     "CleanupFromRCPop", | ||||
|     "__rmonGetGRegisters", | ||||
|     "__rmonSetGRegisters", | ||||
|     "__rmonGetFRegisters", | ||||
|     "__rmonSetFRegisters", | ||||
|     "rmonGetRcpRegister", | ||||
|     "__rmonGetSRegs", | ||||
|     "__rmonSetSRegs", | ||||
|     "__rmonGetVRegs", | ||||
|     "__rmonSetVRegs", | ||||
|     "__rmonGetRegisterContents", | ||||
| 
 | ||||
|     // rmonbrk
 | ||||
|     "SetTempBreakpoint", | ||||
|     "ClearTempBreakpoint", | ||||
|     "__rmonSetBreak", | ||||
|     "__rmonListBreak", | ||||
|     "__rmonClearBreak", | ||||
|     "__rmonGetBranchTarget", | ||||
|     "IsJump", | ||||
|     "__rmonSetSingleStep", | ||||
|     "__rmonGetExceptionStatus", | ||||
|     "rmonSendBreakMessage", | ||||
|     "__rmonHitBreak", | ||||
|     "__rmonHitSpBreak", | ||||
|     "__rmonHitCpuFault", | ||||
|     "rmonFindFaultedThreads", | ||||
| 
 | ||||
|     // kdebugserver
 | ||||
|     "string_to_u32", | ||||
|     "send_packet", | ||||
|     "clear_IP6", | ||||
|     "send", | ||||
|     "kdebugserver", | ||||
| }; | ||||
| 
 | ||||
| const std::unordered_set<std::string> N64Recomp::renamed_funcs{ | ||||
|     // Math
 | ||||
|     "sincosf", | ||||
|     "sinf", | ||||
|     "cosf", | ||||
|     "__sinf", | ||||
|     "__cosf", | ||||
|     "asinf", | ||||
|     "acosf", | ||||
|     "atanf", | ||||
|     "atan2f", | ||||
|     "tanf", | ||||
|     "sqrt", | ||||
|     "sqrtf", | ||||
| 
 | ||||
|     // Memory
 | ||||
|     "memcpy", | ||||
|     "memset", | ||||
|     "memmove", | ||||
|     "memcmp", | ||||
|     "strcmp", | ||||
|     "strcat", | ||||
|     "strcpy", | ||||
|     "strchr", | ||||
|     "strlen", | ||||
|     "strtok", | ||||
|     "sprintf", | ||||
|     "bzero", | ||||
|     "bcopy", | ||||
|     "bcmp", | ||||
| 
 | ||||
|     // long jumps
 | ||||
|     "setjmp", | ||||
|     "longjmp", | ||||
| 
 | ||||
|     // Math 2
 | ||||
|     "ldiv", | ||||
|     "lldiv", | ||||
|     "ceil", | ||||
|     "ceilf", | ||||
|     "floor", | ||||
|     "floorf", | ||||
|     "fmodf", | ||||
|     "fmod", | ||||
|     "modf", | ||||
|     "lround", | ||||
|     "lroundf", | ||||
|     "nearbyint", | ||||
|     "nearbyintf", | ||||
|     "round", | ||||
|     "roundf", | ||||
|     "trunc", | ||||
|     "truncf", | ||||
| 
 | ||||
|     // printf family
 | ||||
|     "vsprintf", | ||||
|     "gcvt", | ||||
|     "fcvt", | ||||
|     "ecvt", | ||||
| 
 | ||||
|     "__assert", | ||||
| 
 | ||||
|     // allocations
 | ||||
|     "malloc", | ||||
|     "free", | ||||
|     "realloc", | ||||
|     "calloc", | ||||
| 
 | ||||
|     // rand
 | ||||
|     "rand", | ||||
|     "srand", | ||||
|     "random", | ||||
| 
 | ||||
|     // gzip
 | ||||
|     "huft_build", | ||||
|     "huft_free", | ||||
|     "inflate_codes", | ||||
|     "inflate_stored", | ||||
|     "inflate_fixed", | ||||
|     "inflate_dynamic", | ||||
|     "inflate_block", | ||||
|     "inflate", | ||||
|     "expand_gzip", | ||||
|     "auRomDataRead" | ||||
|     "data_write", | ||||
|     "unzip", | ||||
|     "updcrc", | ||||
|     "clear_bufs", | ||||
|     "fill_inbuf", | ||||
|     "flush_window", | ||||
| 
 | ||||
|     // libgcc math routines
 | ||||
|     "__muldi3", | ||||
|     "__divdi3", | ||||
|     "__udivdi3", | ||||
|     "__umoddi3", | ||||
|     "div64_64", | ||||
|     "div64_32", | ||||
|     "__moddi3", | ||||
|     "_matherr", | ||||
| }; | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Wiseguy
						Wiseguy