commit 6e9049be40eac63d28ba27a367ccb936bb799c80 Author: Ran-j Date: Sat Apr 12 03:49:35 2025 -0300 migrate from private cloud diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a3e0b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/bin/ +/intermediate/ +.vs/ +out +.vscode +build + +*.exe +*.ilk +*.exp +*.log +*.tlog +*.ipch \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c041abb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.21) + +project("PS2 Retro X") + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_subdirectory("ps2xRecomp") +add_subdirectory("ps2xRuntime") +add_subdirectory("ps2xAnalyzer") \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..0803b81 --- /dev/null +++ b/Readme.md @@ -0,0 +1,104 @@ +## PS2Recomp: PlayStation 2 Static Recompiler (Not ready) + +* Note this is an experiment and does work as it should, feel free to open a PR to help the project. + +PS2Recomp is a tool designed to statically recompile PlayStation 2 ELF binaries into C++ code that can be compiled for any modern platform. This enables running PS2 games natively on PC and other platforms without traditional emulation. + +### Features + +* Translates MIPS R5900 instructions to C++ code +* Supports PS2-specific 128-bit MMI instructions +* Handles VU0 in macro mode +* Supports relocations and overlays +* Configurable via TOML files +* Single-file or multi-file output options +* Function stubbing and skipping + +### How It Works +PS2Recomp works by: + +Parsing a PS2 ELF file to extract functions, symbols, and relocations +Decoding the MIPS R5900 instructions in each function +Translating those instructions to equivalent C++ code +Generating a runtime that can execute the recompiled code + +The translated code is very literal, with each MIPS instruction mapping to a C++ operation. For example, `addiu $r4, $r4, 0x20 becomes ctx->r4 = ADD32(ctx->r4, 0X20);`. + +### Requirements + +* CMake 3.20 or higher +* C++20 compatible compiler (I only test with MSVC) +* SSE4/AVX support for 128-bit operations + +#### Building +```bash +git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git +cd PS2Recomp + +# Create build directory +mkdir build +cd build + +cmake .. +cmake --build . +``` +### Usage + +1. Create a configuration file (see `./ps2xRecomp/example_config.toml`) +2. Run the recompiler: +``` +./ps2recomp your_config.toml +``` + +Compile the generated C++ code +Link with a runtime implementation + +### Configuration +PS2Recomp uses TOML configuration files to specify: + +* Input ELF file +* Output directory +* Functions to stub or skip +* Instruction patches + +#### Example configuration: +```toml +toml[general] +input = "path/to/game.elf" +output = "output/" +single_file_output = false + +# Functions to stub +stubs = ["printf", "malloc", "free"] + +# Functions to skip +skip = ["abort", "exit"] + +# Patches +[patches] +instructions = [ + { address = "0x100004", value = "0x00000000" } +] +``` + +### Runtime +To execute the recompiled code, you'll need to implement or use a runtime that provides: + +* Memory management +* System call handling +* PS2-specific hardware simulation + +A basic runtime header is provided in `ps2xRuntime` folder. + +### Limitations + +* VU1 microcode support is limited +* Graphics Synthesizer and other hardware components need external implementation +* Some PS2-specific features may not be fully supported yet + +### Acknowledgments + +* Inspired by N64Recomp +* Uses ELFIO for ELF parsing +* Uses toml11 for TOML parsing +* Uses fmt for string formatting \ No newline at end of file diff --git a/ps2xAnalyzer/CMakeLists.txt b/ps2xAnalyzer/CMakeLists.txt new file mode 100644 index 0000000..2b8afdf --- /dev/null +++ b/ps2xAnalyzer/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.20) +project(PS2Analyzer VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +file(GLOB_RECURSE PS2ANALYZER_SOURCES + "src/*.cpp" +) + +add_executable(ps2_analyzer ${PS2ANALYZER_SOURCES}) + +target_include_directories(ps2_analyzer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/ps2xRecomp/include +) + +target_link_libraries(ps2_analyzer PRIVATE + fmt::fmt + ps2_recomp +) + +install(TARGETS ps2_analyzer + RUNTIME DESTINATION bin +) \ No newline at end of file diff --git a/ps2xAnalyzer/Readme.md b/ps2xAnalyzer/Readme.md new file mode 100644 index 0000000..3ce4b20 --- /dev/null +++ b/ps2xAnalyzer/Readme.md @@ -0,0 +1,84 @@ +# PS2 ELF Analyzer Tool + +The PS2 ELF Analyzer Tool helps automate the process of creating TOML configuration files for the PS2Recomp static recompiler. It analyzes PlayStation 2 ELF files and generates a recommended configuration based on the binary's characteristics. + +## Key Features + +* Analyzes PS2 ELF binaries to extract symbols, functions, and structure +* Identifies common library functions that should be stubbed +* Flags system functions that should be skipped during recompilation +* Detects potential instruction patterns that may need patching +* Generates a ready-to-use TOML configuration file for PS2Recomp + +## Using the Analyzer +```bash +ps2_analyzer +``` + +### Where: + +* `input_elf` is the path to the PS2 ELF file you want to analyze +* `output_toml` is the path where the generated TOML configuration will be saved + +## Example: +```bash +ps2_analyzer path/to/your/ps2_game.elf config.toml +``` + +## How It Works +The analyzer performs the following steps: + +* Parses the ELF file using the same ElfParser used by PS2Recomp +* Extracts functions, symbols, sections, and relocations +* Analyzes the entry point to understand initialization patterns +* Identifies library functions by name patterns and signatures +* Maps the call graph to understand relationships between functions +* Analyzes data usage patterns (basic implementation) +* Scans for problematic instructions that might need patching +* Generates a TOML configuration file with all findings + +## Generated Configuration +The tool creates a TOML file with the following sections: +```toml +[general] +input = "path/to/your/ps2_game.elf" +output = "output/" +single_file_output = false +runtime_header = "include/ps2_runtime.h" + +stubs = [ + # List of identified library functions to stub + "printf", + "malloc", + # ... +] + +skip = [ + # List of system functions to skip + "entry", + "_start", + # ... +] + +[patches] +instructions = [ + # Potential instruction patches + { address = "0x100008", value = "0x00000000" }, + # ... +] +``` + +## Extending the Analyzer +The analyzer is designed to be extensible. You can enhance its capabilities by: + +* Adding more library function patterns in initializeLibraryFunctions() +* Improving the call graph analysis in analyzeCallGraph() +* Enhancing data usage pattern detection in analyzeDataUsage() +* Refining patch detection logic in identifyPotentialPatches() + +## Limitations + +* The analyzer uses basic heuristics and may not catch all special cases +* Function identification relies heavily on symbol names +* Patch recommendations are preliminary and may need manual review +* Complex game-specific behaviors may not be detected \ No newline at end of file diff --git a/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h b/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h new file mode 100644 index 0000000..29e10ed --- /dev/null +++ b/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h @@ -0,0 +1,59 @@ +#ifndef PS2RECOMP_ELF_ANALYZER_H +#define PS2RECOMP_ELF_ANALYZER_H + +#include "ps2recomp/types.h" +#include "ps2recomp/elf_parser.h" +#include "ps2recomp/r5900_decoder.h" +#include +#include +#include +#include +#include +#include + +namespace ps2recomp +{ + class ElfAnalyzer + { + public: + ElfAnalyzer(const std::string &elfPath); + ~ElfAnalyzer(); + + bool analyze(); + bool generateToml(const std::string &outputPath); + + private: + std::string m_elfPath; + std::unique_ptr m_elfParser; + std::unique_ptr m_decoder; + + std::vector m_functions; + std::vector m_symbols; + std::vector
m_sections; + std::vector m_relocations; + + std::unordered_set m_libFunctions; // Library functions to stub + std::unordered_set m_skipFunctions; // Functions to skip + std::unordered_map m_patches; // Address -> instruction patches + + // Common PS2 library function names + void initializeLibraryFunctions(); + + // Analysis methods + void analyzeEntryPoint(); + void analyzeLibraryFunctions(); + void analyzeCallGraph(); + void analyzeDataUsage(); + void identifyPotentialPatches(); + std::string escapeBackslashes(const std::string &path); + + // Helpers + bool isSystemFunction(const std::string &name) const; + bool isLibraryFunction(const std::string &name) const; + void decodeFunction(const Function &function); + std::string formatAddress(uint32_t address) const; + }; + +} + +#endif // PS2RECOMP_ELF_ANALYZER_H \ No newline at end of file diff --git a/ps2xAnalyzer/src/analyzer_main.cpp b/ps2xAnalyzer/src/analyzer_main.cpp new file mode 100644 index 0000000..2c270c8 --- /dev/null +++ b/ps2xAnalyzer/src/analyzer_main.cpp @@ -0,0 +1,58 @@ +#include "ps2recomp/elf_analyzer.h" +#include +#include + +void printUsage() +{ + std::cout << "PS2 ELF Analyzer\n"; + std::cout << "A tool to analyze PS2 ELF files and generate TOML configuration for PS2Recomp\n\n"; + std::cout << "Usage: ps2_analyzer \n"; + std::cout << " input_elf Path to the PS2 ELF file\n"; + std::cout << " output_toml Path to output TOML configuration file\n"; +} + +int main(int argc, char *argv[]) +{ + if (argc < 3) + { + printUsage(); + return 1; + } + + std::string elfPath = argv[1]; + std::string tomlPath = argv[2]; + + std::cout << "PS2 ELF Analyzer\n"; + std::cout << "----------------\n"; + std::cout << "Input ELF: " << elfPath << "\n"; + std::cout << "Output TOML: " << tomlPath << "\n\n"; + + try + { + ps2recomp::ElfAnalyzer analyzer(elfPath); + + if (!analyzer.analyze()) + { + std::cerr << "Failed to analyze ELF file\n"; + return 1; + } + + if (!analyzer.generateToml(tomlPath)) + { + std::cerr << "Failed to generate TOML configuration\n"; + return 1; + } + + std::cout << "\nAnalysis complete\n"; + std::cout << "TOML configuration has been written to: " << tomlPath << "\n"; + std::cout << "\nYou can now use this configuration with PS2Recomp:\n"; + std::cout << " ps2recomp " << tomlPath << "\n"; + + return 0; + } + catch (const std::exception &e) + { + std::cerr << "Error: " << e.what() << "\n"; + return 1; + } +} \ No newline at end of file diff --git a/ps2xAnalyzer/src/elf_analyzer.cpp b/ps2xAnalyzer/src/elf_analyzer.cpp new file mode 100644 index 0000000..abeb803 --- /dev/null +++ b/ps2xAnalyzer/src/elf_analyzer.cpp @@ -0,0 +1,354 @@ +#include "ps2recomp/elf_analyzer.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace ps2recomp +{ + ElfAnalyzer::ElfAnalyzer(const std::string &elfPath) + : m_elfPath(elfPath) + { + m_elfParser = std::make_unique(elfPath); + m_decoder = std::make_unique(); + + initializeLibraryFunctions(); + } + + ElfAnalyzer::~ElfAnalyzer() = default; + + bool ElfAnalyzer::analyze() + { + std::cout << "Analyzing ELF file: " << m_elfPath << std::endl; + + if (!m_elfParser->parse()) + { + std::cerr << "Failed to parse ELF file" << std::endl; + return false; + } + + m_functions = m_elfParser->extractFunctions(); + m_symbols = m_elfParser->extractSymbols(); + m_sections = m_elfParser->getSections(); + m_relocations = m_elfParser->getRelocations(); + + std::cout << "Extracted " << m_functions.size() << " functions" << std::endl; + std::cout << "Extracted " << m_symbols.size() << " symbols" << std::endl; + std::cout << "Extracted " << m_sections.size() << " sections" << std::endl; + std::cout << "Extracted " << m_relocations.size() << " relocations" << std::endl; + + analyzeEntryPoint(); + analyzeLibraryFunctions(); + analyzeCallGraph(); + analyzeDataUsage(); + identifyPotentialPatches(); + + std::cout << "Analysis completed" << std::endl; + std::cout << "- " << m_libFunctions.size() << " library functions to stub" << std::endl; + std::cout << "- " << m_skipFunctions.size() << " functions to skip" << std::endl; + std::cout << "- " << m_patches.size() << " potential patches identified" << std::endl; + + return true; + } + + bool ElfAnalyzer::generateToml(const std::string &outputPath) + { + std::ofstream file(outputPath); + if (!file) + { + std::cerr << "Failed to open output file: " << outputPath << std::endl; + return false; + } + + fs::path elfPathObj(m_elfPath); + std::string elfFileName = elfPathObj.filename().string(); + + fs::path outputPathObj(outputPath); + fs::path outputDir = outputPathObj.parent_path(); + std::string outputDirStr = outputDir.string() + "\\output\\"; + + file << "# PS2Recomp configuration for: " << elfFileName << "\n"; + file << "# Generated by ElfAnalyzer\n\n"; + + file << "[general]\n"; + file << "# Path to input ELF file\n"; + file << "input = \"" << escapeBackslashes(m_elfPath) << "\"\n\n"; + + file << "# Path to output directory\n"; + file << "output = \"" << escapeBackslashes(outputDirStr) << "\"\n\n"; + + file << "# Single file output mode (false for one file per function)\n"; + file << "single_file_output = false\n\n"; + + file << "# Functions to stub (these will generate empty implementations)\n"; + file << "stubs = [\n"; + for (const auto &func : m_libFunctions) + { + file << " \"" << func << "\",\n"; + } + file << "]\n\n"; + + file << "# Functions to skip (these will not be recompiled)\n"; + file << "skip = [\n"; + for (const auto &func : m_skipFunctions) + { + file << " \"" << func << "\",\n"; + } + file << "]\n\n"; + + if (!m_patches.empty()) + { + file << "# Patches to apply during recompilation\n"; + file << "[patches]\n"; + file << "# Individual instruction patches\n"; + file << "instructions = [\n"; + for (const auto &[address, value] : m_patches) + { + file << " { address = \"0x" << std::hex << address << "\", value = \"0x" + << std::hex << value << "\" }, # Identified potential patch\n"; + } + file << "]\n\n"; + } + + // file << "# Function hook patches\n"; + // file << "#[[patches.hook]]\n"; + // file << "#function = \"printf\"\n"; + // file << "#code = '''\n"; + // file << "#// Custom printf implementation\n"; + // file << "#void printf(uint8_t* rdram, R5900Context* ctx) {\n"; + // file << "# // Implementation here\n"; + // file << "#}\n"; + // file << "#'''\n\n"; + + std::cout << "Generated TOML configuration: " << outputPath << std::endl; + return true; + } + + void ElfAnalyzer::initializeLibraryFunctions() + { + // Standard C library functions + const std::vector stdLibFuncs = { + "printf", "sprintf", "snprintf", "fprintf", "vprintf", "vfprintf", + "malloc", "free", "calloc", "realloc", + "memcpy", "memset", "memmove", "memcmp", + "strcpy", "strncpy", "strcat", "strncat", + "strcmp", "strncmp", "strlen", "strstr", + "fopen", "fclose", "fread", "fwrite", "fseek", + "atoi", "atof", "rand", "srand"}; + + // PS2-specific system functions + const std::vector ps2SysFuncs = { + "FlushCache", "EI", "DI", "SYNC", + "syscall", "ResetEE", "SetGsCrt", "Exit", + "LoadExecPS2", "ExecPS2", "GetThreadId", + "RFU009", "InitRCnt", "GetOsTick", "ResetRCnt", + "DisableFPUExceptions", "EnableFPUExceptions"}; + + // PS2-specific library functions + const std::vector ps2LibFuncs = { + // GS + "GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetIMR", + "GsInit", "GsSyncV", "GsGetVideoMode", "GsSetVideoMode", + + // Pad + "PadInit", "PadPortOpen", "PadGetState", "PadRead", + "PadPortClose", "PadSetActAlign", "PadSetActDirect", + + // SIF + "SifInitRpc", "SifExitRpc", "SifBindRpc", "SifCallRpc", + "SifRegisterRpc", "SifCheckStatRpc", "SifSetRpcQueue", + "SifRpcLoop", "SifGetOtherData", + + // IPU + "sceSifAddCmdHandler", "sceSifRemoveCmdHandler", "sceSifSendCmd", + "sceSifInitCmd", "sceSifExitCmd", "sceSifSetCmdBuffer"}; + + // Add all to our library functions set + m_libFunctions.insert(stdLibFuncs.begin(), stdLibFuncs.end()); + m_libFunctions.insert(ps2SysFuncs.begin(), ps2SysFuncs.end()); + m_libFunctions.insert(ps2LibFuncs.begin(), ps2LibFuncs.end()); + } + + void ElfAnalyzer::analyzeEntryPoint() + { + auto it = std::find_if(m_functions.begin(), m_functions.end(), + [](const Function &f) + { return f.name == "entry" || f.name == "_start"; }); + + if (it != m_functions.end()) + { + std::cout << "Found entry point: " << it->name << " at 0x" << std::hex << it->start << std::dec << std::endl; + + m_skipFunctions.insert(it->name); + decodeFunction(*it); + } + else + { + std::cout << "Entry point not found" << std::endl; + } + } + + void ElfAnalyzer::analyzeLibraryFunctions() + { + for (const auto &symbol : m_symbols) + { + if (symbol.isFunction) + { + if (isLibraryFunction(symbol.name)) + { + m_libFunctions.insert(symbol.name); + } + + if (isSystemFunction(symbol.name)) + { + m_skipFunctions.insert(symbol.name); + } + } + } + } + + void ElfAnalyzer::analyzeCallGraph() + { + // functions called by the entry point are likely initialization and should be skipped + for (const auto &func : m_functions) + { + if (func.name.find("init") != std::string::npos || + func.name.find("Init") != std::string::npos || + func.name.find("start") != std::string::npos || + func.name.find("Start") != std::string::npos) + { + + m_skipFunctions.insert(func.name); + } + } + } + + void ElfAnalyzer::analyzeDataUsage() + { + // TODO + } + + void ElfAnalyzer::identifyPotentialPatches() + { + // This is a very basic implementation that looks for potentially problematic instructions + + for (const auto &func : m_functions) + { + if (m_skipFunctions.find(func.name) != m_skipFunctions.end()) + { + continue; // Skip functions that we're going to skip anyway + } + + // Decode the function + decodeFunction(func); + } + + // Example: If we find syscall instructions, suggest patching them to NOP + for (uint32_t addr = 0x100000; addr < 0x101000; addr += 4) + { + if (m_elfParser->isValidAddress(addr)) + { + uint32_t instr = m_elfParser->readWord(addr); + if ((instr & 0xFC00003F) == 0x0000000C) + { // syscall instruction + m_patches[addr] = 0x00000000; // NOP + } + } + } + } + + std::string ElfAnalyzer::escapeBackslashes(const std::string &path) + { + std::string result; + for (char ch : path) + { + if (ch == '\\') + result.append("\\\\"); + else + result.push_back(ch); + } + return result; + } + + bool ElfAnalyzer::isSystemFunction(const std::string &name) const + { + static const std::unordered_set systemFuncs = { + "entry", "_start", "_init", "_fini", + "abort", "exit", "_exit", + "_profiler_start", "_profiler_stop"}; + + return systemFuncs.find(name) != systemFuncs.end(); + } + + bool ElfAnalyzer::isLibraryFunction(const std::string &name) const + { + if (name.empty()) + return false; + + if (name[0] == '_' && name.size() > 1 && std::isalpha(name[1])) + { + return true; // Many library functions start with underscore + } + + // Check for common prefixes by Claude + const std::vector libraryPrefixes = { + "sce", "Sce", "SCE", // Sony prefixes + "sif", "Sif", "SIF", // SIF functions + "pad", "Pad", "PAD", // Pad functions + "gs", "Gs", "GS", // Graphics Synthesizer + "dma", "Dma", "DMA", // DMA functions + "iop", "Iop", "IOP" // IOP functions + }; + + for (const auto &prefix : libraryPrefixes) + { + if (name.rfind(prefix, 0) == 0) + { + return true; + } + } + + return false; + } + + void ElfAnalyzer::decodeFunction(const Function &function) + { + for (uint32_t addr = function.start; addr < function.end; addr += 4) + { + if (!m_elfParser->isValidAddress(addr)) + { + continue; + } + + uint32_t rawInstruction = m_elfParser->readWord(addr); + + try + { + Instruction inst = m_decoder->decodeInstruction(addr, rawInstruction); + + // TODO Analyze instructions for potential issues + + // Example: Look for syscalls that might need to be patched + if (inst.opcode == 0 && inst.function == 0xC) + { // syscall + std::cout << "Found syscall at " << formatAddress(addr) << std::endl; + } + } + catch (const std::exception &e) + { + std::cerr << "Error decoding instruction at " << formatAddress(addr) + << ": " << e.what() << std::endl; + } + } + } + + std::string ElfAnalyzer::formatAddress(uint32_t address) const + { + std::stringstream ss; + ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << address; + return ss.str(); + } + +} \ No newline at end of file diff --git a/ps2xRecomp/CMakeLists.txt b/ps2xRecomp/CMakeLists.txt new file mode 100644 index 0000000..34d59d7 --- /dev/null +++ b/ps2xRecomp/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 3.20) + +project(PS2Recomp VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) + +FetchContent_Declare( + elfio + GIT_REPOSITORY https://github.com/serge1/ELFIO.git + GIT_TAG main + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(elfio) + +FetchContent_Declare( + toml11 + GIT_REPOSITORY https://github.com/ToruNiina/toml11.git + GIT_TAG master +) +FetchContent_MakeAvailable(toml11) + +FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG master +) +FetchContent_MakeAvailable(fmt) + +file(GLOB_RECURSE PS2RECOMP_SOURCES + "src/*.cpp" +) + +file(GLOB_RECURSE PS2RECOMP_HEADERS + "include/*.h" + "include/*.hpp" +) + +add_executable(ps2recomp ${PS2RECOMP_SOURCES}) + +add_library(ps2_recomp STATIC ${PS2RECOMP_SOURCES}) + +target_include_directories(ps2recomp PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${elfio_SOURCE_DIR} +) + +target_include_directories(ps2_recomp PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${elfio_SOURCE_DIR} +) + +target_link_libraries(ps2recomp PRIVATE + fmt::fmt + toml11::toml11 +) + +target_link_libraries(ps2_recomp PUBLIC + fmt::fmt + toml11::toml11 +) + +install(TARGETS ps2recomp ps2_recomp + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +install(DIRECTORY include/ + DESTINATION include +) \ No newline at end of file diff --git a/ps2xRecomp/example_config.toml b/ps2xRecomp/example_config.toml new file mode 100644 index 0000000..3a1ab81 --- /dev/null +++ b/ps2xRecomp/example_config.toml @@ -0,0 +1,46 @@ +[general] +# Path to input ELF file +input = "path/to/your/ps2_game.elf" + +# Path to output directory +output = "output/" + +# Single file output mode (false for one file per function) +single_file_output = false + +# Path to runtime header (optional) +runtime_header = "include/ps2_runtime.h" + +# Functions to stub (these will generate empty implementations) +stubs = ["printf", "malloc", "free", "memcpy", "memset", "strncpy", "sprintf"] + +# Functions to skip (these will not be recompiled) +skip = ["abort", "exit", "_exit"] + +# Patches to apply during recompilation +[patches] +# Individual instruction patches +instructions = [ + { address = "0x100004", value = "0x00000000" }, # NOP an instruction + { address = "0x100104", value = "0x24040000" }, # Change an immediate value +] + +# Function hook patches (not yet implemented) +[[patches.hook]] +function = "printf" +code = ''' +// Custom printf implementation +void printf(uint8_t* rdram, R5900Context* ctx) { + // Implementation here +} +''' + +# Function replacement patches (not yet implemented) +[[patches.func]] +address = "0x100000" +code = ''' +// Custom implementation for function at 0x100000 +void func_00100000(uint8_t* rdram, R5900Context* ctx) { + // Implementation here +} +''' diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h new file mode 100644 index 0000000..e1f7dde --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -0,0 +1,38 @@ +#ifndef PS2RECOMP_CODE_GENERATOR_H +#define PS2RECOMP_CODE_GENERATOR_H + +#include "ps2recomp/types.h" +#include +#include + +namespace ps2recomp +{ + + class CodeGenerator + { + public: + CodeGenerator(const std::vector &symbols); + ~CodeGenerator(); + + std::string generateFunction(const Function &function, const std::vector &instructions); + std::string generateMacroHeader(); + std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot); + + private: + std::vector m_symbols; + + std::string translateInstruction(const Instruction &inst); + std::string translateMMIInstruction(const Instruction &inst); + std::string translateVUInstruction(const Instruction &inst); + std::string translateFPUInstruction(const Instruction& inst); + std::string translateCOP0Instruction(const Instruction& inst); + + std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, + const std::vector &entries); + + Symbol *findSymbolByAddress(uint32_t address); + }; + +} + +#endif // PS2RECOMP_CODE_GENERATOR_H \ No newline at end of file diff --git a/ps2xRecomp/include/ps2recomp/config_manager.h b/ps2xRecomp/include/ps2recomp/config_manager.h new file mode 100644 index 0000000..9a53d38 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/config_manager.h @@ -0,0 +1,25 @@ +#ifndef PS2RECOMP_CONFIG_MANAGER_H +#define PS2RECOMP_CONFIG_MANAGER_H + +#include "ps2recomp/types.h" +#include + +namespace ps2recomp +{ + + class ConfigManager + { + public: + ConfigManager(const std::string &configPath); + ~ConfigManager(); + + RecompilerConfig loadConfig(); + void saveConfig(const RecompilerConfig &config); + + private: + std::string m_configPath; + }; + +} + +#endif // PS2RECOMP_CONFIG_MANAGER_H \ No newline at end of file diff --git a/ps2xRecomp/include/ps2recomp/elf_parser.h b/ps2xRecomp/include/ps2recomp/elf_parser.h new file mode 100644 index 0000000..8754dcf --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/elf_parser.h @@ -0,0 +1,50 @@ +#ifndef PS2RECOMP_ELF_PARSER_H +#define PS2RECOMP_ELF_PARSER_H + +#include "ps2recomp/types.h" +#include +#include +#include +#include + +namespace ps2recomp +{ + + class ElfParser + { + public: + ElfParser(const std::string &filePath); + ~ElfParser(); + + bool parse(); + + std::vector extractFunctions(); + std::vector extractSymbols(); + std::vector
getSections(); + std::vector getRelocations(); + + // Helper methods + bool isValidAddress(uint32_t address) const; + uint32_t readWord(uint32_t address) const; + uint8_t *getSectionData(const std::string §ionName); + uint32_t getSectionAddress(const std::string §ionName); + uint32_t getSectionSize(const std::string §ionName); + + private: + std::string m_filePath; + std::unique_ptr m_elf; + + std::vector
m_sections; + std::vector m_symbols; + std::vector m_relocations; + + void loadSections(); + void loadSymbols(); + void loadRelocations(); + bool isExecutableSection(const ELFIO::section *section) const; + bool isDataSection(const ELFIO::section *section) const; + }; + +} // namespace ps2recomp + +#endif // PS2RECOMP_ELF_PARSER_H \ No newline at end of file diff --git a/ps2xRecomp/include/ps2recomp/instructions.h b/ps2xRecomp/include/ps2recomp/instructions.h new file mode 100644 index 0000000..d4968fb --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/instructions.h @@ -0,0 +1,376 @@ +#ifndef PS2RECOMP_INSTRUCTIONS_H +#define PS2RECOMP_INSTRUCTIONS_H + +#include + +namespace ps2recomp +{ + // Basic MIPS opcodes (shared with R4300i) + enum MipsOpcodes + { + OPCODE_SPECIAL = 0x00, + OPCODE_REGIMM = 0x01, + OPCODE_J = 0x02, + OPCODE_JAL = 0x03, + OPCODE_BEQ = 0x04, + OPCODE_BNE = 0x05, + OPCODE_BLEZ = 0x06, + OPCODE_BGTZ = 0x07, + OPCODE_ADDI = 0x08, + OPCODE_ADDIU = 0x09, + OPCODE_SLTI = 0x0A, + OPCODE_SLTIU = 0x0B, + OPCODE_ANDI = 0x0C, + OPCODE_ORI = 0x0D, + OPCODE_XORI = 0x0E, + OPCODE_LUI = 0x0F, + OPCODE_COP0 = 0x10, + OPCODE_COP1 = 0x11, + OPCODE_COP2 = 0x12, // VU0 macro instructions + OPCODE_COP3 = 0x13, // Unused on PS2 + OPCODE_BEQL = 0x14, + OPCODE_BNEL = 0x15, + OPCODE_BLEZL = 0x16, + OPCODE_BGTZL = 0x17, + OPCODE_DADDI = 0x18, + OPCODE_DADDIU = 0x19, + OPCODE_LDL = 0x1A, + OPCODE_LDR = 0x1B, + OPCODE_MMI = 0x1C, // PS2 specific multimedia instructions + OPCODE_LQ = 0x1E, // PS2 specific 128-bit load + OPCODE_SQ = 0x1F, // PS2 specific 128-bit store + OPCODE_LB = 0x20, + OPCODE_LH = 0x21, + OPCODE_LWL = 0x22, + OPCODE_LW = 0x23, + OPCODE_LBU = 0x24, + OPCODE_LHU = 0x25, + OPCODE_LWR = 0x26, + OPCODE_LWU = 0x27, + OPCODE_SB = 0x28, + OPCODE_SH = 0x29, + OPCODE_SWL = 0x2A, + OPCODE_SW = 0x2B, + OPCODE_SDL = 0x2C, + OPCODE_SDR = 0x2D, + OPCODE_SWR = 0x2E, + OPCODE_CACHE = 0x2F, + OPCODE_LL = 0x30, + OPCODE_LWC1 = 0x31, + OPCODE_LWC2 = 0x32, + OPCODE_PREF = 0x33, + OPCODE_LLD = 0x34, + OPCODE_LDC1 = 0x35, + OPCODE_LDC2 = 0x36, + OPCODE_LD = 0x37, + OPCODE_SC = 0x38, + OPCODE_SWC1 = 0x39, + OPCODE_SWC2 = 0x3A, + OPCODE_SCD = 0x3C, + OPCODE_SDC1 = 0x3D, + OPCODE_SDC2 = 0x3E, + OPCODE_SD = 0x3F + }; + + // SPECIAL function codes + enum SpecialFunctions + { + SPECIAL_SLL = 0x00, + SPECIAL_SRL = 0x02, + SPECIAL_SRA = 0x03, + SPECIAL_SLLV = 0x04, + SPECIAL_SRLV = 0x06, + SPECIAL_SRAV = 0x07, + SPECIAL_JR = 0x08, + SPECIAL_JALR = 0x09, + SPECIAL_MOVZ = 0x0A, + SPECIAL_MOVN = 0x0B, + SPECIAL_SYSCALL = 0x0C, + SPECIAL_BREAK = 0x0D, + SPECIAL_SYNC = 0x0F, + SPECIAL_MFHI = 0x10, + SPECIAL_MTHI = 0x11, + SPECIAL_MFLO = 0x12, + SPECIAL_MTLO = 0x13, + SPECIAL_DSLLV = 0x14, + SPECIAL_DSRLV = 0x16, + SPECIAL_DSRAV = 0x17, + SPECIAL_MULT = 0x18, + SPECIAL_MULTU = 0x19, + SPECIAL_DIV = 0x1A, + SPECIAL_DIVU = 0x1B, + SPECIAL_ADD = 0x20, + SPECIAL_ADDU = 0x21, + SPECIAL_SUB = 0x22, + SPECIAL_SUBU = 0x23, + SPECIAL_AND = 0x24, + SPECIAL_OR = 0x25, + SPECIAL_XOR = 0x26, + SPECIAL_NOR = 0x27, + SPECIAL_MFSA = 0x28, + SPECIAL_MTSA = 0x29, + SPECIAL_SLT = 0x2A, + SPECIAL_SLTU = 0x2B, + SPECIAL_DADD = 0x2C, + SPECIAL_DADDU = 0x2D, + SPECIAL_DSUB = 0x2E, + SPECIAL_DSUBU = 0x2F, + SPECIAL_TGE = 0x30, + SPECIAL_TGEU = 0x31, + SPECIAL_TLT = 0x32, + SPECIAL_TLTU = 0x33, + SPECIAL_TEQ = 0x34, + SPECIAL_TNE = 0x36, + SPECIAL_DSLL = 0x38, + SPECIAL_DSRL = 0x3A, + SPECIAL_DSRA = 0x3B, + SPECIAL_DSLL32 = 0x3C, + SPECIAL_DSRL32 = 0x3E, + SPECIAL_DSRA32 = 0x3F + }; + + // REGIMM function codes + enum RegimmFunctions + { + REGIMM_BLTZ = 0x00, + REGIMM_BGEZ = 0x01, + REGIMM_BLTZL = 0x02, + REGIMM_BGEZL = 0x03, + REGIMM_TGEI = 0x08, + REGIMM_TGEIU = 0x09, + REGIMM_TLTI = 0x0A, + REGIMM_TLTIU = 0x0B, + REGIMM_TEQI = 0x0C, + REGIMM_TNEI = 0x0E, + REGIMM_BLTZAL = 0x10, + REGIMM_BGEZAL = 0x11, + REGIMM_BLTZALL = 0x12, + REGIMM_BGEZALL = 0x13, + REGIMM_MTSAB = 0x18, + REGIMM_MTSAH = 0x19 + }; + + // PS2-specific MMI function codes + enum MMIFunctions + { + MMI_MADD = 0x00, + MMI_MADDU = 0x01, + MMI_PLZCW = 0x04, + MMI_MMI0 = 0x08, + MMI_MMI2 = 0x09, + MMI_MFHI1 = 0x10, + MMI_MTHI1 = 0x11, + MMI_MFLO1 = 0x12, + MMI_MTLO1 = 0x13, + MMI_MULT1 = 0x18, + MMI_MULTU1 = 0x19, + MMI_DIV1 = 0x1A, + MMI_DIVU1 = 0x1B, + MMI_MADD1 = 0x20, + MMI_MADDU1 = 0x21, + MMI_MMI1 = 0x28, + MMI_MMI3 = 0x29, + MMI_PMFHL = 0x30, + MMI_PMTHL = 0x31, + MMI_PSLLH = 0x34, + MMI_PSRLH = 0x36, + MMI_PSRAH = 0x37, + MMI_PSLLW = 0x3C, + MMI_PSRLW = 0x3E, + MMI_PSRAW = 0x3F, + MMI_MSUB = 0x02, + MMI_MSUBU = 0x03 + }; + + // PS2-specific MMI0 function codes + enum MMI0Functions + { + MMI0_PADDW = 0x00, + MMI0_PSUBW = 0x01, + MMI0_PCGTW = 0x02, + MMI0_PMAXW = 0x03, + MMI0_PADDH = 0x04, + MMI0_PSUBH = 0x05, + MMI0_PCGTH = 0x06, + MMI0_PMAXH = 0x07, + MMI0_PADDB = 0x08, + MMI0_PSUBB = 0x09, + MMI0_PCGTB = 0x0A, + MMI0_PADDSW = 0x10, + MMI0_PSUBSW = 0x11, + MMI0_PEXTLW = 0x12, + MMI0_PPACW = 0x13, + MMI0_PADDSH = 0x14, + MMI0_PSUBSH = 0x15, + MMI0_PEXTLH = 0x16, + MMI0_PPACH = 0x17, + MMI0_PADDSB = 0x18, + MMI0_PSUBSB = 0x19, + MMI0_PEXTLB = 0x1A, + MMI0_PPACB = 0x1B, + MMI0_PEXT5 = 0x1E, + MMI0_PPAC5 = 0x1F + }; + + // PS2-specific MMI1 function codes + enum MMI1Functions + { + MMI1_PABSW = 0x01, + MMI1_PCEQW = 0x02, + MMI1_PMINW = 0x03, + MMI1_PADSBH = 0x04, + MMI1_PABSH = 0x05, + MMI1_PCEQH = 0x06, + MMI1_PMINH = 0x07, + MMI1_PCEQB = 0x0A, + MMI1_PADDUW = 0x10, + MMI1_PSUBUW = 0x11, + MMI1_PEXTUW = 0x12, + MMI1_PADDUH = 0x14, + MMI1_PSUBUH = 0x15, + MMI1_PEXTUH = 0x16, + MMI1_PADDUB = 0x18, + MMI1_PSUBUB = 0x19, + MMI1_PEXTUB = 0x1A, + MMI1_QFSRV = 0x1B + }; + + // PS2-specific MMI2 function codes + enum MMI2Functions + { + MMI2_PMADDW = 0x00, + MMI2_PSLLVW = 0x02, + MMI2_PSRLVW = 0x03, + MMI2_PMSUBW = 0x04, + MMI2_PMFHI = 0x08, + MMI2_PMFLO = 0x09, + MMI2_PINTH = 0x0A, + MMI2_PMULTW = 0x0C, + MMI2_PDIVW = 0x0D, + MMI2_PCPYLD = 0x0E, + MMI2_PAND = 0x12, + MMI2_PXOR = 0x13, + MMI2_PMADDH = 0x14, + MMI2_PHMADH = 0x15, + MMI2_PAND_ = 0x16, + MMI2_PXOR_ = 0x17, + MMI2_PMSUBH = 0x18, + MMI2_PHMSBH = 0x19, + MMI2_PEXEH = 0x1A, + MMI2_PREVH = 0x1B, + MMI2_PMULTH = 0x1C, + MMI2_PDIVBW = 0x1D, + MMI2_PEXEW = 0x1E, + MMI2_PROT3W = 0x1F + }; + + // PS2-specific MMI3 function codes + enum MMI3Functions + { + MMI3_PMADDUW = 0x00, + MMI3_PSRAVW = 0x03, + MMI3_PINTEH = 0x0A, + MMI3_PMULTUW = 0x0C, + MMI3_PDIVUW = 0x0D, + MMI3_PCPYUD = 0x0E, + MMI3_POR = 0x12, + MMI3_PNOR = 0x13, + MMI3_PEXCH = 0x1A, + MMI3_PCPYH = 0x1B, + MMI3_PEXCW = 0x1E, + }; + + // COP0 (System Control) function codes + enum Cop0Functions + { + COP0_MF = 0x00, + COP0_MT = 0x04, + COP0_CO = 0x10 // COProcessor commands + }; + + // COP0 CO (COProcessor) function codes + enum Cop0CoFunctions + { + COP0_CO_TLBR = 0x01, + COP0_CO_TLBWI = 0x02, + COP0_CO_TLBWR = 0x06, + COP0_CO_TLBP = 0x08, + COP0_CO_ERET = 0x18, + COP0_CO_EI = 0x38, + COP0_CO_DI = 0x39 + }; + + // COP1 (FPU) function codes + enum Cop1Functions + { + COP1_MF = 0x00, + COP1_CF = 0x02, + COP1_MT = 0x04, + COP1_CT = 0x06, + COP1_BC = 0x08, + COP1_S = 0x10, + COP1_W = 0x14, + COP1_BC_BCF = 0x00, + COP1_BC_BCT = 0x01 + }; + + // COP2 (VU0 macro) function codes + enum Cop2Functions + { + COP2_QMFC2 = 0x00, // Move From Coprocessor 2 (128-bit) + COP2_CFC2 = 0x02, // Move Control From Coprocessor 2 + COP2_QMTC2 = 0x04, // Move To Coprocessor 2 (128-bit) + COP2_CTC2 = 0x06, // Move Control To Coprocessor 2 + COP2_BC2 = 0x08, // Branch On Coprocessor 2 Condition + COP2_CO = 0x10, // COProcessor instructions (VU0 macro) + COP2_BCF = 0x00, + COP2_BCT = 0x01, + COP2_MFC2 = 0x02, + COP2_MTC2 = 0x0A + }; + + // VU0 macro instruction function codes (subset - there are many more) + enum VU0MacroFunctions + { + VU0_VADD = 0x00, + VU0_VSUB = 0x01, + VU0_VMUL = 0x02, + VU0_VDIV = 0x03, + VU0_VSQRT = 0x04, + VU0_VRSQRT = 0x05, + VU0_VMULQ = 0x06, + VU0_VIADD = 0x10, + VU0_VISUB = 0x11, + VU0_VIADDI = 0x12, + VU0_VIAND = 0x13, + VU0_VIOR = 0x14, + VU0_VILWR = 0x15, + VU0_VISWR = 0x16, + VU0_VCALLMS = 0x20, + VU0_VCALLMSR = 0x21 + }; + + // PMFHL functions (sa field) + enum PMFHLFunctions + { + PMFHL_LW = 0x00, + PMFHL_UW = 0x01, + PMFHL_SLW = 0x02, + PMFHL_LH = 0x03, + PMFHL_SH = 0x04 + }; + +// Instruction decoding helper macros +#define OPCODE(inst) ((inst >> 26) & 0x3F) +#define RS(inst) ((inst >> 21) & 0x1F) +#define RT(inst) ((inst >> 16) & 0x1F) +#define RD(inst) ((inst >> 11) & 0x1F) +#define SA(inst) ((inst >> 6) & 0x1F) +#define FUNCTION(inst) ((inst) & 0x3F) +#define IMMEDIATE(inst) ((inst) & 0xFFFF) +#define SIMMEDIATE(inst) ((int16_t)((inst) & 0xFFFF)) +#define TARGET(inst) ((inst) & 0x3FFFFFF) + +} // namespace ps2recomp + +#endif // PS2RECOMP_INSTRUCTIONS_H \ No newline at end of file diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h new file mode 100644 index 0000000..bc98127 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -0,0 +1,55 @@ +#ifndef PS2RECOMP_PS2_RECOMPILER_H +#define PS2RECOMP_PS2_RECOMPILER_H + +#include "ps2recomp/types.h" +#include "ps2recomp/elf_parser.h" +#include "ps2recomp/r5900_decoder.h" +#include "ps2recomp/code_generator.h" +#include "ps2recomp/config_manager.h" +#include +#include +#include +#include + +namespace ps2recomp +{ + + class PS2Recompiler + { + public: + PS2Recompiler(const std::string &configPath); + ~PS2Recompiler() = default; + + bool initialize(); + bool recompile(); + void generateOutput(); + + private: + ConfigManager m_configManager; + std::unique_ptr m_elfParser; + std::unique_ptr m_decoder; + std::unique_ptr m_codeGenerator; + RecompilerConfig m_config; + + std::vector m_functions; + std::vector m_symbols; + std::vector
m_sections; + std::vector m_relocations; + + std::unordered_map> m_decodedFunctions; + std::unordered_map m_stubFunctions; + std::unordered_map m_skipFunctions; + std::map m_generatedStubs; + + bool decodeFunction(Function &function); + bool shouldStubFunction(const std::string &name) const; + bool shouldSkipFunction(const std::string &name) const; + std::string generateRuntimeHeader(); + std::string generateStubFunction(const Function& function); + bool writeToFile(const std::string &path, const std::string &content); + std::filesystem::path getOutputPath(const Function &function) const; + }; + +} + +#endif \ No newline at end of file diff --git a/ps2xRecomp/include/ps2recomp/r5900_decoder.h b/ps2xRecomp/include/ps2recomp/r5900_decoder.h new file mode 100644 index 0000000..8456021 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/r5900_decoder.h @@ -0,0 +1,52 @@ +#ifndef PS2RECOMP_R5900_DECODER_H +#define PS2RECOMP_R5900_DECODER_H + +#include "ps2recomp/types.h" +#include "ps2recomp/instructions.h" +#include + +namespace ps2recomp +{ + + class R5900Decoder + { + public: + R5900Decoder(); + ~R5900Decoder(); + + Instruction decodeInstruction(uint32_t address, uint32_t rawInstruction); + + bool isBranchInstruction(const Instruction &inst) const; + bool isJumpInstruction(const Instruction &inst) const; + bool isCallInstruction(const Instruction &inst) const; + bool isReturnInstruction(const Instruction &inst) const; + bool isMMIInstruction(const Instruction &inst) const; + bool isVUInstruction(const Instruction &inst) const; + bool isStore(const Instruction &inst) const; + bool isLoad(const Instruction &inst) const; + bool hasDelaySlot(const Instruction &inst) const; + + uint32_t getBranchTarget(const Instruction &inst) const; + uint32_t getJumpTarget(const Instruction &inst) const; + + private: + void decodeRType(Instruction &inst) const; + void decodeIType(Instruction &inst) const; + void decodeJType(Instruction &inst) const; + + void decodeSpecial(Instruction &inst) const; + void decodeRegimm(Instruction &inst) const; + void decodeMMI(Instruction &inst) const; + void decodeCOP0(Instruction& inst) const; + void decodeCOP1(Instruction &inst) const; + void decodeCOP2(Instruction &inst) const; + void decodeMMI0(Instruction &inst) const; + void decodeMMI1(Instruction &inst) const; + void decodeMMI2(Instruction &inst) const; + void decodeMMI3(Instruction &inst) const; + void decodePMFHL(Instruction &inst) const; + }; + +} // namespace ps2recomp + +#endif // PS2RECOMP_R5900_DECODER_H \ No newline at end of file diff --git a/ps2xRecomp/include/ps2recomp/types.h b/ps2xRecomp/include/ps2recomp/types.h new file mode 100644 index 0000000..c14c843 --- /dev/null +++ b/ps2xRecomp/include/ps2recomp/types.h @@ -0,0 +1,139 @@ +#ifndef PS2RECOMP_TYPES_H +#define PS2RECOMP_TYPES_H + +#include +#include +#include +#include +#include + +namespace ps2recomp +{ + + // Instruction representation + struct Instruction + { + uint32_t address; + uint32_t opcode; + uint32_t rs; // Source register + uint32_t rt; // Target register + uint32_t rd; // Destination register + uint32_t sa; // Shift amount + uint32_t function; // Function code for R-type instructions + uint32_t immediate; // Immediate value for I-type instructions + uint32_t target; // Jump target for J-type instructions + uint32_t raw; // Raw instruction value + bool isMMI; // Is MMI instruction (PS2 specific) + bool isVU; // Is VU instruction (PS2 specific) + bool isBranch; // Is branch instruction + bool isJump; // Is jump instruction + bool isCall; // Is function call + bool isReturn; // Is return instruction + bool hasDelaySlot; // Has delay slot + bool isMultimedia; // PS2-specific multimedia operations + bool isStore; + bool isLoad; + uint8_t pmfhlVariation; + }; + + // Function information + struct Function + { + std::string name; + uint32_t start; + uint32_t end; + std::vector instructions; + std::vector callers; + std::vector callees; + bool isRecompiled; + bool isStub; + }; + + // Symbol information + struct Symbol + { + std::string name; + uint32_t address; + uint32_t size; + bool isFunction; + bool isImported; + bool isExported; + }; + + // Section information + struct Section + { + std::string name; + uint32_t address; + uint32_t size; + uint32_t offset; + bool isCode; + bool isData; + bool isBSS; + bool isReadOnly; + uint8_t *data; + }; + + // Relocation information + struct Relocation + { + uint32_t offset; + uint32_t info; + uint32_t symbol; + uint32_t type; + int32_t addend; + }; + + // Jump table entry + struct JumpTableEntry + { + uint32_t index; + uint32_t target; + }; + + // Jump table + struct JumpTable + { + uint32_t address; + uint32_t baseRegister; + std::vector entries; + }; + + // Control flow graph + struct CFGNode + { + uint32_t startAddress; + uint32_t endAddress; + std::vector instructions; + std::vector predecessors; + std::vector successors; + bool isJumpTarget; + bool hasJumpTable; + JumpTable jumpTable; + }; + + using CFG = std::unordered_map; + + // Function call + struct FunctionCall + { + uint32_t callerAddress; + uint32_t calleeAddress; + std::string calleeName; + }; + + // Recompiler configuration + struct RecompilerConfig + { + std::string inputPath; + std::string outputPath; + bool singleFileOutput; + std::vector stubFunctions; + std::vector skipFunctions; + std::unordered_map patches; + std::map stubImplementations; + }; + +} // namespace ps2recomp + +#endif // PS2RECOMP_TYPES_H \ No newline at end of file diff --git a/ps2xRecomp/src/code_generator.cpp b/ps2xRecomp/src/code_generator.cpp new file mode 100644 index 0000000..8d4f153 --- /dev/null +++ b/ps2xRecomp/src/code_generator.cpp @@ -0,0 +1,1381 @@ +#include "ps2recomp/code_generator.h" +#include "ps2recomp/instructions.h" +#include +#include +#include + +namespace ps2recomp +{ + CodeGenerator::CodeGenerator(const std::vector &symbols) + : m_symbols(symbols) + { + } + + std::string CodeGenerator::handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot) + { + std::stringstream ss; + + if (branchInst.opcode == OPCODE_J || branchInst.opcode == OPCODE_JAL) + { + // J/JAL instruction + if (branchInst.opcode == OPCODE_JAL) + { + // For JAL, set the return address + ss << " ctx->r31 = 0x" << std::hex << (branchInst.address + 8) << ";\n" + << std::dec; + } + + // Execute delay slot + ss << " " << translateInstruction(delaySlot) << "\n"; + + // Jump to target + uint32_t target = (branchInst.address & 0xF0000000) | (branchInst.target << 2); + Symbol *sym = findSymbolByAddress(target); + if (sym && sym->isFunction) + { + ss << " " << sym->name << "(rdram, ctx);\n"; + ss << " return;\n"; + } + else + { + ss << " // Jump to unknown target: 0x" << std::hex << target << std::dec << "\n"; + ss << " return;\n"; + } + } + else if (branchInst.opcode == OPCODE_SPECIAL && + (branchInst.function == SPECIAL_JR || branchInst.function == SPECIAL_JALR)) + { + // JR/JALR instruction + if (branchInst.function == SPECIAL_JALR) + { + // For JALR, set the return address + ss << " ctx->r" << branchInst.rd << " = 0x" << std::hex << (branchInst.address + 8) << ";\n" + << std::dec; + } + + // Execute delay slot + ss << " " << translateInstruction(delaySlot) << "\n"; + + // Jump to address in register + if (branchInst.rs == 31 && branchInst.function == SPECIAL_JR) + { + // JR $ra - likely a return + ss << " return;\n"; + } + else + { + ss << " LOOKUP_FUNC(ctx->r" << branchInst.rs << ")(rdram, ctx);\n"; + ss << " return;\n"; + } + } + else if (branchInst.isBranch) + { + std::string conditionStr; + + // Generate condition based on branch type + switch (branchInst.opcode) + { + case OPCODE_BEQ: + conditionStr = fmt::format("ctx->r{} == ctx->r{}", branchInst.rs, branchInst.rt); + break; + + case OPCODE_BNE: + conditionStr = fmt::format("ctx->r{} != ctx->r{}", branchInst.rs, branchInst.rt); + break; + + case OPCODE_BLEZ: + conditionStr = fmt::format("(int32_t)ctx->r{} <= 0", branchInst.rs); + break; + + case OPCODE_BGTZ: + conditionStr = fmt::format("(int32_t)ctx->r{} > 0", branchInst.rs); + break; + + case OPCODE_BEQL: + conditionStr = fmt::format("ctx->r{} == ctx->r{}", branchInst.rs, branchInst.rt); + break; + + case OPCODE_BNEL: + conditionStr = fmt::format("ctx->r{} != ctx->r{}", branchInst.rs, branchInst.rt); + break; + + case OPCODE_BLEZL: + conditionStr = fmt::format("(int32_t)ctx->r{} <= 0", branchInst.rs); + break; + + case OPCODE_BGTZL: + conditionStr = fmt::format("(int32_t)ctx->r{} > 0", branchInst.rs); + break; + + case OPCODE_REGIMM: + switch (branchInst.rt) + { + case REGIMM_BLTZ: + conditionStr = fmt::format("(int32_t)ctx->r{} < 0", branchInst.rs); + break; + + case REGIMM_BGEZ: + conditionStr = fmt::format("(int32_t)ctx->r{} >= 0", branchInst.rs); + break; + + case REGIMM_BLTZL: + conditionStr = fmt::format("(int32_t)ctx->r{} < 0", branchInst.rs); + break; + + case REGIMM_BGEZL: + conditionStr = fmt::format("(int32_t)ctx->r{} >= 0", branchInst.rs); + break; + + case REGIMM_BLTZAL: + conditionStr = fmt::format("(int32_t)ctx->r{} < 0", branchInst.rs); + ss << " ctx->r31 = 0x" << std::hex << (branchInst.address + 8) << ";\n" + << std::dec; + break; + + case REGIMM_BGEZAL: + conditionStr = fmt::format("(int32_t)ctx->r{} >= 0", branchInst.rs); + ss << " ctx->r31 = 0x" << std::hex << (branchInst.address + 8) << ";\n" + << std::dec; + break; + + case REGIMM_BLTZALL: + conditionStr = fmt::format("(int32_t)ctx->r{} < 0", branchInst.rs); + ss << " ctx->r31 = 0x" << std::hex << (branchInst.address + 8) << ";\n" + << std::dec; + break; + + case REGIMM_BGEZALL: + conditionStr = fmt::format("(int32_t)ctx->r{} >= 0", branchInst.rs); + ss << " ctx->r31 = 0x" << std::hex << (branchInst.address + 8) << ";\n" + << std::dec; + break; + + default: + conditionStr = "false"; + break; + } + break; + + default: + conditionStr = "false"; + break; + } + + int32_t offset = static_cast(branchInst.immediate) << 2; + uint32_t target = branchInst.address + 4 + offset; + + Symbol *sym = findSymbolByAddress(target); + std::string targetLabel; + + if (sym && sym->isFunction) + { + targetLabel = sym->name; + } + else + { + targetLabel = fmt::format("func_{:08X}", target); + } + + bool isLikely = (branchInst.opcode == OPCODE_BEQL || + branchInst.opcode == OPCODE_BNEL || + branchInst.opcode == OPCODE_BLEZL || + branchInst.opcode == OPCODE_BGTZL || + branchInst.rt == REGIMM_BLTZL || + branchInst.rt == REGIMM_BGEZL || + branchInst.rt == REGIMM_BLTZALL || + branchInst.rt == REGIMM_BGEZALL); + + if (isLikely) + { + // Likely branches only execute the delay slot if the branch is taken + ss << " if (" << conditionStr << ") {\n"; + ss << " " << translateInstruction(delaySlot) << "\n"; + ss << " " << targetLabel << "(rdram, ctx);\n"; + ss << " return;\n"; + ss << " }\n"; + } + else + { + // Regular branches always execute the delay slot + ss << " " << translateInstruction(delaySlot) << "\n"; + ss << " if (" << conditionStr << ") {\n"; + ss << " " << targetLabel << "(rdram, ctx);\n"; + ss << " return;\n"; + ss << " }\n"; + } + } + + return ss.str(); + } + + CodeGenerator::~CodeGenerator() = default; + + std::string CodeGenerator::generateMacroHeader() + { + std::stringstream ss; + + ss << "#ifndef PS2_RUNTIME_MACROS_H\n"; + ss << "#define PS2_RUNTIME_MACROS_H\n\n"; + ss << "#include \n"; + ss << "#include // For SSE/AVX intrinsics\n\n"; + + ss << "// Basic MIPS arithmetic operations\n"; + ss << "#define ADD32(a, b) ((uint32_t)((a) + (b)))\n"; + ss << "#define SUB32(a, b) ((uint32_t)((a) - (b)))\n"; + ss << "#define MUL32(a, b) ((uint32_t)((a) * (b)))\n"; + ss << "#define DIV32(a, b) ((uint32_t)((a) / (b)))\n"; + ss << "#define AND32(a, b) ((uint32_t)((a) & (b)))\n"; + ss << "#define OR32(a, b) ((uint32_t)((a) | (b)))\n"; + ss << "#define XOR32(a, b) ((uint32_t)((a) ^ (b)))\n"; + ss << "#define NOR32(a, b) ((uint32_t)(~((a) | (b))))\n"; + ss << "#define SLL32(a, b) ((uint32_t)((a) << (b)))\n"; + ss << "#define SRL32(a, b) ((uint32_t)((a) >> (b)))\n"; + ss << "#define SRA32(a, b) ((uint32_t)((int32_t)(a) >> (b)))\n"; + ss << "#define SLT32(a, b) ((uint32_t)((int32_t)(a) < (int32_t)(b) ? 1 : 0))\n"; + ss << "#define SLTU32(a, b) ((uint32_t)((a) < (b) ? 1 : 0))\n\n"; + + ss << "// PS2-specific 128-bit MMI operations\n"; + ss << "#define PS2_PEXTLW(a, b) _mm_unpacklo_epi32((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PEXTUW(a, b) _mm_unpackhi_epi32((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PEXTLH(a, b) _mm_unpacklo_epi16((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PEXTUH(a, b) _mm_unpackhi_epi16((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PEXTLB(a, b) _mm_unpacklo_epi8((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PEXTUB(a, b) _mm_unpackhi_epi8((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PADDW(a, b) _mm_add_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PSUBW(a, b) _mm_sub_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PMAXW(a, b) _mm_max_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PMINW(a, b) _mm_min_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PADDH(a, b) _mm_add_epi16((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PSUBH(a, b) _mm_sub_epi16((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PMAXH(a, b) _mm_max_epi16((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PMINH(a, b) _mm_min_epi16((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PADDB(a, b) _mm_add_epi8((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PSUBB(a, b) _mm_sub_epi8((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PAND(a, b) _mm_and_si128((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_POR(a, b) _mm_or_si128((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PXOR(a, b) _mm_xor_si128((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PNOR(a, b) _mm_xor_si128(_mm_or_si128((__m128i)(a), (__m128i)(b)), _mm_set1_epi32(0xFFFFFFFF))\n\n"; + + ss << "// PS2 VU (Vector Unit) operations\n"; + ss << "#define PS2_VADD(a, b) _mm_add_ps((__m128)(a), (__m128)(b))\n"; + ss << "#define PS2_VSUB(a, b) _mm_sub_ps((__m128)(a), (__m128)(b))\n"; + ss << "#define PS2_VMUL(a, b) _mm_mul_ps((__m128)(a), (__m128)(b))\n"; + ss << "#define PS2_VDIV(a, b) _mm_div_ps((__m128)(a), (__m128)(b))\n"; + ss << "#define PS2_VMULQ(a, q) _mm_mul_ps((__m128)(a), _mm_set1_ps(q))\n\n"; + + ss << "// Memory access helpers\n"; + ss << "#define READ8(addr) (*(uint8_t*)((rdram) + ((addr) & 0x1FFFFFF)))\n"; + ss << "#define READ16(addr) (*(uint16_t*)((rdram) + ((addr) & 0x1FFFFFF)))\n"; + ss << "#define READ32(addr) (*(uint32_t*)((rdram) + ((addr) & 0x1FFFFFF)))\n"; + ss << "#define READ64(addr) (*(uint64_t*)((rdram) + ((addr) & 0x1FFFFFF)))\n"; + ss << "#define READ128(addr) (*((__m128i*)((rdram) + ((addr) & 0x1FFFFFF))))\n"; + ss << "#define WRITE8(addr, val) (*(uint8_t*)((rdram) + ((addr) & 0x1FFFFFF)) = (val))\n"; + ss << "#define WRITE16(addr, val) (*(uint16_t*)((rdram) + ((addr) & 0x1FFFFFF)) = (val))\n"; + ss << "#define WRITE32(addr, val) (*(uint32_t*)((rdram) + ((addr) & 0x1FFFFFF)) = (val))\n"; + ss << "#define WRITE64(addr, val) (*(uint64_t*)((rdram) + ((addr) & 0x1FFFFFF)) = (val))\n"; + ss << "#define WRITE128(addr, val) (*((__m128i*)((rdram) + ((addr) & 0x1FFFFFF))) = (val))\n\n"; + + ss << "// Function lookup for indirect calls\n"; + ss << "#define LOOKUP_FUNC(addr) runtime->lookupFunction(addr)\n\n"; + + // Packed Compare Greater Than (PCGT) + ss << "#define PS2_PCGTW(a, b) _mm_cmpgt_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PCGTH(a, b) _mm_cmpgt_epi16((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PCGTB(a, b) _mm_cmpgt_epi8((__m128i)(a), (__m128i)(b))\n"; + + // Packed Compare Equal (PCEQ) + ss << "#define PS2_PCEQW(a, b) _mm_cmpeq_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PCEQH(a, b) _mm_cmpeq_epi16((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PCEQB(a, b) _mm_cmpeq_epi8((__m128i)(a), (__m128i)(b))\n"; + + // Packed Absolute (PABS) + ss << "#define PS2_PABSW(a) _mm_abs_epi32((__m128i)(a))\n"; + ss << "#define PS2_PABSH(a) _mm_abs_epi16((__m128i)(a))\n"; + ss << "#define PS2_PABSB(a) _mm_abs_epi8((__m128i)(a))\n"; + + // Packed Pack (PPAC) - Packs larger elements into smaller ones + ss << "#define PS2_PPACW(a, b) _mm_packs_epi32((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PPACH(a, b) _mm_packs_epi16((__m128i)(b), (__m128i)(a))\n"; + ss << "#define PS2_PPACB(a, b) _mm_packus_epi16(_mm_packs_epi32((__m128i)(b), (__m128i)(a)), _mm_setzero_si128())\n"; + + // Packed Interleave (PINT) + ss << "#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)))\n"; + ss << "#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)))\n"; + + // Packed Multiply-Add (PMADD) + ss << "#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1,0,3,2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1,0,3,2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0))))\n"; + + // Packed Variable Shifts + ss << "#define PS2_PSLLVW(a, b) _mm_custom_sllv_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PSRLVW(a, b) _mm_custom_srlv_epi32((__m128i)(a), (__m128i)(b))\n"; + ss << "#define PS2_PSRAVW(a, b) _mm_custom_srav_epi32((__m128i)(a), (__m128i)(b))\n"; + + // Helper function declarations for custom variable shifts + ss << "inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count) {\n"; + ss << " int32_t a_arr[4], count_arr[4], result[4];\n"; + ss << " _mm_storeu_si128((__m128i*)a_arr, a);\n"; + ss << " _mm_storeu_si128((__m128i*)count_arr, count);\n"; + ss << " for (int i = 0; i < 4; i++) {\n"; + ss << " result[i] = a_arr[i] << (count_arr[i] & 0x1F);\n"; + ss << " }\n"; + ss << " return _mm_loadu_si128((__m128i*)result);\n"; + ss << "}\n\n"; + + ss << "inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count) {\n"; + ss << " int32_t a_arr[4], count_arr[4], result[4];\n"; + ss << " _mm_storeu_si128((__m128i*)a_arr, a);\n"; + ss << " _mm_storeu_si128((__m128i*)count_arr, count);\n"; + ss << " for (int i = 0; i < 4; i++) {\n"; + ss << " result[i] = (uint32_t)a_arr[i] >> (count_arr[i] & 0x1F);\n"; + ss << " }\n"; + ss << " return _mm_loadu_si128((__m128i*)result);\n"; + ss << "}\n\n"; + + ss << "inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) {\n"; + ss << " int32_t a_arr[4], count_arr[4], result[4];\n"; + ss << " _mm_storeu_si128((__m128i*)a_arr, a);\n"; + ss << " _mm_storeu_si128((__m128i*)count_arr, count);\n"; + ss << " for (int i = 0; i < 4; i++) {\n"; + ss << " result[i] = a_arr[i] >> (count_arr[i] & 0x1F);\n"; + ss << " }\n"; + ss << " return _mm_loadu_si128((__m128i*)result);\n"; + ss << "}\n\n"; + + // PMFHL function implementations + ss << "#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(lo, hi)\n"; + ss << "#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(lo, hi)\n"; + ss << "#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(lo, hi)\n"; + ss << "#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0))\n"; + ss << "#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)), _MM_SHUFFLE(3,1,2,0))\n"; + + ss << "// FPU (COP1) operations\n"; + ss << "#define FPU_ADD_S(a, b) ((float)(a) + (float)(b))\n"; + ss << "#define FPU_SUB_S(a, b) ((float)(a) - (float)(b))\n"; + ss << "#define FPU_MUL_S(a, b) ((float)(a) * (float)(b))\n"; + ss << "#define FPU_DIV_S(a, b) ((float)(a) / (float)(b))\n"; + ss << "#define FPU_SQRT_S(a) sqrtf((float)(a))\n"; + ss << "#define FPU_ABS_S(a) fabsf((float)(a))\n"; + ss << "#define FPU_MOV_S(a) ((float)(a))\n"; + ss << "#define FPU_NEG_S(a) (-(float)(a))\n"; + ss << "#define FPU_ROUND_L_S(a) ((int64_t)roundf((float)(a)))\n"; + ss << "#define FPU_TRUNC_L_S(a) ((int64_t)(float)(a))\n"; + ss << "#define FPU_CEIL_L_S(a) ((int64_t)ceilf((float)(a)))\n"; + ss << "#define FPU_FLOOR_L_S(a) ((int64_t)floorf((float)(a)))\n"; + ss << "#define FPU_ROUND_W_S(a) ((int32_t)roundf((float)(a)))\n"; + ss << "#define FPU_TRUNC_W_S(a) ((int32_t)(float)(a))\n"; + ss << "#define FPU_CEIL_W_S(a) ((int32_t)ceilf((float)(a)))\n"; + ss << "#define FPU_FLOOR_W_S(a) ((int32_t)floorf((float)(a)))\n"; + ss << "#define FPU_CVT_S_W(a) ((float)(int32_t)(a))\n"; + ss << "#define FPU_CVT_S_L(a) ((float)(int64_t)(a))\n"; + ss << "#define FPU_CVT_W_S(a) ((int32_t)(float)(a))\n"; + ss << "#define FPU_CVT_L_S(a) ((int64_t)(float)(a))\n"; + ss << "#define FPU_C_F_S(a, b) (0)\n"; + ss << "#define FPU_C_UN_S(a, b) (isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_EQ_S(a, b) ((float)(a) == (float)(b))\n"; + ss << "#define FPU_C_UEQ_S(a, b) ((float)(a) == (float)(b) || isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_OLT_S(a, b) ((float)(a) < (float)(b))\n"; + ss << "#define FPU_C_ULT_S(a, b) ((float)(a) < (float)(b) || isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_OLE_S(a, b) ((float)(a) <= (float)(b))\n"; + ss << "#define FPU_C_ULE_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_SF_S(a, b) (0)\n"; + ss << "#define FPU_C_NGLE_S(a, b) (isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_SEQ_S(a, b) ((float)(a) == (float)(b))\n"; + ss << "#define FPU_C_NGL_S(a, b) ((float)(a) == (float)(b) || isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_LT_S(a, b) ((float)(a) < (float)(b))\n"; + ss << "#define FPU_C_NGE_S(a, b) ((float)(a) < (float)(b) || isnan((float)(a)) || isnan((float)(b)))\n"; + ss << "#define FPU_C_LE_S(a, b) ((float)(a) <= (float)(b))\n"; + ss << "#define FPU_C_NGT_S(a, b) ((float)(a) <= (float)(b) || isnan((float)(a)) || isnan((float)(b)))\n\n"; + + ss << "#endif // PS2_RUNTIME_MACROS_H\n"; + + return ss.str(); + } + + std::string CodeGenerator::generateFunction(const Function &function, const std::vector &instructions) + { + std::stringstream ss; + + ss << "#include \"ps2_runtime_macros.h\"\n"; + ss << "#include \"ps2_runtime.h\"\n\n"; + + ss << "// Function: " << function.name << "\n"; + ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; + ss << "void " << function.name << "(uint8_t* rdram, R5900Context* ctx) {\n"; + + ss << " // Local variables\n"; + ss << " uint32_t temp;\n"; + ss << " uint32_t branch_target;\n\n"; + + for (size_t i = 0; i < instructions.size(); ++i) + { + const Instruction &inst = instructions[i]; + + ss << " // 0x" << std::hex << inst.address << ": 0x" << inst.raw << std::dec << "\n"; + + // Check if this is a branch or jump with a delay slot + if (inst.hasDelaySlot && i + 1 < instructions.size()) + { + const Instruction &delaySlot = instructions[i + 1]; + ss << handleBranchDelaySlots(inst, delaySlot); + + // Skip the delay slot instruction as we've already handled it + ++i; + } + else + { + ss << " " << translateInstruction(inst) << "\n"; + } + } + + ss << "}\n"; + + return ss.str(); + } + + std::string CodeGenerator::translateInstruction(const Instruction &inst) + { + if (inst.isMMI) + { + return translateMMIInstruction(inst); + } + else if (inst.isVU) + { + return translateVUInstruction(inst); + } + + switch (inst.opcode) + { + case OPCODE_SPECIAL: + switch (inst.function) + { + case SPECIAL_SLL: + if (inst.rd == 0 && inst.rt == 0 && inst.sa == 0) + { + return "// NOP"; + } + return fmt::format("ctx->r{} = SLL32(ctx->r{}, {});", + inst.rd, inst.rt, inst.sa); + + case SPECIAL_SRL: + return fmt::format("ctx->r{} = SRL32(ctx->r{}, {});", + inst.rd, inst.rt, inst.sa); + + case SPECIAL_SRA: + return fmt::format("ctx->r{} = SRA32(ctx->r{}, {});", + inst.rd, inst.rt, inst.sa); + + case SPECIAL_SLLV: + return fmt::format("ctx->r{} = SLL32(ctx->r{}, ctx->r{} & 0x1F);", + inst.rd, inst.rt, inst.rs); + + case SPECIAL_SRLV: + return fmt::format("ctx->r{} = SRL32(ctx->r{}, ctx->r{} & 0x1F);", + inst.rd, inst.rt, inst.rs); + + case SPECIAL_SRAV: + return fmt::format("ctx->r{} = SRA32(ctx->r{}, ctx->r{} & 0x1F);", + inst.rd, inst.rt, inst.rs); + + case SPECIAL_MFHI: + return fmt::format("ctx->r{} = ctx->hi;", inst.rd); + + case SPECIAL_MTHI: + return fmt::format("ctx->hi = ctx->r{};", inst.rs); + + case SPECIAL_MFLO: + return fmt::format("ctx->r{} = ctx->lo;", inst.rd); + + case SPECIAL_MTLO: + return fmt::format("ctx->lo = ctx->r{};", inst.rs); + + case SPECIAL_MULT: + return fmt::format("{{ int64_t result = (int64_t)(int32_t)ctx->r{} * (int64_t)(int32_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case SPECIAL_MULTU: + return fmt::format("{{ uint64_t result = (uint64_t)ctx->r{} * (uint64_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case SPECIAL_DIV: + return fmt::format("{{ if (ctx->r{} != 0) {{ ctx->lo = (uint32_t)((int32_t)ctx->r{} / (int32_t)ctx->r{}); ctx->hi = (uint32_t)((int32_t)ctx->r{} % (int32_t)ctx->r{}); }} }}", + inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); + + case SPECIAL_DIVU: + return fmt::format("{{ if (ctx->r{} != 0) {{ ctx->lo = ctx->r{} / ctx->r{}; ctx->hi = ctx->r{} % ctx->r{}; }} }}", + inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); + + case SPECIAL_ADD: + case SPECIAL_ADDU: + return fmt::format("ctx->r{} = ADD32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_SUB: + case SPECIAL_SUBU: + return fmt::format("ctx->r{} = SUB32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_AND: + return fmt::format("ctx->r{} = AND32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_OR: + return fmt::format("ctx->r{} = OR32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_XOR: + return fmt::format("ctx->r{} = XOR32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_NOR: + return fmt::format("ctx->r{} = NOR32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_SLT: + return fmt::format("ctx->r{} = SLT32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case SPECIAL_SLTU: + return fmt::format("ctx->r{} = SLTU32(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + // PS2-specific instructions + case SPECIAL_MOVZ: + return fmt::format("if (ctx->r{} == 0) ctx->r{} = ctx->r{};", + inst.rt, inst.rd, inst.rs); + + case SPECIAL_MOVN: + return fmt::format("if (ctx->r{} != 0) ctx->r{} = ctx->r{};", + inst.rt, inst.rd, inst.rs); + + default: + return fmt::format("// Unhandled SPECIAL instruction: 0x{:X}", inst.function); + } + break; + + case OPCODE_ADDI: + case OPCODE_ADDIU: + if (inst.rt == 0) + { + return "// NOP (addiu $zero, ...)"; + } + return fmt::format("ctx->r{} = ADD32(ctx->r{}, 0x{:X});", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_SLTI: + return fmt::format("ctx->r{} = (int32_t)ctx->r{} < (int32_t)0x{:X} ? 1 : 0;", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_SLTIU: + return fmt::format("ctx->r{} = ctx->r{} < (uint32_t)0x{:X} ? 1 : 0;", + inst.rt, inst.rs, (uint32_t)(int16_t)inst.immediate); + + case OPCODE_ANDI: + return fmt::format("ctx->r{} = AND32(ctx->r{}, 0x{:X});", + inst.rt, inst.rs, inst.immediate); + + case OPCODE_ORI: + return fmt::format("ctx->r{} = OR32(ctx->r{}, 0x{:X});", + inst.rt, inst.rs, inst.immediate); + + case OPCODE_XORI: + return fmt::format("ctx->r{} = XOR32(ctx->r{}, 0x{:X});", + inst.rt, inst.rs, inst.immediate); + + case OPCODE_LUI: + return fmt::format("ctx->r{} = 0x{:X} << 16;", + inst.rt, inst.immediate); + + case OPCODE_LB: + return fmt::format("ctx->r{} = (int32_t)(int8_t)READ8(ADD32(ctx->r{}, 0x{:X}));", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_LH: + return fmt::format("ctx->r{} = (int32_t)(int16_t)READ16(ADD32(ctx->r{}, 0x{:X}));", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_LW: + return fmt::format("ctx->r{} = READ32(ADD32(ctx->r{}, 0x{:X}));", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_LBU: + return fmt::format("ctx->r{} = (uint32_t)READ8(ADD32(ctx->r{}, 0x{:X}));", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_LHU: + return fmt::format("ctx->r{} = (uint32_t)READ16(ADD32(ctx->r{}, 0x{:X}));", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_SB: + return fmt::format("WRITE8(ADD32(ctx->r{}, 0x{:X}), (uint8_t)ctx->r{});", + inst.rs, (int16_t)inst.immediate, inst.rt); + + case OPCODE_SH: + return fmt::format("WRITE16(ADD32(ctx->r{}, 0x{:X}), (uint16_t)ctx->r{});", + inst.rs, (int16_t)inst.immediate, inst.rt); + + case OPCODE_SW: + return fmt::format("WRITE32(ADD32(ctx->r{}, 0x{:X}), ctx->r{});", + inst.rs, (int16_t)inst.immediate, inst.rt); + + // PS2-specific 128-bit load/store + case OPCODE_LQ: + return fmt::format("ctx->r{} = (__m128i)READ128(ADD32(ctx->r{}, 0x{:X}));", + inst.rt, inst.rs, (int16_t)inst.immediate); + + case OPCODE_SQ: + return fmt::format("WRITE128(ADD32(ctx->r{}, 0x{:X}), (__m128i)ctx->r{});", + inst.rs, (int16_t)inst.immediate, inst.rt); + + // Special case for R5900 + case OPCODE_CACHE: + return "// CACHE instruction (ignored)"; + + case OPCODE_PREF: + return "// PREF instruction (ignored)"; + + case OPCODE_COP1: + return translateFPUInstruction(inst); + + case OPCODE_COP0: + return translateCOP0Instruction(inst); + + default: + return fmt::format("// Unhandled opcode: 0x{:X}", inst.opcode); + } + } + + std::string CodeGenerator::translateMMIInstruction(const Instruction &inst) + { + uint32_t function = inst.function; + + switch (function) + { + case MMI_MADD: + return fmt::format("{{ int64_t result = (int64_t)(((int64_t)ctx->hi << 32) | ctx->lo) + (int64_t)(int32_t)ctx->r{} * (int64_t)(int32_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case MMI_MADDU: + return fmt::format("{{ uint64_t result = (uint64_t)(((uint64_t)ctx->hi << 32) | ctx->lo) + (uint64_t)ctx->r{} * (uint64_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case MMI_PLZCW: + return fmt::format("ctx->r{} = __builtin_clz(ctx->r{});", + inst.rd, inst.rs); + + case MMI_MFHI1: + return fmt::format("ctx->r{} = ctx->hi;", inst.rd); + + case MMI_MTHI1: + return fmt::format("ctx->hi = ctx->r{};", inst.rs); + + case MMI_MFLO1: + return fmt::format("ctx->r{} = ctx->lo;", inst.rd); + + case MMI_MTLO1: + return fmt::format("ctx->lo = ctx->r{};", inst.rs); + + case MMI_MULT1: + return fmt::format("{{ int64_t result = (int64_t)(int32_t)ctx->r{} * (int64_t)(int32_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case MMI_MULTU1: + return fmt::format("{{ uint64_t result = (uint64_t)ctx->r{} * (uint64_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case MMI_DIV1: + return fmt::format("{{ if (ctx->r{} != 0) {{ ctx->lo = (uint32_t)((int32_t)ctx->r{} / (int32_t)ctx->r{}); ctx->hi = (uint32_t)((int32_t)ctx->r{} % (int32_t)ctx->r{}); }} }}", + inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); + + case MMI_DIVU1: + return fmt::format("{{ if (ctx->r{} != 0) {{ ctx->lo = ctx->r{} / ctx->r{}; ctx->hi = ctx->r{} % ctx->r{}; }} }}", + inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); + + case MMI_MADD1: + return fmt::format("{{ int64_t result = (int64_t)(((int64_t)ctx->hi << 32) | ctx->lo) + (int64_t)(int32_t)ctx->r{} * (int64_t)(int32_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + case MMI_MADDU1: + return fmt::format("{{ uint64_t result = (uint64_t)(((uint64_t)ctx->hi << 32) | ctx->lo) + (uint64_t)ctx->r{} * (uint64_t)ctx->r{}; ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", + inst.rs, inst.rt); + + // MMI0 functions (PADDW, PSUBW, etc.) + case MMI_MMI0: + switch (inst.sa) + { + case MMI0_PADDW: + return fmt::format("ctx->r{} = PS2_PADDW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PSUBW: + return fmt::format("ctx->r{} = PS2_PSUBW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PCGTW: + return fmt::format("ctx->r{} = PS2_PCGTW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PMAXW: + return fmt::format("ctx->r{} = PS2_PMAXW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PADDH: + return fmt::format("ctx->r{} = PS2_PADDH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PSUBH: + return fmt::format("ctx->r{} = PS2_PSUBH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PCGTH: + return fmt::format("ctx->r{} = PS2_PCGTH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PMAXH: + return fmt::format("ctx->r{} = PS2_PMAXH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PADDB: + return fmt::format("ctx->r{} = PS2_PADDB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PSUBB: + return fmt::format("ctx->r{} = PS2_PSUBB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PCGTB: + return fmt::format("ctx->r{} = PS2_PCGTB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PEXTLW: + return fmt::format("ctx->r{} = PS2_PEXTLW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PPACW: + return fmt::format("ctx->r{} = PS2_PPACW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PEXTLH: + return fmt::format("ctx->r{} = PS2_PEXTLH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PPACH: + return fmt::format("ctx->r{} = PS2_PPACH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PEXTLB: + return fmt::format("ctx->r{} = PS2_PEXTLB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI0_PPACB: + return fmt::format("// PS2_PPACB not implemented"); + + default: + return fmt::format("// Unhandled MMI0 function: 0x{:X}", inst.sa); + } + break; + + // MMI1 functions (PABSW, PCEQW, etc.) + case MMI_MMI1: + switch (inst.sa) + { + case MMI1_PADDUW: + return fmt::format("ctx->r{} = PS2_PADDW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PSUBUW: + return fmt::format("ctx->r{} = PS2_PSUBW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PEXTUW: + return fmt::format("ctx->r{} = PS2_PEXTUW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PADDUH: + return fmt::format("ctx->r{} = PS2_PADDH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PSUBUH: + return fmt::format("ctx->r{} = PS2_PSUBH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PEXTUH: + return fmt::format("ctx->r{} = PS2_PEXTUH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + case MMI1_PABSW: + return fmt::format("ctx->r{} = PS2_PABSW(ctx->r{});", + inst.rd, inst.rs); + + case MMI1_PABSH: + return fmt::format("ctx->r{} = PS2_PABSH(ctx->r{});", + inst.rd, inst.rs); + + case MMI1_PCEQW: + return fmt::format("ctx->r{} = PS2_PCEQW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PCEQH: + return fmt::format("ctx->r{} = PS2_PCEQH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PCEQB: + return fmt::format("ctx->r{} = PS2_PCEQB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PMINW: + return fmt::format("ctx->r{} = PS2_PMINW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PMINH: + return fmt::format("ctx->r{} = PS2_PMINH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PADDUB: + return fmt::format("ctx->r{} = PS2_PADDB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PSUBUB: + return fmt::format("ctx->r{} = PS2_PSUBB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_PEXTUB: + return fmt::format("ctx->r{} = PS2_PEXTUB(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI1_QFSRV: + return fmt::format("// PS2_QFSRV - Quadword Funnel Shift Right Variable"); + + default: + return fmt::format("// Unhandled MMI1 function: 0x{:X}", inst.sa); + } + break; + + // MMI2 functions (PMADDW, PSLLVW, etc.) + case MMI_MMI2: + switch (inst.sa) + { + case MMI2_PMADDW: + return fmt::format("ctx->r{} = PS2_PMADDW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI2_PSLLVW: + return fmt::format("ctx->r{} = PS2_PSLLVW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI2_PSRLVW: + return fmt::format("ctx->r{} = PS2_PSRLVW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI2_PINTH: + return fmt::format("ctx->r{} = PS2_PINTH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI2_PAND: + return fmt::format("ctx->r{} = PS2_PAND(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI2_PXOR: + return fmt::format("ctx->r{} = PS2_PXOR(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI2_PMULTW: + return fmt::format("// PS2_PMULTW - Packed Multiply Word"); + + case MMI2_PDIVW: + return fmt::format("// PS2_PDIVW - Packed Divide Word"); + + case MMI2_PCPYLD: + return fmt::format("// PS2_PCPYLD - Parallel Copy Lower Doubleword"); + + case MMI2_PMADDH: + return fmt::format("// PS2_PMADDH - Packed Multiply-Add Halfword"); + + case MMI2_PHMADH: + return fmt::format("// PS2_PHMADH - Packed Horizontal Multiply-Add Halfword"); + + case MMI2_PEXEH: + return fmt::format("// PS2_PEXEH - Parallel Exchange Even Halfword"); + + case MMI2_PREVH: + return fmt::format("// PS2_PREVH - Parallel Reverse Halfword"); + + case MMI2_PMULTH: + return fmt::format("// PS2_PMULTH - Packed Multiply Halfword"); + + case MMI2_PDIVBW: + return fmt::format("// PS2_PDIVBW - Packed Divide Broadcast Word"); + + case MMI2_PEXEW: + return fmt::format("// PS2_PEXEW - Parallel Exchange Even Word"); + + case MMI2_PROT3W: + return fmt::format("// PS2_PROT3W - Parallel Rotate 3 Words"); + + default: + return fmt::format("// Unhandled MMI2 function: 0x{:X}", inst.sa); + } + break; + + // MMI3 functions (PMADDUW, PSRAVW, etc.) + case MMI_MMI3: + switch (inst.sa) + { + case MMI3_POR: + return fmt::format("ctx->r{} = PS2_POR(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI3_PNOR: + return fmt::format("ctx->r{} = PS2_PNOR(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI3_PMADDUW: + return fmt::format("// PS2_PMADDUW - Packed Multiply-Add Unsigned Word"); + + case MMI3_PSRAVW: + return fmt::format("ctx->r{} = PS2_PSRAVW(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI3_PINTEH: + return fmt::format("ctx->r{} = PS2_PINTEH(ctx->r{}, ctx->r{});", + inst.rd, inst.rs, inst.rt); + + case MMI3_PMULTUW: + return fmt::format("// PS2_PMULTUW - Packed Multiply Unsigned Word"); + + case MMI3_PDIVUW: + return fmt::format("// PS2_PDIVUW - Packed Divide Unsigned Word"); + + case MMI3_PCPYUD: + return fmt::format("// PS2_PCPYUD - Parallel Copy Upper Doubleword"); + + case MMI3_PEXCH: + return fmt::format("// PS2_PEXCH - Parallel Exchange Center Halfword"); + + case MMI3_PCPYH: + return fmt::format("// PS2_PCPYH - Parallel Copy Halfword"); + + case MMI3_PEXCW: + return fmt::format("// PS2_PEXCW - Parallel Exchange Center Word"); + + default: + return fmt::format("// Unhandled MMI3 function: 0x{:X}", inst.sa); + } + break; + + // MMI_PMFHL functions + case MMI_PMFHL: + switch (inst.pmfhlVariation) + { + case PMFHL_LW: + return fmt::format("ctx->r{} = PS2_PMFHL_LW(ctx->hi, ctx->lo);", + inst.rd); + + case PMFHL_UW: + return fmt::format("ctx->r{} = PS2_PMFHL_UW(ctx->hi, ctx->lo);", + inst.rd); + + case PMFHL_SLW: + return fmt::format("ctx->r{} = PS2_PMFHL_SLW(ctx->hi, ctx->lo);", + inst.rd); + + case PMFHL_LH: + return fmt::format("ctx->r{} = PS2_PMFHL_LH(ctx->hi, ctx->lo);", + inst.rd); + + case PMFHL_SH: + return fmt::format("ctx->r{} = PS2_PMFHL_SH(ctx->hi, ctx->lo);", + inst.rd); + + default: + return fmt::format("// Unknown PMFHL variation: 0x{:X}", inst.pmfhlVariation); + } + break; + + default: + return fmt::format("// Unhandled MMI instruction: 0x{:X}", function); + } + } + + std::string CodeGenerator::translateVUInstruction(const Instruction &inst) + { + uint32_t rs = inst.rs; + + switch (rs) + { + case COP2_QMFC2: + return fmt::format("ctx->r{} = (__m128i)ctx->vu0_vf{};", + inst.rt, inst.rd); + + case COP2_CFC2: + if (inst.rd == 0) + { + return fmt::format("ctx->r{} = ctx->vu0_status;", inst.rt); + } + else + { + return fmt::format("// Unhandled CFC2 VU control register: {}", inst.rd); + } + + case COP2_QMTC2: + return fmt::format("ctx->vu0_vf{} = (__m128)ctx->r{};", + inst.rd, inst.rt); + + case COP2_CTC2: + if (inst.rd == 0) + { + return fmt::format("ctx->vu0_status = ctx->r{} & 0xFFFF;", inst.rt); + } + else + { + return fmt::format("// Unhandled CTC2 VU control register: {}", inst.rd); + } + + case COP2_BC2: + return fmt::format("// VU branch instruction not implemented"); + + case COP2_CO: + // VU0 macro instructions + switch (inst.function) + { + case VU0_VADD: + return fmt::format("ctx->vu0_vf{} = PS2_VADD(ctx->vu0_vf{}, ctx->vu0_vf{});", + inst.rd, inst.rs, inst.rt); + + case VU0_VSUB: + return fmt::format("ctx->vu0_vf{} = PS2_VSUB(ctx->vu0_vf{}, ctx->vu0_vf{});", + inst.rd, inst.rs, inst.rt); + + case VU0_VMUL: + return fmt::format("ctx->vu0_vf{} = PS2_VMUL(ctx->vu0_vf{}, ctx->vu0_vf{});", + inst.rd, inst.rs, inst.rt); + + case VU0_VDIV: + return fmt::format("ctx->vu0_vf{} = PS2_VDIV(ctx->vu0_vf{}, ctx->vu0_vf{});", + inst.rd, inst.rs, inst.rt); + + case VU0_VMULQ: + return fmt::format("ctx->vu0_vf{} = PS2_VMULQ(ctx->vu0_vf{}, ctx->vu0_q);", + inst.rd, inst.rs); + + default: + return fmt::format("// Unhandled VU0 macro instruction: 0x{:X}", inst.function); + } + break; + + default: + return fmt::format("// Unhandled VU instruction format: 0x{:X}", rs); + } + } + + std::string CodeGenerator::translateFPUInstruction(const Instruction &inst) + { + uint32_t rs = inst.rs; // Format field + uint32_t ft = inst.rt; // FPU source register + uint32_t fs = inst.rd; // FPU source register + uint32_t fd = inst.sa; // FPU destination register + uint32_t function = inst.function; + + // For MFC1/MTC1/CFC1/CTC1, the GPR is in rt and the FPR is in rd(fs) + if (rs == COP1_MF) + { + return fmt::format("ctx->r{} = *(uint32_t*)&ctx->f{};", + ft, fs); + } + else if (rs == COP1_MT) + { + return fmt::format("*(uint32_t*)&ctx->f{} = ctx->r{};", + fs, ft); + } + else if (rs == COP1_CF) + { + // CFC1 - Move Control From FPU + if (fs == 31) // FCR31 contains status/control + { + return fmt::format("ctx->r{} = ctx->fcr31;", ft); + } + else if (fs == 0) // FCR0 is the FPU implementation register + { + return fmt::format("ctx->r{} = 0x00000000; // Emulated FPU implementation", ft); + } + else + { + return fmt::format("ctx->r{} = 0; // Unimplemented FCR{}", ft, fs); + } + } + else if (rs == COP1_CT) + { + // CTC1 - Move Control To FPU + if (fs == 31) // FCR31 contains status/control + { + return fmt::format("ctx->fcr31 = ctx->r{} & 0x0183FFFF;", ft); // Apply bit mask for valid bits + } + else + { + return fmt::format("// CTC1 to FCR{} ignored", fs); + } + } + else if (rs == COP1_BC) + { + // FPU Branch instructions - handled by delay slot code + return fmt::format("// FPU branch instruction - handled elsewhere"); + } + else if (rs == COP1_S) + { + // Single precision operations + switch (function) + { + case 0x00: // ADD.S + return fmt::format("ctx->f{} = FPU_ADD_S(ctx->f{}, ctx->f{});", + fd, fs, ft); + + case 0x01: // SUB.S + return fmt::format("ctx->f{} = FPU_SUB_S(ctx->f{}, ctx->f{});", + fd, fs, ft); + + case 0x02: // MUL.S + return fmt::format("ctx->f{} = FPU_MUL_S(ctx->f{}, ctx->f{});", + fd, fs, ft); + + case 0x03: // DIV.S + return fmt::format("ctx->f{} = FPU_DIV_S(ctx->f{}, ctx->f{});", + fd, fs, ft); + + case 0x04: // SQRT.S + return fmt::format("ctx->f{} = FPU_SQRT_S(ctx->f{});", + fd, fs); + + case 0x05: // ABS.S + return fmt::format("ctx->f{} = FPU_ABS_S(ctx->f{});", + fd, fs); + + case 0x06: // MOV.S + return fmt::format("ctx->f{} = FPU_MOV_S(ctx->f{});", + fd, fs); + + case 0x07: // NEG.S + return fmt::format("ctx->f{} = FPU_NEG_S(ctx->f{});", + fd, fs); + + case 0x08: // ROUND.L.S + return fmt::format("*(int64_t*)&ctx->f{} = FPU_ROUND_L_S(ctx->f{});", + fd, fs); + + case 0x09: // TRUNC.L.S + return fmt::format("*(int64_t*)&ctx->f{} = FPU_TRUNC_L_S(ctx->f{});", + fd, fs); + + case 0x0A: // CEIL.L.S + return fmt::format("*(int64_t*)&ctx->f{} = FPU_CEIL_L_S(ctx->f{});", + fd, fs); + + case 0x0B: // FLOOR.L.S + return fmt::format("*(int64_t*)&ctx->f{} = FPU_FLOOR_L_S(ctx->f{});", + fd, fs); + + case 0x0C: // ROUND.W.S + return fmt::format("*(int32_t*)&ctx->f{} = FPU_ROUND_W_S(ctx->f{});", + fd, fs); + + case 0x0D: // TRUNC.W.S + return fmt::format("*(int32_t*)&ctx->f{} = FPU_TRUNC_W_S(ctx->f{});", + fd, fs); + + case 0x0E: // CEIL.W.S + return fmt::format("*(int32_t*)&ctx->f{} = FPU_CEIL_W_S(ctx->f{});", + fd, fs); + + case 0x0F: // FLOOR.W.S + return fmt::format("*(int32_t*)&ctx->f{} = FPU_FLOOR_W_S(ctx->f{});", + fd, fs); + + // Continuing the COP1_S switch case from the previous code: + + case 0x21: // CVT.D.S - Convert Single to Double (not commonly used on PS2) + return fmt::format("// CVT.D.S not implemented (PS2 rarely uses double precision)"); + + case 0x24: // CVT.W.S - Convert Single to Word + return fmt::format("*(int32_t*)&ctx->f{} = FPU_CVT_W_S(ctx->f{});", + fd, fs); + + case 0x25: // CVT.L.S - Convert Single to Long + return fmt::format("*(int64_t*)&ctx->f{} = FPU_CVT_L_S(ctx->f{});", + fd, fs); + + case 0x30: // C.F.S - Compare False + return fmt::format("ctx->fcr31 = (ctx->fcr31 & ~0x800000); // Clear condition bit", + fs, ft); + + case 0x31: // C.UN.S - Compare Unordered + return fmt::format("ctx->fcr31 = (FPU_C_UN_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + case 0x32: // C.EQ.S - Compare Equal + return fmt::format("ctx->fcr31 = (FPU_C_EQ_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + case 0x33: // C.UEQ.S - Compare Unordered or Equal + return fmt::format("ctx->fcr31 = (FPU_C_UEQ_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + case 0x34: // C.OLT.S - Compare Ordered Less Than + return fmt::format("ctx->fcr31 = (FPU_C_OLT_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + case 0x35: // C.ULT.S - Compare Unordered or Less Than + return fmt::format("ctx->fcr31 = (FPU_C_ULT_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + case 0x36: // C.OLE.S - Compare Ordered Less Than or Equal + return fmt::format("ctx->fcr31 = (FPU_C_OLE_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + case 0x37: // C.ULE.S - Compare Unordered or Less Than or Equal + return fmt::format("ctx->fcr31 = (FPU_C_ULE_S(ctx->f{}, ctx->f{})) ? (ctx->fcr31 | 0x800000) : (ctx->fcr31 & ~0x800000);", + fs, ft); + + default: + return fmt::format("// Unhandled FPU.S instruction: function 0x{:X}", function); + } + } + else if (rs == COP1_W) + { + // Word format operations + switch (function) + { + case 0x20: // CVT.S.W - Convert Word to Single + return fmt::format("ctx->f{} = FPU_CVT_S_W(*(int32_t*)&ctx->f{});", + fd, fs); + + default: + return fmt::format("// Unhandled FPU.W instruction: function 0x{:X}", function); + } + } + + return fmt::format("// Unhandled FPU instruction: format 0x{:X}, function 0x{:X}", rs, function); + } + + std::string CodeGenerator::translateCOP0Instruction(const Instruction &inst) + { + uint32_t rs = inst.rs; // Format field + uint32_t rt = inst.rt; // GPR register + uint32_t rd = inst.rd; // COP0 register + + if (rs == COP0_MF) + { + // MFC0 - Move From COP0 + switch (rd) + { + case 12: // Status register + return fmt::format("ctx->r{} = ctx->cop0_status;", rt); + + case 13: // Cause register + return fmt::format("ctx->r{} = ctx->cop0_cause;", rt); + + case 14: // EPC register + return fmt::format("ctx->r{} = ctx->cop0_epc;", rt); + + default: + return fmt::format("ctx->r{} = 0; // Unimplemented COP0 register {}", rt, rd); + } + } + else if (rs == COP0_MT) + { + // MTC0 - Move To COP0 + switch (rd) + { + case 12: // Status register + return fmt::format("ctx->cop0_status = ctx->r{};", rt); + + case 13: // Cause register + return fmt::format("ctx->cop0_cause = ctx->r{};", rt); + + case 14: // EPC register + return fmt::format("ctx->cop0_epc = ctx->r{};", rt); + + default: + return fmt::format("// MTC0 to register {} ignored", rd); + } + } + else if (rs == COP0_CO) + { + // COP0 co-processor operations + uint32_t function = inst.function; + + switch (function) + { + case COP0_CO_ERET: + return fmt::format("// ERET instruction - Return from exception\n ctx->pc = ctx->cop0_epc;\n return;"); + + case COP0_CO_TLBR: + return fmt::format("// TLBR instruction - TLB Read (ignored)"); + + case COP0_CO_TLBWI: + return fmt::format("// TLBWI instruction - TLB Write Indexed (ignored)"); + + case COP0_CO_TLBWR: + return fmt::format("// TLBWR instruction - TLB Write Random (ignored)"); + + case COP0_CO_TLBP: + return fmt::format("// TLBP instruction - TLB Probe (ignored)"); + + case COP0_CO_EI: + return fmt::format("// EI instruction - Enable Interrupts\n ctx->cop0_status |= 0x1;"); + + case COP0_CO_DI: + return fmt::format("// DI instruction - Disable Interrupts\n ctx->cop0_status &= ~0x1;"); + + default: + return fmt::format("// Unhandled COP0 CO-OP: 0x{:X}", function); + } + } + + return fmt::format("// Unhandled COP0 instruction: format 0x{:X}", rs); + } + + std::string CodeGenerator::generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, + const std::vector &entries) + { + std::stringstream ss; + + uint32_t indexReg = inst.rs; + + ss << "switch (ctx->r" << indexReg << ") {\n"; + + for (const auto &entry : entries) + { + ss << " case " << entry.index << ": {\n"; + + Symbol *sym = findSymbolByAddress(entry.target); + if (sym && sym->isFunction) + { + ss << " " << sym->name << "(rdram, ctx);\n"; + } + else + { + ss << " func_" << std::hex << entry.target << std::dec << "(rdram, ctx);\n"; + } + + ss << " return;\n"; + ss << " }\n"; + } + + ss << " default:\n"; + ss << " // Unknown jump table target\n"; + ss << " return;\n"; + ss << "}\n"; + + return ss.str(); + } + + Symbol *CodeGenerator::findSymbolByAddress(uint32_t address) + { + for (auto &symbol : m_symbols) + { + if (symbol.address == address) + { + return &symbol; + } + } + + return nullptr; + } +}; \ No newline at end of file diff --git a/ps2xRecomp/src/config_manager.cpp b/ps2xRecomp/src/config_manager.cpp new file mode 100644 index 0000000..9ccea6b --- /dev/null +++ b/ps2xRecomp/src/config_manager.cpp @@ -0,0 +1,126 @@ +#include "ps2recomp/config_manager.h" +#include +#include +#include +#include + +namespace ps2recomp +{ + + ConfigManager::ConfigManager(const std::string &configPath) + : m_configPath(configPath) + { + } + + ConfigManager::~ConfigManager() = default; + + RecompilerConfig ConfigManager::loadConfig() + { + RecompilerConfig config; + + try + { + auto data = toml::parse(m_configPath); + + config.inputPath = toml::find(data, "general", "input"); + config.outputPath = toml::find(data, "general", "output"); + config.singleFileOutput = toml::find(data, "general", "single_file_output"); + + config.stubFunctions = toml::find>(data, "general", "stubs"); + config.skipFunctions = toml::find>(data, "general", "skip"); + + if (data.contains("patches") && data.at("patches").is_table()) + { + const auto &patches = toml::find(data, "patches"); + + if (patches.contains("instructions") && patches.at("instructions").is_array()) + { + const auto &instPatches = toml::find(patches, "instructions").as_array(); + for (const auto &patch : instPatches) + { + if (patch.contains("address") && patch.contains("value")) + { + uint32_t address = std::stoul(toml::find(patch, "address"), nullptr, 0); + std::string value = toml::find(patch, "value"); + config.patches[address] = value; + } + } + } + } + + if (data.contains("stub_implementations") && data.at("stub_implementations").is_table()) + { + const auto &stubImpls = toml::find(data, "stub_implementations"); + for (const auto &item : stubImpls.as_table()) + { + const std::string &funcName = item.first; + const std::string &implementation = toml::find(stubImpls, funcName); + config.stubImplementations[funcName] = implementation; + } + } + } + catch (const std::exception &e) + { + std::cerr << "Error parsing configuration file: " << e.what() << std::endl; + throw; + } + + return config; + } + + void ConfigManager::saveConfig(const RecompilerConfig &config) + { + toml::value data; + + toml::table general; + general["input"] = config.inputPath; + general["output"] = config.outputPath; + general["single_file_output"] = config.singleFileOutput; + data["general"] = general; + + toml::array stubs; + for (const auto &stub : config.stubFunctions) + { + stubs.push_back(stub); + } + data["stubs"] = stubs; + + toml::array skips; + for (const auto &skip : config.skipFunctions) + { + skips.push_back(skip); + } + data["skip"] = skips; + + toml::table patches; + toml::array instPatches; + for (const auto &patch : config.patches) + { + toml::table p; + p["address"] = "0x" + std::to_string(patch.first); + p["value"] = patch.second; + instPatches.push_back(p); + } + patches["instructions"] = instPatches; + data["patches"] = patches; + + if (!config.stubImplementations.empty()) + { + toml::table stubImpls; + for (const auto &impl : config.stubImplementations) + { + stubImpls[impl.first] = impl.second; + } + data["stub_implementations"] = stubImpls; + } + + std::ofstream file(m_configPath); + if (!file) + { + throw std::runtime_error("Failed to open file for writing: " + m_configPath); + } + + file << data; + } + +} // namespace ps2recomp \ No newline at end of file diff --git a/ps2xRecomp/src/elf_parser.cpp b/ps2xRecomp/src/elf_parser.cpp new file mode 100644 index 0000000..b993a48 --- /dev/null +++ b/ps2xRecomp/src/elf_parser.cpp @@ -0,0 +1,291 @@ +#include "ps2recomp/elf_parser.h" +#include +#include + +namespace ps2recomp +{ + + ElfParser::ElfParser(const std::string &filePath) + : m_filePath(filePath), m_elf(new ELFIO::elfio()) + { + } + + bool ElfParser::isExecutableSection(const ELFIO::section *section) const + { + return (section->get_flags() & ELFIO::SHF_EXECINSTR) != 0; + } + + bool ElfParser::isDataSection(const ELFIO::section *section) const + { + return (section->get_flags() & ELFIO::SHF_ALLOC) != 0 && + !(section->get_flags() & ELFIO::SHF_EXECINSTR); + } + + std::vector ElfParser::extractFunctions() + { + std::vector functions; + + for (const auto &symbol : m_symbols) + { + if (symbol.isFunction && symbol.size > 0) + { + Function func; + func.name = symbol.name; + func.start = symbol.address; + func.end = symbol.address + symbol.size; + func.isRecompiled = false; + func.isStub = false; + + functions.push_back(func); + } + } + + std::sort(functions.begin(), functions.end(), + [](const Function &a, const Function &b) + { return a.start < b.start; }); + + return functions; + } + + std::vector ElfParser::extractSymbols() + { + return m_symbols; + } + + std::vector
ElfParser::getSections() + { + return m_sections; + } + + std::vector ElfParser::getRelocations() + { + return m_relocations; + } + + bool ElfParser::isValidAddress(uint32_t address) const + { + for (const auto §ion : m_sections) + { + if (address >= section.address && address < (section.address + section.size)) + { + return true; + } + } + + return false; + } + + uint32_t ElfParser::readWord(uint32_t address) const + { + for (const auto §ion : m_sections) + { + if (address >= section.address && address < (section.address + section.size)) + { + if (section.data) + { + uint32_t offset = address - section.address; + return *reinterpret_cast(section.data + offset); + } + } + } + + throw std::runtime_error("Invalid address for readWord: " + std::to_string(address)); + } + + uint8_t *ElfParser::getSectionData(const std::string §ionName) + { + for (const auto §ion : m_sections) + { + if (section.name == sectionName) + { + return section.data; + } + } + + return nullptr; + } + + uint32_t ElfParser::getSectionAddress(const std::string §ionName) + { + for (const auto §ion : m_sections) + { + if (section.name == sectionName) + { + return section.address; + } + } + + return 0; + } + + uint32_t ElfParser::getSectionSize(const std::string §ionName) + { + for (const auto §ion : m_sections) + { + if (section.name == sectionName) + { + return section.size; + } + } + + return 0; + } + + ElfParser::~ElfParser() = default; + + bool ElfParser::parse() + { + if (!m_elf->load(m_filePath)) + { + std::cerr << "Error: Could not load ELF file: " << m_filePath << std::endl; + return false; + } + + // Check if this is a PS2 ELF (MIPS R5900) + if (m_elf->get_machine() != ELFIO::EM_MIPS) + { + std::cerr << "Error: Not a MIPS ELF file" << std::endl; + return false; + } + + loadSections(); + loadSymbols(); + loadRelocations(); + + return true; + } + + void ElfParser::loadSections() + { + m_sections.clear(); + + ELFIO::Elf_Half sec_num = m_elf->sections.size(); + + for (ELFIO::Elf_Half i = 0; i < sec_num; ++i) + { + ELFIO::section *psec = m_elf->sections[i]; + + Section section; + section.name = psec->get_name(); + section.address = psec->get_address(); + section.size = psec->get_size(); + section.offset = psec->get_offset(); + section.isCode = isExecutableSection(psec); + section.isData = isDataSection(psec); + section.isBSS = (psec->get_type() == ELFIO::SHT_NOBITS); + section.isReadOnly = !(psec->get_flags() & ELFIO::SHF_WRITE); + + if (psec->get_size() > 0 && psec->get_type() != ELFIO::SHT_NOBITS) + { + section.data = (uint8_t *)psec->get_data(); + } + else + { + section.data = nullptr; + } + + m_sections.push_back(section); + } + } + + void ElfParser::loadSymbols() + { + m_symbols.clear(); + + for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i) + { + ELFIO::section *psec = m_elf->sections[i]; + + if (psec->get_type() == ELFIO::SHT_SYMTAB || psec->get_type() == ELFIO::SHT_DYNSYM) + { + ELFIO::symbol_section_accessor symbols(*m_elf, psec); + + ELFIO::Elf_Xword sym_num = symbols.get_symbols_num(); + + ELFIO::section *pstrSec = m_elf->sections[psec->get_link()]; + ELFIO::string_section_accessor strings(pstrSec); + + for (ELFIO::Elf_Xword j = 0; j < sym_num; ++j) + { + 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; + + symbols.get_symbol(j, name, value, size, bind, type, section_index, other); + + // Skip empty symbols or those with invalid section index + if (name.empty() || section_index == ELFIO::SHN_UNDEF) + { + continue; + } + + Symbol symbol; + symbol.name = name; + symbol.address = static_cast(value); + symbol.size = static_cast(size); + symbol.isFunction = (type == ELFIO::STT_FUNC); + symbol.isImported = (bind == ELFIO::STB_GLOBAL && section_index == ELFIO::SHN_UNDEF); + symbol.isExported = (bind == ELFIO::STB_GLOBAL && section_index != ELFIO::SHN_UNDEF); + + m_symbols.push_back(symbol); + } + } + } + } + + void ElfParser::loadRelocations() + { + m_relocations.clear(); + + for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i) + { + ELFIO::section *psec = m_elf->sections[i]; + + if (psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA) + { + ELFIO::relocation_section_accessor relocs(*m_elf, psec); + + ELFIO::section *symSec = m_elf->sections[psec->get_link()]; + ELFIO::symbol_section_accessor symbols(*m_elf, symSec); + + ELFIO::section *strSec = m_elf->sections[symSec->get_link()]; + + ELFIO::string_section_accessor strings(strSec); + + for (ELFIO::Elf_Xword j = 0; j < relocs.get_entries_num(); ++j) + { + ELFIO::Elf64_Addr offset; + ELFIO::Elf_Word symbol; + ELFIO::Elf_Word type; + ELFIO::Elf_Sxword addend; + + // Always use the 5-parameter version + if (psec->get_type() == ELFIO::SHT_REL) + { + // Pass addend even for REL sections + relocs.get_entry(j, offset, symbol, type, addend); + // Reset addend for REL sections since it's not part of the section + addend = 0; + } + else + { + relocs.get_entry(j, offset, symbol, type, addend); + } + + Relocation reloc; + reloc.offset = static_cast(offset); + reloc.info = (symbol << 8) | (type & 0xFF); + reloc.symbol = symbol; + reloc.type = type; + reloc.addend = static_cast(addend); + + m_relocations.push_back(reloc); + } + } + } + } +} \ No newline at end of file diff --git a/ps2xRecomp/src/main.cpp b/ps2xRecomp/src/main.cpp new file mode 100644 index 0000000..b64c2dc --- /dev/null +++ b/ps2xRecomp/src/main.cpp @@ -0,0 +1,50 @@ +#include "ps2recomp/ps2_recompiler.h" +#include +#include + +using namespace ps2recomp; + +void printUsage() +{ + std::cout << "PS2Recomp - A static recompiler for PlayStation 2 ELF files\n"; + std::cout << "Usage: ps2recomp \n"; + std::cout << " config.toml: Configuration file for the recompiler\n"; +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + printUsage(); + return 1; + } + + std::string configPath = argv[1]; + + try + { + PS2Recompiler recompiler(configPath); + + if (!recompiler.initialize()) + { + std::cerr << "Failed to initialize recompiler\n"; + return 1; + } + + if (!recompiler.recompile()) + { + std::cerr << "Recompilation failed\n"; + return 1; + } + + recompiler.generateOutput(); + + std::cout << "Recompilation completed successfully\n"; + return 0; + } + catch (const std::exception &e) + { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} \ No newline at end of file diff --git a/ps2xRecomp/src/ps2_recompiler.cpp b/ps2xRecomp/src/ps2_recompiler.cpp new file mode 100644 index 0000000..f7f0815 --- /dev/null +++ b/ps2xRecomp/src/ps2_recompiler.cpp @@ -0,0 +1,388 @@ +#include "ps2recomp/ps2_recompiler.h" +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace ps2recomp +{ + + PS2Recompiler::PS2Recompiler(const std::string &configPath) + : m_configManager(configPath) + { + } + + bool PS2Recompiler::initialize() + { + try + { + m_config = m_configManager.loadConfig(); + + for (const auto &name : m_config.stubFunctions) + { + m_stubFunctions[name] = true; + } + + for (const auto &name : m_config.skipFunctions) + { + m_skipFunctions[name] = true; + } + + m_elfParser = std::make_unique(m_config.inputPath); + if (!m_elfParser->parse()) + { + std::cerr << "Failed to parse ELF file: " << m_config.inputPath << std::endl; + return false; + } + + m_functions = m_elfParser->extractFunctions(); + m_symbols = m_elfParser->extractSymbols(); + m_sections = m_elfParser->getSections(); + m_relocations = m_elfParser->getRelocations(); + + m_decoder = std::make_unique(); + m_codeGenerator = std::make_unique(m_symbols); + + fs::create_directories(m_config.outputPath); + + return true; + } + catch (const std::exception &e) + { + std::cerr << "Error during initialization: " << e.what() << std::endl; + return false; + } + } + + bool PS2Recompiler::recompile() + { + try + { + std::cout << "Recompiling " << m_functions.size() << " functions..." << std::endl; + + std::string runtimeHeader = generateRuntimeHeader(); + fs::path runtimeHeaderPath = fs::path(m_config.outputPath) / "ps2_runtime_macros.h"; + + writeToFile(runtimeHeaderPath.string(), runtimeHeader); + + size_t processedCount = 0; + for (auto &function : m_functions) + { + if (shouldSkipFunction(function.name)) + { + std::cout << "Skipping function: " << function.name << std::endl; + continue; + } + + if (shouldStubFunction(function.name)) + { + std::cout << "Stubbing function: " << function.name << std::endl; + function.isStub = true; + // TODO: Generate stub implementation + continue; + } + + if (shouldStubFunction(function.name)) + { + std::cout << "Stubbing function: " << function.name << std::endl; + function.isStub = true; + function.isRecompiled = true; // we're generating code for it + + // Generate stub implementation and store it + std::string stubCode = generateStubFunction(function); + m_generatedStubs[function.start] = stubCode; + + continue; + } + + if (!decodeFunction(function)) + { + std::cerr << "Failed to decode function: " << function.name << std::endl; + return false; + } + + function.isRecompiled = true; +#if _DEBUG + processedCount++; + if (processedCount % 100 == 0) + { + std::cout << "Processed " << processedCount << " functions." << std::endl; + } +#endif + } + + std::cout << "Recompilation completed successfully." << std::endl; + return true; + } + catch (const std::exception &e) + { + std::cerr << "Error during recompilation: " << e.what() << std::endl; + return false; + } + } + + void PS2Recompiler::generateOutput() + { + try + { + if (m_config.singleFileOutput) + { + std::stringstream combinedOutput; + + combinedOutput << "#include \"ps2_runtime_macros.h\"\n"; + combinedOutput << "#include \"ps2_runtime.h\"\n\n"; + + for (const auto &function : m_functions) + { + if (!function.isRecompiled) + { + continue; + } + + if (function.isStub) + { + combinedOutput << m_generatedStubs[function.start] << "\n\n"; + } + else + { + const auto &instructions = m_decodedFunctions[function.start]; + std::string code = m_codeGenerator->generateFunction(function, instructions); + combinedOutput << code << "\n\n"; + } + } + + fs::path outputPath = fs::path(m_config.outputPath) / "recompiled.cpp"; + writeToFile(outputPath.string(), combinedOutput.str()); + std::cout << "Wrote combined output to: " << outputPath << std::endl; + } + else + { + for (const auto &function : m_functions) + { + if (!function.isRecompiled || function.isStub) + { + continue; + } + + std::string code; + if (function.isStub) + { + code = m_generatedStubs[function.start]; + } + else + { + const auto &instructions = m_decodedFunctions[function.start]; + code = m_codeGenerator->generateFunction(function, instructions); + } + + fs::path outputPath = getOutputPath(function); + fs::create_directories(outputPath.parent_path()); + writeToFile(outputPath.string(), code); + } + + std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl; + } + } + catch (const std::exception &e) + { + std::cerr << "Error during output generation: " << e.what() << std::endl; + } + } + + bool PS2Recompiler::decodeFunction(Function &function) + { + std::vector instructions; + + uint32_t start = function.start; + uint32_t end = function.end; + + for (uint32_t address = start; address < end; address += 4) + { + try + { + if (!m_elfParser->isValidAddress(address)) + { + std::cerr << "Invalid address: 0x" << std::hex << address << std::dec + << " in function: " << function.name << std::endl; + return false; + } + + uint32_t rawInstruction = m_elfParser->readWord(address); + + auto patchIt = m_config.patches.find(address); + if (patchIt != m_config.patches.end()) + { + rawInstruction = std::stoul(patchIt->second, nullptr, 0); + std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl; + } + + Instruction inst = m_decoder->decodeInstruction(address, rawInstruction); + + instructions.push_back(inst); + } + catch (const std::exception &e) + { + std::cerr << "Error decoding instruction at 0x" << std::hex << address << std::dec + << " in function: " << function.name << ": " << e.what() << std::endl; + return false; + } + } + + m_decodedFunctions[function.start] = instructions; + + return true; + } + + bool PS2Recompiler::shouldStubFunction(const std::string &name) const + { + return m_stubFunctions.find(name) != m_stubFunctions.end(); + } + + bool PS2Recompiler::shouldSkipFunction(const std::string &name) const + { + return m_skipFunctions.find(name) != m_skipFunctions.end(); + } + + std::string PS2Recompiler::generateRuntimeHeader() + { + return m_codeGenerator->generateMacroHeader(); + } + + std::string PS2Recompiler::generateStubFunction(const Function &function) + { + std::stringstream ss; + + ss << "#include \"ps2_runtime_macros.h\"\n"; + ss << "#include \"ps2_runtime.h\"\n\n"; + + ss << "// STUB FUNCTION: " << function.name << "\n"; + ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; + ss << "void " << function.name << "(uint8_t* rdram, R5900Context* ctx) {\n"; + + auto stubImpl = m_config.stubImplementations.find(function.name); + if (stubImpl != m_config.stubImplementations.end()) + { + ss << " // Custom stub implementation\n"; + ss << " " << stubImpl->second << "\n"; + } + else + { + // Default stub implementation based on common functions + if (function.name == "printf" || function.name == "fprintf" || + function.name == "sprintf" || function.name == "snprintf") + { + ss << " // Format string is in $a0 (r4), args start at $a1 (r5)\n"; + ss << " #ifdef PS2_RECOMP_DEBUG\n"; + ss << " printf(\"Stub called: " << function.name << " with format at 0x%08X\\n\", ctx->r[4]);\n"; + ss << " #endif\n"; + ss << " // Return success (number of characters, but we'll just say 1)\n"; + ss << " ctx->r[2] = 1;\n"; + } + else if (function.name == "malloc" || function.name == "calloc" || + function.name == "realloc" || function.name == "memalign") + { + ss << " // Memory allocation - Would call the runtime's allocation system\n"; + ss << " uint32_t size = ctx->r[4]; // Size is in $a0\n"; + ss << " #ifdef PS2_RECOMP_DEBUG\n"; + ss << " printf(\"Stub called: " << function.name << " size=%u\\n\", size);\n"; + ss << " #endif\n"; + ss << " // In a real implementation, call runtime->allocateMemory(size)\n"; + ss << " ctx->r[2] = 0; // Return NULL for now - replace with actual allocation in real implementation\n"; + } + else if (function.name == "free") + { + ss << " // Free memory - Would call the runtime's free system\n"; + ss << " uint32_t ptr = ctx->r[4]; // Pointer is in $a0\n"; + ss << " #ifdef PS2_RECOMP_DEBUG\n"; + ss << " printf(\"Stub called: free(0x%08X)\\n\", ptr);\n"; + ss << " #endif\n"; + ss << " // In a real implementation, call runtime->freeMemory(ptr)\n"; + } + else if (function.name == "memcpy" || function.name == "memmove") + { + ss << " // Memory copy\n"; + ss << " uint32_t dst = ctx->r[4]; // Destination in $a0\n"; + ss << " uint32_t src = ctx->r[5]; // Source in $a1\n"; + ss << " uint32_t size = ctx->r[6]; // Size in $a2\n"; + ss << " #ifdef PS2_RECOMP_DEBUG\n"; + ss << " printf(\"Stub called: " << function.name << "(dst=0x%08X, src=0x%08X, size=%u)\\n\", dst, src, size);\n"; + ss << " #endif\n"; + ss << " // Only copy if within valid memory range\n"; + ss << " if (dst < 0x2000000 && src < 0x2000000 && dst + size < 0x2000000 && src + size < 0x2000000) {\n"; + ss << " memcpy(rdram + dst, rdram + src, size);\n"; + ss << " }\n"; + ss << " ctx->r[2] = dst; // Return destination pointer\n"; + } + else if (function.name == "memset") + { + ss << " // Memory set\n"; + ss << " uint32_t dst = ctx->r[4]; // Destination in $a0\n"; + ss << " uint8_t value = (uint8_t)ctx->r[5]; // Value in $a1\n"; + ss << " uint32_t size = ctx->r[6]; // Size in $a2\n"; + ss << " #ifdef PS2_RECOMP_DEBUG\n"; + ss << " printf(\"Stub called: memset(dst=0x%08X, value=%u, size=%u)\\n\", dst, value, size);\n"; + ss << " #endif\n"; + ss << " // Only set if within valid memory range\n"; + ss << " if (dst < 0x2000000 && dst + size < 0x2000000) {\n"; + ss << " memset(rdram + dst, value, size);\n"; + ss << " }\n"; + ss << " ctx->r[2] = dst; // Return destination pointer\n"; + } + else + { + // Generic stub for unknown functions + ss << " // Default stub implementation\n"; + ss << " #ifdef PS2_RECOMP_DEBUG\n"; + ss << " printf(\"Stub function called: " << function.name << " at PC=0x%08X\\n\", ctx->pc);\n"; + ss << " #endif\n"; + ss << " // Default return value (0)\n"; + ss << " ctx->r[2] = 0;\n"; + } + } + + ss << "}\n"; + + return ss.str(); + } + + bool PS2Recompiler::writeToFile(const std::string &path, const std::string &content) + { + std::ofstream file(path); + if (!file) + { + std::cerr << "Failed to open file for writing: " << path << std::endl; + return false; + } + + file << content; + file.close(); + + return true; + } + + std::filesystem::path PS2Recompiler::getOutputPath(const Function &function) const + { + std::string safeName = function.name; + + std::replace_if(safeName.begin(), safeName.end(), [](char c) + { return c == '/' || c == '\\' || c == ':' || c == '*' || + c == '?' || c == '"' || c == '<' || c == '>' || + c == '|' || c == '$'; }, '_'); + + if (safeName.empty()) + { + std::stringstream ss; + ss << "func_" << std::hex << function.start; + safeName = ss.str(); + } + + std::filesystem::path outputPath = m_config.outputPath; + outputPath /= safeName + ".cpp"; + + return outputPath; + } +} \ No newline at end of file diff --git a/ps2xRecomp/src/r5900_decoder.cpp b/ps2xRecomp/src/r5900_decoder.cpp new file mode 100644 index 0000000..39a8fc7 --- /dev/null +++ b/ps2xRecomp/src/r5900_decoder.cpp @@ -0,0 +1,636 @@ +#include "ps2recomp/r5900_decoder.h" + +namespace ps2recomp +{ + + R5900Decoder::R5900Decoder() + { + } + + R5900Decoder::~R5900Decoder() + { + } + + Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction) + { + Instruction inst; + + inst.address = address; + inst.raw = rawInstruction; + inst.opcode = OPCODE(rawInstruction); + inst.rs = RS(rawInstruction); + inst.rt = RT(rawInstruction); + inst.rd = RD(rawInstruction); + inst.sa = SA(rawInstruction); + inst.function = FUNCTION(rawInstruction); + inst.immediate = IMMEDIATE(rawInstruction); + inst.target = TARGET(rawInstruction); + + inst.isMMI = false; + inst.isVU = false; + inst.isBranch = false; + inst.isJump = false; + inst.isCall = false; + inst.isReturn = false; + inst.hasDelaySlot = false; + inst.isMultimedia = false; + + switch (inst.opcode) + { + case OPCODE_SPECIAL: + decodeSpecial(inst); + break; + + case OPCODE_REGIMM: + decodeRegimm(inst); + break; + + case OPCODE_J: + decodeJType(inst); + inst.isJump = true; + inst.hasDelaySlot = true; + break; + + case OPCODE_JAL: + decodeJType(inst); + inst.isJump = true; + inst.isCall = true; + inst.hasDelaySlot = true; + break; + + case OPCODE_BEQ: + case OPCODE_BNE: + case OPCODE_BLEZ: + case OPCODE_BGTZ: + case OPCODE_BEQL: + case OPCODE_BNEL: + case OPCODE_BLEZL: + case OPCODE_BGTZL: + decodeIType(inst); + inst.isBranch = true; + inst.hasDelaySlot = true; + break; + + case OPCODE_ADDI: + case OPCODE_ADDIU: + case OPCODE_SLTI: + case OPCODE_SLTIU: + case OPCODE_ANDI: + case OPCODE_ORI: + case OPCODE_XORI: + case OPCODE_LUI: + decodeIType(inst); + break; + + case OPCODE_MMI: + decodeMMI(inst); + inst.isMMI = true; + inst.isMultimedia = true; + break; + + case OPCODE_LQ: + decodeIType(inst); + inst.isLoad = true; + inst.isMultimedia = true; // 128-bit load + break; + + case OPCODE_SQ: + decodeIType(inst); + inst.isStore = true; + inst.isMultimedia = true; // 128-bit store + break; + + case OPCODE_LB: + case OPCODE_LH: + case OPCODE_LWL: + case OPCODE_LW: + case OPCODE_LBU: + case OPCODE_LHU: + case OPCODE_LWR: + case OPCODE_LWU: + case OPCODE_LD: + case OPCODE_LDL: + case OPCODE_LDR: + case OPCODE_LL: + case OPCODE_LWC1: + case OPCODE_LDC1: + case OPCODE_LWC2: + case OPCODE_LDC2: + decodeIType(inst); + inst.isLoad = true; + break; + + case OPCODE_SB: + case OPCODE_SH: + case OPCODE_SWL: + case OPCODE_SW: + case OPCODE_SWR: + case OPCODE_SD: + case OPCODE_SDL: + case OPCODE_SDR: + case OPCODE_SC: + case OPCODE_SWC1: + case OPCODE_SDC1: + case OPCODE_SWC2: + case OPCODE_SDC2: + case OPCODE_SCD: + decodeIType(inst); + inst.isStore = true; + break; + + case OPCODE_COP0: + decodeCOP0(inst); + break; + + case OPCODE_COP1: + decodeCOP1(inst); + break; + + case OPCODE_COP2: + decodeCOP2(inst); + inst.isVU = true; + inst.isMultimedia = true; + break; + + case OPCODE_PREF: + case OPCODE_CACHE: + // Prefetch and cache operations + decodeIType(inst); + break; + + default: + // Default to I-type for most other instructions + decodeIType(inst); + break; + } + + return inst; + } + + void R5900Decoder::decodeRType(Instruction &inst) const + { + // R-type instructions already have all fields set correctly + } + + void R5900Decoder::decodeIType(Instruction &inst) const + { + // I-type instructions already have all fields set correctly + } + + void R5900Decoder::decodeJType(Instruction &inst) const + { + // J-type instructions already have all fields set correctly + } + + void R5900Decoder::decodeSpecial(Instruction &inst) const + { + switch (inst.function) + { + case SPECIAL_JR: + inst.isJump = true; + inst.hasDelaySlot = true; + if (inst.rs == 31) + { + // jr $ra is typically a return + inst.isReturn = true; + } + break; + + case SPECIAL_JALR: + inst.isJump = true; + inst.isCall = true; + inst.hasDelaySlot = true; + break; + + case SPECIAL_SYSCALL: + case SPECIAL_BREAK: + // Special handling for syscall/break + break; + + case SPECIAL_MFHI: + case SPECIAL_MTHI: + case SPECIAL_MFLO: + case SPECIAL_MTLO: + // HI/LO register operations + break; + + case SPECIAL_MULT: + case SPECIAL_MULTU: + case SPECIAL_DIV: + case SPECIAL_DIVU: + // Multiplication and division operations + inst.isMultimedia = true; + break; + + case SPECIAL_ADD: + case SPECIAL_ADDU: + case SPECIAL_SUB: + case SPECIAL_SUBU: + case SPECIAL_AND: + case SPECIAL_OR: + case SPECIAL_XOR: + case SPECIAL_NOR: + case SPECIAL_SLT: + case SPECIAL_SLTU: + // ALU operations + break; + + case SPECIAL_SLL: + case SPECIAL_SRL: + case SPECIAL_SRA: + case SPECIAL_SLLV: + case SPECIAL_SRLV: + case SPECIAL_SRAV: + // Shift operations + break; + + // 64-bit specific operations + case SPECIAL_DADD: + case SPECIAL_DADDU: + case SPECIAL_DSUB: + case SPECIAL_DSUBU: + case SPECIAL_DSLL: + case SPECIAL_DSRL: + case SPECIAL_DSRA: + case SPECIAL_DSLL32: + case SPECIAL_DSRL32: + case SPECIAL_DSRA32: + case SPECIAL_DSLLV: + case SPECIAL_DSRLV: + case SPECIAL_DSRAV: + // 64-bit operations + break; + + default: + // Other R-type instructions + break; + } + } + + void R5900Decoder::decodeRegimm(Instruction &inst) const + { + uint32_t rt = inst.rt; + + switch (rt) + { + case REGIMM_BLTZ: + case REGIMM_BGEZ: + case REGIMM_BLTZL: + case REGIMM_BGEZL: + inst.isBranch = true; + inst.hasDelaySlot = true; + break; + + case REGIMM_BLTZAL: + case REGIMM_BGEZAL: + case REGIMM_BLTZALL: + case REGIMM_BGEZALL: + inst.isBranch = true; + inst.isCall = true; + inst.hasDelaySlot = true; + break; + + case REGIMM_MTSAB: + case REGIMM_MTSAH: + // PS2 specific MTSAB/MTSAH instructions (for QMFC2/QMTC2) + inst.isMultimedia = true; + break; + + default: + // Other REGIMM instructions + break; + } + } + + void R5900Decoder::decodeMMI(Instruction &inst) const + { + inst.isMMI = true; + inst.isMultimedia = true; + + // The function field is actually determined by the lowest 6 bits (as in R-type) + uint32_t mmiFunction = inst.function; + + // Categorize the MMI instruction type based on the rs field + uint32_t rs = inst.rs; + + switch (mmiFunction) + { + case MMI_MADD: + case MMI_MADDU: + case MMI_MADD1: + case MMI_MADDU1: + // Multiply-add operations + break; + + case MMI_PLZCW: + // Count leading zeros/ones + break; + + case MMI_MFHI1: + case MMI_MTHI1: + case MMI_MFLO1: + case MMI_MTLO1: + // Secondary HI/LO register operations + break; + + case MMI_MULT1: + case MMI_MULTU1: + case MMI_DIV1: + case MMI_DIVU1: + // Secondary multiply/divide operations + break; + + case MMI_MMI0: + // First set of multimedia instructions + decodeMMI0(inst); + break; + + case MMI_MMI1: + // Second set of multimedia instructions + decodeMMI1(inst); + break; + + case MMI_MMI2: + // Third set of multimedia instructions + decodeMMI2(inst); + break; + + case MMI_MMI3: + // Fourth set of multimedia instructions + decodeMMI3(inst); + break; + + case MMI_PMFHL: + // PMFHL variations based on sa field + decodePMFHL(inst); + break; + + case MMI_PMTHL: + // PMTHL operations + break; + + case MMI_PSLLH: + case MMI_PSRLH: + case MMI_PSRAH: + case MMI_PSLLW: + case MMI_PSRLW: + case MMI_PSRAW: + // SIMD shift operations + break; + + default: + // Unknown or unsupported MMI function + break; + } + } + + void R5900Decoder::decodeCOP0(Instruction &inst) const + { + // COP0 (System Control) instructions + uint32_t rs = inst.rs; // Actually the cop0 format field + + if (rs == COP0_MF) + { + // Move From COP0 register + } + else if (rs == COP0_MT) + { + // Move To COP0 register + } + else if (rs == COP0_CO) + { + // COProcessor operations + uint32_t function = inst.function; + + if (function == COP0_CO_ERET) + { + inst.isReturn = true; + inst.hasDelaySlot = true; + } + else if (function == COP0_CO_TLBR || + function == COP0_CO_TLBWI || + function == COP0_CO_TLBWR || + function == COP0_CO_TLBP) + { + // TLB operations + } + else if (function == COP0_CO_EI || function == COP0_CO_DI) + { + // Enable/Disable Interrupts + } + } + } + + void R5900Decoder::decodeCOP1(Instruction &inst) const + { + // COP1 (FPU) instructions + uint32_t rs = inst.rs; // The FPU format field + + if (rs == COP1_MF) + { + // Move From FPU register + } + else if (rs == COP1_CF) + { + // Move From FPU Control register + } + else if (rs == COP1_MT) + { + // Move To FPU register + } + else if (rs == COP1_CT) + { + // Move To FPU Control register + } + else if (rs == COP1_BC) + { + // FPU Branch on Condition + uint32_t rt = inst.rt; // The condition code + if (rt == COP1_BC_BCF || rt == COP1_BC_BCT) + { + inst.isBranch = true; + inst.hasDelaySlot = true; + } + } + else if (rs == COP1_S || rs == COP1_W) + { + // FPU operations (single precision or word) + uint32_t function = inst.function; + // Decode specific FPU operation based on function field + } + } + + void R5900Decoder::decodeCOP2(Instruction &inst) const + { + // COP2 (VU0 macro mode) instructions + inst.isVU = true; + inst.isMultimedia = true; + + uint32_t rs = inst.rs; // The VU0 format field + + if (rs == COP2_MFC2) + { + // Move From COP2 register + } + else if (rs == COP2_CFC2) + { + // Move From COP2 Control register + } + else if (rs == COP2_MTC2) + { + // Move To COP2 register + } + else if (rs == COP2_CTC2) + { + // Move To COP2 Control register + } + else if (rs == COP2_BCF || rs == COP2_BCT) + { + // VU0 Branch on Condition + inst.isBranch = true; + inst.hasDelaySlot = true; + } + else + { + // VU0 vector operations + // These would need detailed decoding based on function field + } + } + + void R5900Decoder::decodeMMI0(Instruction &inst) const + { + // Decode MMI0 subfunctions (based on function field) + uint32_t subFunction = inst.function & 0x3F; + + // The implementation would set appropriate flags or properties based on the specific MMI0 operation + } + + void R5900Decoder::decodeMMI1(Instruction &inst) const + { + // Decode MMI1 subfunctions (based on function field) + uint32_t subFunction = inst.function & 0x3F; + + // The implementation would set appropriate flags or properties based on the specific MMI1 operation + } + + void R5900Decoder::decodeMMI2(Instruction &inst) const + { + // Decode MMI2 subfunctions (based on function field) + uint32_t subFunction = inst.function & 0x3F; + + // The implementation would set appropriate flags or properties based on the specific MMI2 operation + } + + void R5900Decoder::decodeMMI3(Instruction &inst) const + { + // Decode MMI3 subfunctions (based on function field) + uint32_t subFunction = inst.function & 0x3F; + + // The implementation would set appropriate flags or properties based on the specific MMI3 operation + } + + void R5900Decoder::decodePMFHL(Instruction &inst) const + { + // PMFHL has different variations based on the sa field + uint32_t saField = inst.sa; + + switch (saField) + { + case PMFHL_LW: + case PMFHL_UW: + case PMFHL_SLW: + case PMFHL_LH: + case PMFHL_SH: + // Set the appropriate flag for the PMFHL variation + inst.pmfhlVariation = saField; + break; + + default: + // Unknown PMFHL variation + inst.pmfhlVariation = 0xFF; + break; + } + } + + bool R5900Decoder::isBranchInstruction(const Instruction &inst) const + { + return inst.isBranch; + } + + bool R5900Decoder::isJumpInstruction(const Instruction &inst) const + { + return inst.isJump; + } + + bool R5900Decoder::isCallInstruction(const Instruction &inst) const + { + return inst.isCall; + } + + bool R5900Decoder::isReturnInstruction(const Instruction &inst) const + { + return inst.isReturn; + } + + bool R5900Decoder::isMMIInstruction(const Instruction &inst) const + { + return inst.isMMI; + } + + bool R5900Decoder::isVUInstruction(const Instruction &inst) const + { + return inst.isVU; + } + + bool R5900Decoder::isStore(const Instruction &inst) const + { + return inst.isStore; + } + + bool R5900Decoder::isLoad(const Instruction &inst) const + { + return inst.isLoad; + } + + bool R5900Decoder::hasDelaySlot(const Instruction &inst) const + { + return inst.hasDelaySlot; + } + + uint32_t R5900Decoder::getBranchTarget(const Instruction &inst) const + { + if (!inst.isBranch) + { + return 0; + } + + // Calculate branch target: PC + 4 + (sign-extended immediate << 2) + int32_t offset = static_cast(inst.immediate) << 2; + return inst.address + 4 + offset; + } + + uint32_t R5900Decoder::getJumpTarget(const Instruction &inst) const + { + if (!inst.isJump) + { + return 0; + } + + if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) + { + // J/JAL: target is in the lower 26 bits, shifted left by 2 + // and combined with the upper 4 bits of PC + 4 + uint32_t pc_upper = (inst.address + 4) & 0xF0000000; + return pc_upper | (inst.target << 2); + } + else if (inst.opcode == OPCODE_SPECIAL && + (inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR)) + { + // JR/JALR: target is in the rs register (can't be determined statically) + return 0; + } + + return 0; + } + +} // namespace ps2recomp \ No newline at end of file diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt new file mode 100644 index 0000000..1757913 --- /dev/null +++ b/ps2xRuntime/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.20) + +project(PS2Runtime VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(ps2_runtime STATIC + src/ps2_memory.cpp + src/ps2_runtime.cpp +) + +target_include_directories(ps2_runtime PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +install(TARGETS ps2_runtime + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +install(DIRECTORY include/ + DESTINATION include +) \ No newline at end of file diff --git a/ps2xRuntime/Readme.md b/ps2xRuntime/Readme.md new file mode 100644 index 0000000..f89f3e5 --- /dev/null +++ b/ps2xRuntime/Readme.md @@ -0,0 +1,40 @@ +# Runtime Library +The runtime library provides the execution environment for recompiled code, including: + +* Memory management (32MB main RAM, scratchpad, etc.) +* Register context (128-bit GPRs, VU0 registers, etc.) +* Function table for dynamic linking +* Basic PS2 system call stubs + +## Adding Custom Function Implementations +You can add custom implementations for PS2 system calls or game functions by: + +1. Creating function implementations that match the signature: +```cpp +void function_name(uint8_t* rdram, R5900Context* ctx); +``` + +2. Registering them with the runtime: +```cpp +runtime.registerFunction(address, function_name); +``` + +## Advanced Features +Memory Translation +The runtime handles PS2's memory addressing, including: + +* KSEG0/KSEG1 direct mapping +* TLB lookups for user memory +* Special memory areas (scratchpad, I/O registers) + +## Vector Unit Support +PS2-specific 128-bit MMI instructions and VU0 macro mode instructions are supported via SSE/AVX intrinsics. + +## Instruction Patching +You can patch specific instructions in the recompiled code to fix game issues or implement custom behavior. + +## Limitations + +* Graphics and sound output require external implementations +* Some PS2-specific hardware features may not be fully supported +* Performance may vary based on the complexity of the game \ No newline at end of file diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h new file mode 100644 index 0000000..09a908c --- /dev/null +++ b/ps2xRuntime/include/ps2_runtime.h @@ -0,0 +1,130 @@ +#ifndef PS2_RUNTIME_H +#define PS2_RUNTIME_H + +#include +#include +#include +#include +#include +#include // For SSE/AVX instructions + +// PS2 CPU context (R5900) +struct R5900Context +{ + // General Purpose Registers (128-bit) + __m128i r[32]; + + // Program Counter Hi/Lo registers (64-bit) + uint32_t pc; + uint32_t hi; // High result register + uint32_t lo; // Low result register + uint32_t sa; // Shift amount register + + // VU0 registers (when used in macro mode) + __m128 vu0_vf[32]; + float vu0_acc[4]; // VU0 ACC (accumulator) + float vu0_q; // VU0 Q register (quotient) + float vu0_p; // VU0 P register + uint16_t vu0_status; // VU0 status/flags + + // COP0 System control registers (some critical ones) + uint32_t cop0_registers[32]; + uint32_t cop0_status; // Status register + uint32_t cop0_cause; // Cause register + uint32_t cop0_epc; // Exception PC + + // FPU registers (COP1) + float f[32]; + + // FPU control registers + uint32_t fcr0; // Implementation/revision register + uint32_t fcr31; // Control/status register +}; + +class PS2Memory +{ +public: + PS2Memory(); + ~PS2Memory(); + + // Initialize memory + bool initialize(size_t ramSize = 32 * 1024 * 1024); + + // Memory access methods + uint8_t *getRDRAM() { return m_rdram; } + uint8_t *getScratchpad() { return m_scratchpad; } + + // Read/write memory + uint8_t read8(uint32_t address); + uint16_t read16(uint32_t address); + uint32_t read32(uint32_t address); + uint64_t read64(uint32_t address); + __m128i read128(uint32_t address); + + void write8(uint32_t address, uint8_t value); + void write16(uint32_t address, uint16_t value); + void write32(uint32_t address, uint32_t value); + void write64(uint32_t address, uint64_t value); + void write128(uint32_t address, __m128i value); + + // TLB handling + uint32_t translateAddress(uint32_t virtualAddress); + +private: + // Main RAM (32MB) + uint8_t *m_rdram; + + // Scratchpad (16KB) + uint8_t *m_scratchpad; + + // I/O registers + std::unordered_map m_ioRegisters; + + // TLB entries + struct TLBEntry + { + uint32_t vpn; + uint32_t pfn; + uint32_t mask; + bool valid; + }; + + std::vector m_tlbEntries; +}; + +class PS2Runtime +{ +public: + PS2Runtime(); + ~PS2Runtime(); + + bool initialize(); + bool loadELF(const std::string &elfPath); + void run(); + + using RecompiledFunction = void (*)(uint8_t *, R5900Context *); + void registerFunction(uint32_t address, RecompiledFunction func); + RecompiledFunction lookupFunction(uint32_t address); + + void registerBuiltinStubs(); + +private: + PS2Memory m_memory; + R5900Context m_cpuContext; + + // Function table for recompiled code + std::unordered_map m_functionTable; + + // Currently loaded modules + struct LoadedModule + { + std::string name; + uint32_t baseAddress; + size_t size; + bool active; + }; + + std::vector m_loadedModules; +}; + +#endif // PS2_RUNTIME_H \ No newline at end of file diff --git a/ps2xRuntime/main_example.cpp b/ps2xRuntime/main_example.cpp new file mode 100644 index 0000000..7b1ce7e --- /dev/null +++ b/ps2xRuntime/main_example.cpp @@ -0,0 +1,108 @@ +#include "ps2_runtime.h" +#include +#include + +// Example of how to use the PS2 runtime with recompiled code + +// Stub implementation for PS2 syscalls +void syscall(uint8_t *rdram, R5900Context *ctx) +{ + uint32_t syscallNum = ctx->r[4].m128i_u32[0]; + std::cout << "Syscall " << syscallNum << " called" << std::endl; + + switch (syscallNum) + { + case 0x01: // Exit program + std::cout << "Program requested exit with code: " << ctx->r[5].m128i_u32[0] << std::endl; + break; + + case 0x3C: // PutChar - print a character to stdout + std::cout << (char)ctx->r[5].m128i_u32[0]; + break; + + case 0x3D: // PutString - print a string to stdout + { + uint32_t strAddr = ctx->r[5].m128i_u32[0]; + if (strAddr == 0) + { + std::cout << "(null)"; + } + else + { + uint32_t physAddr = strAddr & 0x1FFFFFFF; + const char *str = reinterpret_cast(rdram + physAddr); + std::cout << str; + } + } + break; + + default: + std::cout << "Unhandled syscall: " << syscallNum << std::endl; + break; + } +} + +// Example implementation of FlushCache +void FlushCache(uint8_t *rdram, R5900Context *ctx) +{ + uint32_t cacheType = ctx->r[4].m128i_u32[0]; + std::cout << "FlushCache called with type: " << cacheType << std::endl; +} + +// Example implementation of a recompiled function +void recompiled_main(uint8_t *rdram, R5900Context *ctx) +{ + std::cout << "Running recompiled main function" << std::endl; + + // Example of memory access + uint32_t addr = 0x100000; // Some address in memory + uint32_t physAddr = addr & 0x1FFFFFFF; + uint32_t value = *reinterpret_cast(rdram + physAddr); + std::cout << "Value at 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl; + + // Example of register manipulation + ctx->r[2] = _mm_set1_epi32(0x12345678); // Set register v0 + ctx->r[4] = _mm_set1_epi32(0x3D); // Set register a0 for syscall (PutString) + ctx->r[5] = _mm_set1_epi32(0x10000); // Set register a1 with string address + + // Call a "syscall" function + syscall(rdram, ctx); + + // Example of returning a value + ctx->r[2] = _mm_set1_epi32(0); // Return 0 (success) +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + std::string elfPath = argv[1]; + + PS2Runtime runtime; + if (!runtime.initialize()) + { + std::cerr << "Failed to initialize PS2 runtime" << std::endl; + return 1; + } + + // Register built-in functions + runtime.registerFunction(0x00000001, syscall); + runtime.registerFunction(0x00000002, FlushCache); + runtime.registerFunction(0x00100000, recompiled_main); // Example address for main + + // Load the ELF file + if (!runtime.loadELF(elfPath)) + { + std::cerr << "Failed to load ELF file: " << elfPath << std::endl; + return 1; + } + + // Run the program + runtime.run(); + + return 0; +} \ No newline at end of file diff --git a/ps2xRuntime/src/ps2_memory.cpp b/ps2xRuntime/src/ps2_memory.cpp new file mode 100644 index 0000000..0ec0a33 --- /dev/null +++ b/ps2xRuntime/src/ps2_memory.cpp @@ -0,0 +1,401 @@ +#include "ps2_runtime.h" +#include +#include +#include + +constexpr uint32_t PS2_RAM_BASE = 0x00000000; +constexpr uint32_t PS2_RAM_SIZE = 32 * 1024 * 1024; // 32MB +constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000; +constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16 * 1024; // 16KB +constexpr uint32_t PS2_IO_BASE = 0x10000000; +constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB +constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; +constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000; +constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000; +constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000; +constexpr uint32_t PS2_GS_BASE = 0x12000000; + +PS2Memory::PS2Memory() + : m_rdram(nullptr), m_scratchpad(nullptr) +{ +} + +PS2Memory::~PS2Memory() +{ + if (m_rdram) + { + delete[] m_rdram; + m_rdram = nullptr; + } + + if (m_scratchpad) + { + delete[] m_scratchpad; + m_scratchpad = nullptr; + } +} + +bool PS2Memory::initialize(size_t ramSize) +{ + try + { + // Allocate main RAM + m_rdram = new uint8_t[ramSize]; + if (!m_rdram) + { + std::cerr << "Failed to allocate " << ramSize << " bytes for RDRAM" << std::endl; + return false; + } + std::memset(m_rdram, 0, ramSize); + + // Allocate scratchpad + m_scratchpad = new uint8_t[PS2_SCRATCHPAD_SIZE]; + if (!m_scratchpad) + { + std::cerr << "Failed to allocate " << PS2_SCRATCHPAD_SIZE << " bytes for scratchpad" << std::endl; + delete[] m_rdram; + m_rdram = nullptr; + return false; + } + std::memset(m_scratchpad, 0, PS2_SCRATCHPAD_SIZE); + + // Initialize IO registers with default values + m_ioRegisters.clear(); + + // Initialize TLB entries + m_tlbEntries.clear(); + + return true; + } + catch (const std::exception &e) + { + std::cerr << "Error initializing PS2 memory: " << e.what() << std::endl; + return false; + } +} + +uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) +{ + // Handle special memory regions + if (virtualAddress >= PS2_SCRATCHPAD_BASE && virtualAddress < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + // Scratchpad is directly mapped + return virtualAddress - PS2_SCRATCHPAD_BASE; + } + + // For RDRAM, mask the address to get the physical address + if (virtualAddress < PS2_RAM_SIZE || + (virtualAddress >= 0x80000000 && virtualAddress < 0x80000000 + PS2_RAM_SIZE)) + { + // KSEG0 is directly mapped, just mask out the high bits + return virtualAddress & 0x1FFFFFFF; + } + + // For addresses that need TLB lookup + if (virtualAddress >= 0xC0000000) + { + for (const auto &entry : m_tlbEntries) + { + if (entry.valid) + { + uint32_t vpn_masked = (virtualAddress >> 12) & ~entry.mask; + uint32_t entry_vpn_masked = entry.vpn & ~entry.mask; + + if (vpn_masked == entry_vpn_masked) + { + // TLB hit + uint32_t offset = virtualAddress & 0xFFF; // Page offset + uint32_t page = entry.pfn | (virtualAddress & entry.mask); + return (page << 12) | offset; + } + } + } + // TLB miss + throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress)); + } + + // Default to simple masking for other addresses + return virtualAddress & 0x1FFFFFFF; +} + +uint8_t PS2Memory::read8(uint32_t address) +{ + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + return m_rdram[physAddr]; + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + return m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]; + } + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + { + // IO registers - often not handled byte by byte + uint32_t regAddr = physAddr & ~0x3; // Align to word boundary + if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) + { + uint32_t value = m_ioRegisters[regAddr]; + uint32_t shift = (physAddr & 3) * 8; + return (value >> shift) & 0xFF; + } + return 0; // Unimplemented IO register + } + + // Handle other memory regions ,for now return 0 for unimplemented regions + return 0; +} + +uint16_t PS2Memory::read16(uint32_t address) +{ + // Check alignment + if (address & 1) + { + throw std::runtime_error("Unaligned 16-bit read at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + return *reinterpret_cast(&m_rdram[physAddr]); + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); + } + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + { + // IO registers - align to word boundary and extract relevant bits + uint32_t regAddr = physAddr & ~0x3; + if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) + { + uint32_t value = m_ioRegisters[regAddr]; + uint32_t shift = (physAddr & 2) * 8; + return (value >> shift) & 0xFFFF; + } + return 0; // Unimplemented IO register + } + + return 0; +} + +uint32_t PS2Memory::read32(uint32_t address) +{ + // Check alignment + if (address & 3) + { + throw std::runtime_error("Unaligned 32-bit read at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + return *reinterpret_cast(&m_rdram[physAddr]); + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); + } + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + { + // IO registers + if (m_ioRegisters.find(physAddr) != m_ioRegisters.end()) + { + return m_ioRegisters[physAddr]; + } + return 0; // Unimplemented IO register + } + + return 0; +} + +uint64_t PS2Memory::read64(uint32_t address) +{ + // Check alignment + if (address & 7) + { + throw std::runtime_error("Unaligned 64-bit read at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + return *reinterpret_cast(&m_rdram[physAddr]); + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); + } + + // 64-bit IO operations are not common, but who knows + return (uint64_t)read32(address) | ((uint64_t)read32(address + 4) << 32); +} + +__m128i PS2Memory::read128(uint32_t address) +{ + // Check alignment + if (address & 15) + { + throw std::runtime_error("Unaligned 128-bit read at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr])); + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE])); + } + + // 128-bit reads are primarily for quad-word loads in the EE, which are only valid for RAM areas + // Return zeroes for unsupported areas + return _mm_setzero_si128(); +} + +void PS2Memory::write8(uint32_t address, uint8_t value) +{ + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + m_rdram[physAddr] = value; + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE] = value; + } + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + { + // IO registers - handle byte writes by modifying the appropriate byte in the word + uint32_t regAddr = physAddr & ~0x3; + uint32_t shift = (physAddr & 3) * 8; + uint32_t mask = ~(0xFF << shift); + uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); + m_ioRegisters[regAddr] = newValue; + + // Handle potential side effects of IO register writes + } +} + +void PS2Memory::write16(uint32_t address, uint16_t value) +{ + // Check alignment + if (address & 1) + { + throw std::runtime_error("Unaligned 16-bit write at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + *reinterpret_cast(&m_rdram[physAddr]) = value; + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; + } + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + { + // IO registers - handle halfword writes + uint32_t regAddr = physAddr & ~0x3; + uint32_t shift = (physAddr & 2) * 8; + uint32_t mask = ~(0xFFFF << shift); + uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); + m_ioRegisters[regAddr] = newValue; + + // Handle potential side effects of IO register writes + } +} + +void PS2Memory::write32(uint32_t address, uint32_t value) +{ + // Check alignment + if (address & 3) + { + throw std::runtime_error("Unaligned 32-bit write at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + *reinterpret_cast(&m_rdram[physAddr]) = value; + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; + } + else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) + { + // IO registers + m_ioRegisters[physAddr] = value; + + // Handle potential side effects of IO register writes + // This would be where we handle the various hardware effects + // For example, writing to a DMA control register might trigger a transfer + } +} + +void PS2Memory::write64(uint32_t address, uint64_t value) +{ + // Check alignment + if (address & 7) + { + throw std::runtime_error("Unaligned 64-bit write at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + *reinterpret_cast(&m_rdram[physAddr]) = value; + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; + } + else + { + // Split into two 32-bit writes for other memory regions + write32(address, (uint32_t)value); + write32(address + 4, (uint32_t)(value >> 32)); + } +} + +void PS2Memory::write128(uint32_t address, __m128i value) +{ + // Check alignment + if (address & 15) + { + throw std::runtime_error("Unaligned 128-bit write at address: 0x" + std::to_string(address)); + } + + uint32_t physAddr = translateAddress(address); + + if (physAddr < PS2_RAM_SIZE) + { + _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value); + } + else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) + { + _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]), value); + } + else + { + // Split into smaller writes for other memory regions + // Extract the data using SSE intrinsics + uint64_t lo = _mm_extract_epi64(value, 0); + uint64_t hi = _mm_extract_epi64(value, 1); + + write64(address, lo); + write64(address + 8, hi); + } +} \ No newline at end of file diff --git a/ps2xRuntime/src/ps2_runtime.cpp b/ps2xRuntime/src/ps2_runtime.cpp new file mode 100644 index 0000000..2f9361f --- /dev/null +++ b/ps2xRuntime/src/ps2_runtime.cpp @@ -0,0 +1,218 @@ +#include "ps2_runtime.h" +#include +#include +#include +#include + +#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian +#define ET_EXEC 2 // Executable file + +#define EM_MIPS 8 // MIPS architecture + +struct ElfHeader +{ + uint32_t magic; + uint8_t elf_class; + uint8_t endianness; + uint8_t version; + uint8_t os_abi; + uint8_t abi_version; + uint8_t padding[7]; + uint16_t type; + uint16_t machine; + uint32_t version2; + uint32_t entry; + uint32_t phoff; + uint32_t shoff; + uint32_t flags; + uint16_t ehsize; + uint16_t phentsize; + uint16_t phnum; + uint16_t shentsize; + uint16_t shnum; + uint16_t shstrndx; +}; + +struct ProgramHeader +{ + uint32_t type; + uint32_t offset; + uint32_t vaddr; + uint32_t paddr; + uint32_t filesz; + uint32_t memsz; + uint32_t flags; + uint32_t align; +}; + +#define PT_LOAD 1 // Loadable segment + +PS2Runtime::PS2Runtime() +{ + std::memset(&m_cpuContext, 0, sizeof(m_cpuContext)); + + // R0 is always zero in MIPS + m_cpuContext.r[0] = _mm_set1_epi32(0); + + // Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF + + m_functionTable.clear(); + + m_loadedModules.clear(); +} + +PS2Runtime::~PS2Runtime() +{ + m_loadedModules.clear(); + + m_functionTable.clear(); +} + +bool PS2Runtime::initialize() +{ + if (!m_memory.initialize()) + { + std::cerr << "Failed to initialize PS2 memory" << std::endl; + return false; + } + + registerBuiltinStubs(); + + return true; +} + +void PS2Runtime::registerBuiltinStubs() +{ + // Register common PS2 library functions as stubs + + // Standard C library stubs + registerFunction(0xFFFFFFFF, [](uint8_t *rdram, R5900Context *ctx) + { std::cout << "Stub: printf called" << std::endl; }); + + // PS2-specific system call stubs + registerFunction(0xFFFFFFFE, [](uint8_t *rdram, R5900Context *ctx) + { std::cout << "Stub: FlushCache called with mode: " << ctx->r[4].m128i_u32[0] << std::endl; }); +} + +bool PS2Runtime::loadELF(const std::string &elfPath) +{ + std::ifstream file(elfPath, std::ios::binary); + if (!file) + { + std::cerr << "Failed to open ELF file: " << elfPath << std::endl; + return false; + } + + // Read ELF header + ElfHeader header; + file.read(reinterpret_cast(&header), sizeof(header)); + + // Check ELF magic number + if (header.magic != ELF_MAGIC) + { + std::cerr << "Invalid ELF magic number" << std::endl; + return false; + } + + // Check if it's a MIPS executable + if (header.machine != EM_MIPS || header.type != ET_EXEC) + { + std::cerr << "Not a MIPS executable ELF file" << std::endl; + return false; + } + + // Store entry point + m_cpuContext.pc = header.entry; + + // Read program headers and load segments + for (uint16_t i = 0; i < header.phnum; i++) + { + ProgramHeader ph; + file.seekg(header.phoff + i * header.phentsize); + file.read(reinterpret_cast(&ph), sizeof(ph)); + + if (ph.type == PT_LOAD && ph.filesz > 0) + { + std::cout << "Loading segment: 0x" << std::hex << ph.vaddr + << " - 0x" << (ph.vaddr + ph.memsz) + << " (size: 0x" << ph.memsz << ")" << std::dec << std::endl; + + // Allocate temporary buffer for the segment + std::vector buffer(ph.filesz); + + // Read segment data + file.seekg(ph.offset); + file.read(reinterpret_cast(buffer.data()), ph.filesz); + + // Copy to memory + uint32_t physAddr = m_memory.translateAddress(ph.vaddr); + uint8_t *dest = m_memory.getRDRAM() + physAddr; + std::memcpy(dest, buffer.data(), ph.filesz); + + // Zero-initialize the rest (bss-like sections) + if (ph.memsz > ph.filesz) + { + std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); + } + } + } + + // Create a loaded module entry + LoadedModule module; + module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1); + module.baseAddress = 0x00100000; // Typical base address for PS2 executables + module.size = 0; // Would need to calculate from segments + module.active = true; + + m_loadedModules.push_back(module); + + std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; + return true; +} + +void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func) +{ + m_functionTable[address] = func; +} + +PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address) +{ + auto it = m_functionTable.find(address); + if (it != m_functionTable.end()) + { + return it->second; + } + + std::cerr << "Warning: Function at address 0x" << std::hex << address << std::dec << " not found" << std::endl; + + static RecompiledFunction defaultFunction = [](uint8_t* rdram, R5900Context* ctx) + { + std::cerr << "Error: Called unimplemented function at address 0x" << std::hex << ctx->pc << std::dec << std::endl; + }; + + return defaultFunction; +} + +void PS2Runtime::run() +{ + RecompiledFunction entryPoint = lookupFunction(m_cpuContext.pc); + + // Set up initial CPU state + m_cpuContext.r[4] = _mm_set1_epi32(0); // A0 = 0 (argc) + m_cpuContext.r[5] = _mm_set1_epi32(0); // A1 = 0 (argv) + m_cpuContext.r[29] = _mm_set1_epi32(0x02000000); // SP = top of RAM + + std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; + + try + { + // Call the entry point function + entryPoint(m_memory.getRDRAM(), &m_cpuContext); + + std::cout << "Program execution completed successfully" << std::endl; + } + catch (const std::exception &e) + { + std::cerr << "Error during program execution: " << e.what() << std::endl; + } +} \ No newline at end of file