├── .gitignore ├── CMakeLists.txt ├── README.md ├── ps2xAnalyzer ├── CMakeLists.txt ├── Readme.md ├── include │ └── ps2recomp │ │ └── elf_analyzer.h └── src │ ├── analyzer_main.cpp │ └── elf_analyzer.cpp ├── ps2xRecomp ├── CMakeLists.txt ├── example_config.toml ├── include │ └── ps2recomp │ │ ├── code_generator.h │ │ ├── config_manager.h │ │ ├── elf_parser.h │ │ ├── instructions.h │ │ ├── ps2_recompiler.h │ │ ├── r5900_decoder.h │ │ └── types.h └── src │ ├── code_generator.cpp │ ├── config_manager.cpp │ ├── elf_parser.cpp │ ├── main.cpp │ ├── ps2_recompiler.cpp │ └── r5900_decoder.cpp └── ps2xRuntime ├── CMakeLists.txt ├── Readme.md ├── include ├── ps2_runtime.h ├── ps2_stubs.h ├── ps2_syscalls.h └── register_functions.h ├── main_example.cpp └── src ├── main.cpp ├── ps2_memory.cpp ├── ps2_runtime.cpp ├── ps2_stubs.cpp ├── ps2_syscalls.cpp └── register_functions.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /intermediate/ 3 | .vs/ 4 | out 5 | .vscode 6 | build 7 | 8 | *.exe 9 | *.ilk 10 | *.exp 11 | *.log 12 | *.tlog 13 | *.ipch -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project("PS2 Retro X") 4 | 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 6 | 7 | add_subdirectory("ps2xRecomp") 8 | add_subdirectory("ps2xRuntime") 9 | add_subdirectory("ps2xAnalyzer") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PS2Recomp: PlayStation 2 Static Recompiler (Not ready) 2 | 3 | * Note this is an experiment and doesn't work as it should, feel free to open a PR to help the project. 4 | 5 | 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. 6 | 7 | ### Features 8 | 9 | * Translates MIPS R5900 instructions to C++ code 10 | * Supports PS2-specific 128-bit MMI instructions 11 | * Handles VU0 in macro mode 12 | * Supports relocations and overlays 13 | * Configurable via TOML files 14 | * Single-file or multi-file output options 15 | * Function stubbing and skipping 16 | 17 | ### How It Works 18 | PS2Recomp works by: 19 | 20 | Parsing a PS2 ELF file to extract functions, symbols, and relocations 21 | Decoding the MIPS R5900 instructions in each function 22 | Translating those instructions to equivalent C++ code 23 | Generating a runtime that can execute the recompiled code 24 | 25 | 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);`. 26 | 27 | ### Requirements 28 | 29 | * CMake 3.20 or higher 30 | * C++20 compatible compiler (I only test with MSVC) 31 | * SSE4/AVX support for 128-bit operations 32 | 33 | #### Building 34 | ```bash 35 | git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git 36 | cd PS2Recomp 37 | 38 | # Create build directory 39 | mkdir build 40 | cd build 41 | 42 | cmake .. 43 | cmake --build . 44 | ``` 45 | ### Usage 46 | 47 | 1. Create a configuration file (see `./ps2xRecomp/example_config.toml`) 48 | 2. Run the recompiler: 49 | ``` 50 | ./ps2recomp your_config.toml 51 | ``` 52 | 53 | Compile the generated C++ code 54 | Link with a runtime implementation 55 | 56 | ### Configuration 57 | PS2Recomp uses TOML configuration files to specify: 58 | 59 | * Input ELF file 60 | * Output directory 61 | * Functions to stub or skip 62 | * Instruction patches 63 | 64 | #### Example configuration: 65 | ```toml 66 | [general] 67 | input = "path/to/game.elf" 68 | output = "output/" 69 | single_file_output = false 70 | 71 | # Functions to stub 72 | stubs = ["printf", "malloc", "free"] 73 | 74 | # Functions to skip 75 | skip = ["abort", "exit"] 76 | 77 | # Patches 78 | [patches] 79 | instructions = [ 80 | { address = "0x100004", value = "0x00000000" } 81 | ] 82 | ``` 83 | 84 | ### Runtime 85 | To execute the recompiled code, you'll need to implement or use a runtime that provides: 86 | 87 | * Memory management 88 | * System call handling 89 | * PS2-specific hardware simulation 90 | 91 | A basic runtime lib is provided in `ps2xRuntime` folder. 92 | 93 | ### Limitations 94 | 95 | * VU1 microcode support is limited 96 | * Graphics Synthesizer and other hardware components need external implementation 97 | * Some PS2-specific features may not be fully supported yet 98 | 99 | ### Acknowledgments 100 | 101 | * Inspired by N64Recomp 102 | * Uses ELFIO for ELF parsing 103 | * Uses toml11 for TOML parsing 104 | * Uses fmt for string formatting 105 | -------------------------------------------------------------------------------- /ps2xAnalyzer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(PS2Analyzer VERSION 0.1.0 LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | file(GLOB_RECURSE PS2ANALYZER_SOURCES 8 | "src/*.cpp" 9 | ) 10 | 11 | add_executable(ps2_analyzer ${PS2ANALYZER_SOURCES}) 12 | 13 | target_include_directories(ps2_analyzer PRIVATE 14 | ${CMAKE_CURRENT_SOURCE_DIR}/include 15 | ${CMAKE_SOURCE_DIR}/ps2xRecomp/include 16 | ) 17 | 18 | target_link_libraries(ps2_analyzer PRIVATE 19 | fmt::fmt 20 | ps2_recomp 21 | ) 22 | 23 | install(TARGETS ps2_analyzer 24 | RUNTIME DESTINATION bin 25 | ) -------------------------------------------------------------------------------- /ps2xAnalyzer/Readme.md: -------------------------------------------------------------------------------- 1 | # PS2 ELF Analyzer Tool 2 | 3 | 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. 4 | 5 | ## Key Features 6 | 7 | * Analyzes PS2 ELF binaries to extract symbols, functions, and structure 8 | * Identifies common library functions that should be stubbed 9 | * Flags system functions that should be skipped during recompilation 10 | * Detects potential instruction patterns that may need patching 11 | * Generates a ready-to-use TOML configuration file for PS2Recomp 12 | 13 | ## Using the Analyzer 14 | ```bash 15 | ps2_analyzer 16 | ``` 17 | 18 | ### Where: 19 | 20 | * `input_elf` is the path to the PS2 ELF file you want to analyze 21 | * `output_toml` is the path where the generated TOML configuration will be saved 22 | 23 | ## Example: 24 | ```bash 25 | ps2_analyzer path/to/your/ps2_game.elf config.toml 26 | ``` 27 | 28 | ## How It Works 29 | The analyzer performs the following steps: 30 | 31 | * Parses the ELF file using the same ElfParser used by PS2Recomp 32 | * Extracts functions, symbols, sections, and relocations 33 | * Analyzes the entry point to understand initialization patterns 34 | * Identifies library functions by name patterns and signatures 35 | * Maps the call graph to understand relationships between functions 36 | * Analyzes data usage patterns (basic implementation) 37 | * Scans for problematic instructions that might need patching 38 | * Generates a TOML configuration file with all findings 39 | 40 | ## Generated Configuration 41 | The tool creates a TOML file with the following sections: 42 | ```toml 43 | [general] 44 | input = "path/to/your/ps2_game.elf" 45 | output = "output/" 46 | single_file_output = false 47 | runtime_header = "include/ps2_runtime.h" 48 | 49 | stubs = [ 50 | # List of identified library functions to stub 51 | "printf", 52 | "malloc", 53 | # ... 54 | ] 55 | 56 | skip = [ 57 | # List of system functions to skip 58 | "entry", 59 | "_start", 60 | # ... 61 | ] 62 | 63 | [patches] 64 | instructions = [ 65 | # Potential instruction patches 66 | { address = "0x100008", value = "0x00000000" }, 67 | # ... 68 | ] 69 | ``` 70 | 71 | ## Extending the Analyzer 72 | The analyzer is designed to be extensible. You can enhance its capabilities by: 73 | 74 | * Adding more library function patterns in initializeLibraryFunctions() 75 | * Improving the call graph analysis in analyzeCallGraph() 76 | * Enhancing data usage pattern detection in analyzeDataUsage() 77 | * Refining patch detection logic in identifyPotentialPatches() 78 | 79 | ## Limitations 80 | 81 | * The analyzer uses basic heuristics and may not catch all special cases 82 | * Function identification relies heavily on symbol names 83 | * Patch recommendations are preliminary and may need manual review 84 | * Complex game-specific behaviors may not be detected -------------------------------------------------------------------------------- /ps2xAnalyzer/include/ps2recomp/elf_analyzer.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_ELF_ANALYZER_H 2 | #define PS2RECOMP_ELF_ANALYZER_H 3 | 4 | #include "ps2recomp/elf_parser.h" 5 | #include "ps2recomp/r5900_decoder.h" 6 | #include "ps2recomp/types.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace ps2recomp 16 | { 17 | class ElfAnalyzer 18 | { 19 | public: 20 | ElfAnalyzer(const std::string &elfPath); 21 | ~ElfAnalyzer(); 22 | 23 | bool analyze(); 24 | bool generateToml(const std::string &outputPath); 25 | 26 | private: 27 | std::string m_elfPath; 28 | std::unique_ptr m_elfParser; 29 | std::unique_ptr m_decoder; 30 | 31 | std::vector m_functions; 32 | std::vector m_symbols; 33 | std::vector
m_sections; 34 | std::vector m_relocations; 35 | 36 | std::unordered_set m_libFunctions; 37 | std::unordered_set m_skipFunctions; 38 | std::unordered_map> m_functionDataUsage; 39 | std::unordered_map m_commonDataAccess; 40 | 41 | std::map m_patches; 42 | std::map m_patchReasons; 43 | 44 | std::unordered_map m_functionCFGs; 45 | std::vector m_jumpTables; 46 | std::unordered_map> m_functionCalls; 47 | 48 | void initializeLibraryFunctions(); 49 | void analyzeEntryPoint(); 50 | void analyzeLibraryFunctions(); 51 | void analyzeDataUsage(); 52 | void identifyPotentialPatches(); 53 | void analyzeControlFlow(); 54 | void detectJumpTables(); 55 | void analyzePerformanceCriticalPaths(); 56 | void identifyRecursiveFunctions(); 57 | void analyzeRegisterUsage(); 58 | void analyzeFunctionSignatures(); 59 | void optimizePatches(); 60 | 61 | bool identifyMemcpyPattern(const Function &func); 62 | bool identifyMemsetPattern(const Function &func); 63 | bool identifyStringOperationPattern(const Function &func); 64 | bool identifyMathPattern(const Function &func); 65 | 66 | bool isSystemFunction(const std::string &name) const; 67 | bool isLibraryFunction(const std::string &name) const; 68 | std::vector decodeFunction(const Function &function); 69 | CFG buildCFG(const Function &function); 70 | std::string formatAddress(uint32_t address) const; 71 | std::string escapeBackslashes(const std::string &path); 72 | bool hasMMIInstructions(const Function &function); 73 | bool hasVUInstructions(const Function &function); 74 | bool identifyFunctionType(const Function &function); 75 | void categorizeFunction(Function &function); 76 | uint32_t getSuccessor(const Instruction &inst, uint32_t currentAddr); 77 | bool isSelfModifyingCode(const Function &function); 78 | bool isLoopHeavyFunction(const Function &function); 79 | }; 80 | } 81 | 82 | #endif // PS2RECOMP_ELF_ANALYZER_H -------------------------------------------------------------------------------- /ps2xAnalyzer/src/analyzer_main.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2recomp/elf_analyzer.h" 2 | #include 3 | #include 4 | 5 | void printUsage() 6 | { 7 | std::cout << "PS2 ELF Analyzer\n"; 8 | std::cout << "A tool to analyze PS2 ELF files and generate TOML configuration for PS2Recomp\n\n"; 9 | std::cout << "Usage: ps2_analyzer \n"; 10 | std::cout << " input_elf Path to the PS2 ELF file\n"; 11 | std::cout << " output_toml Path to output TOML configuration file\n"; 12 | } 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | if (argc < 3) 17 | { 18 | printUsage(); 19 | return 1; 20 | } 21 | 22 | std::string elfPath = argv[1]; 23 | std::string tomlPath = argv[2]; 24 | 25 | std::cout << "PS2 ELF Analyzer\n"; 26 | std::cout << "----------------\n"; 27 | std::cout << "Input ELF: " << elfPath << "\n"; 28 | std::cout << "Output TOML: " << tomlPath << "\n\n"; 29 | 30 | try 31 | { 32 | ps2recomp::ElfAnalyzer analyzer(elfPath); 33 | 34 | if (!analyzer.analyze()) 35 | { 36 | std::cerr << "Failed to analyze ELF file\n"; 37 | return 1; 38 | } 39 | 40 | if (!analyzer.generateToml(tomlPath)) 41 | { 42 | std::cerr << "Failed to generate TOML configuration\n"; 43 | return 1; 44 | } 45 | 46 | std::cout << "\nAnalysis complete\n"; 47 | std::cout << "TOML configuration has been written to: " << tomlPath << "\n"; 48 | std::cout << "\nYou can now use this configuration with PS2Recomp:\n"; 49 | std::cout << " ps2recomp " << tomlPath << "\n"; 50 | 51 | return 0; 52 | } 53 | catch (const std::exception &e) 54 | { 55 | std::cerr << "Error: " << e.what() << "\n"; 56 | return 1; 57 | } 58 | } -------------------------------------------------------------------------------- /ps2xRecomp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(PS2Recomp VERSION 0.1.0 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | include(FetchContent) 9 | 10 | FetchContent_Declare( 11 | elfio 12 | GIT_REPOSITORY https://github.com/serge1/ELFIO.git 13 | GIT_TAG main 14 | GIT_SHALLOW TRUE 15 | ) 16 | FetchContent_MakeAvailable(elfio) 17 | 18 | FetchContent_Declare( 19 | toml11 20 | GIT_REPOSITORY https://github.com/ToruNiina/toml11.git 21 | GIT_TAG master 22 | ) 23 | FetchContent_MakeAvailable(toml11) 24 | 25 | FetchContent_Declare( 26 | fmt 27 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 28 | GIT_TAG master 29 | ) 30 | FetchContent_MakeAvailable(fmt) 31 | 32 | file(GLOB_RECURSE PS2RECOMP_SOURCES 33 | "src/*.cpp" 34 | ) 35 | 36 | file(GLOB_RECURSE PS2RECOMP_HEADERS 37 | "include/*.h" 38 | "include/*.hpp" 39 | ) 40 | 41 | add_executable(ps2recomp ${PS2RECOMP_SOURCES}) 42 | 43 | add_library(ps2_recomp STATIC ${PS2RECOMP_SOURCES}) 44 | 45 | target_include_directories(ps2recomp PRIVATE 46 | ${CMAKE_CURRENT_SOURCE_DIR}/include 47 | ${elfio_SOURCE_DIR} 48 | ) 49 | 50 | target_include_directories(ps2_recomp PUBLIC 51 | ${CMAKE_CURRENT_SOURCE_DIR}/include 52 | ${elfio_SOURCE_DIR} 53 | ) 54 | 55 | target_link_libraries(ps2recomp PRIVATE 56 | fmt::fmt 57 | toml11::toml11 58 | ) 59 | 60 | target_link_libraries(ps2_recomp PUBLIC 61 | fmt::fmt 62 | toml11::toml11 63 | ) 64 | 65 | install(TARGETS ps2recomp ps2_recomp 66 | RUNTIME DESTINATION bin 67 | LIBRARY DESTINATION lib 68 | ARCHIVE DESTINATION lib 69 | ) 70 | 71 | install(DIRECTORY include/ 72 | DESTINATION include 73 | ) -------------------------------------------------------------------------------- /ps2xRecomp/example_config.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | # Path to input ELF file 3 | input = "path/to/your/ps2_game.elf" 4 | 5 | # Path to output directory 6 | output = "output/" 7 | 8 | # Single file output mode (false for one file per function) 9 | single_file_output = false 10 | 11 | # Path to runtime header (optional) 12 | runtime_header = "include/ps2_runtime.h" 13 | 14 | # Functions to stub (these will generate empty implementations) 15 | stubs = ["printf", "malloc", "free", "memcpy", "memset", "strncpy", "sprintf"] 16 | 17 | # Functions to skip (these will not be recompiled) 18 | skip = ["abort", "exit", "_exit"] 19 | 20 | # Patches to apply during recompilation 21 | [patches] 22 | # Individual instruction patches 23 | instructions = [ 24 | { address = "0x100004", value = "0x00000000" }, # NOP an instruction 25 | { address = "0x100104", value = "0x24040000" }, # Change an immediate value 26 | ] 27 | 28 | # Function hook patches (not yet implemented) 29 | [[patches.hook]] 30 | function = "printf" 31 | code = ''' 32 | // Custom printf implementation 33 | void printf(uint8_t* rdram, R5900Context* ctx) { 34 | // Implementation here 35 | } 36 | ''' 37 | 38 | # Function replacement patches (not yet implemented) 39 | [[patches.func]] 40 | address = "0x100000" 41 | code = ''' 42 | // Custom implementation for function at 0x100000 43 | void func_00100000(uint8_t* rdram, R5900Context* ctx) { 44 | // Implementation here 45 | } 46 | ''' 47 | -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/code_generator.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_CODE_GENERATOR_H 2 | #define PS2RECOMP_CODE_GENERATOR_H 3 | 4 | #include "ps2recomp/types.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ps2recomp 10 | { 11 | 12 | class CodeGenerator 13 | { 14 | public: 15 | CodeGenerator(const std::vector &symbols); 16 | ~CodeGenerator(); 17 | 18 | std::string generateFunction(const Function &function, const std::vector &instructions, const bool &useHeaders); 19 | std::string generateFunctionRegistration(const std::vector &functions, const std::map &stubs); 20 | std::string generateMacroHeader(); 21 | std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot); 22 | 23 | private: 24 | std::vector m_symbols; 25 | 26 | std::string translateInstruction(const Instruction &inst); 27 | std::string translateMMIInstruction(const Instruction &inst); 28 | std::string translateVUInstruction(const Instruction &inst); 29 | std::string translateFPUInstruction(const Instruction &inst); 30 | std::string translateCOP0Instruction(const Instruction &inst); 31 | std::string translateRegimmInstruction(const Instruction &inst); 32 | std::string translateSpecialInstruction(const Instruction &inst); 33 | 34 | // MMI Translation functions 35 | std::string translateMMI0Instruction(const Instruction &inst); 36 | std::string translateMMI1Instruction(const Instruction &inst); 37 | std::string translateMMI2Instruction(const Instruction &inst); 38 | std::string translateMMI3Instruction(const Instruction &inst); 39 | std::string translatePMFHLInstruction(const Instruction &inst); 40 | std::string translatePMTHLInstruction(const Instruction &inst); 41 | 42 | // Instruction Helpers 43 | std::string translateQFSRV(const Instruction &inst); 44 | std::string translatePMADDW(const Instruction &inst); 45 | std::string translatePDIVW(const Instruction &inst); 46 | std::string translatePCPYLD(const Instruction &inst); 47 | std::string translatePMADDH(const Instruction &inst); 48 | std::string translatePHMADH(const Instruction &inst); 49 | std::string translatePEXEH(const Instruction &inst); 50 | std::string translatePREVH(const Instruction &inst); 51 | std::string translatePMULTH(const Instruction &inst); 52 | std::string translatePDIVBW(const Instruction &inst); 53 | std::string translatePEXEW(const Instruction &inst); 54 | std::string translatePROT3W(const Instruction &inst); 55 | std::string translatePMULTUW(const Instruction &inst); 56 | std::string translatePDIVUW(const Instruction &inst); 57 | std::string translatePCPYUD(const Instruction &inst); 58 | std::string translatePEXCH(const Instruction &inst); 59 | std::string translatePCPYH(const Instruction &inst); 60 | std::string translatePEXCW(const Instruction &inst); 61 | std::string translatePMTHI(const Instruction &inst); 62 | std::string translatePMTLO(const Instruction &inst); 63 | 64 | // VU instruction translations 65 | std::string translateVU_VADD_Field(const Instruction &inst); 66 | std::string translateVU_VSUB_Field(const Instruction &inst); 67 | std::string translateVU_VMUL_Field(const Instruction &inst); 68 | std::string translateVU_VADD(const Instruction &inst); 69 | std::string translateVU_VSUB(const Instruction &inst); 70 | std::string translateVU_VMUL(const Instruction &inst); 71 | std::string translateVU_VDIV(const Instruction &inst); 72 | std::string translateVU_VSQRT(const Instruction &inst); 73 | std::string translateVU_VRSQRT(const Instruction &inst); 74 | std::string translateVU_VMTIR(const Instruction &inst); 75 | std::string translateVU_VMFIR(const Instruction &inst); 76 | std::string translateVU_VILWR(const Instruction &inst); 77 | std::string translateVU_VISWR(const Instruction &inst); 78 | std::string translateVU_VIADD(const Instruction &inst); 79 | std::string translateVU_VISUB(const Instruction &inst); 80 | std::string translateVU_VIADDI(const Instruction &inst); 81 | std::string translateVU_VIAND(const Instruction &inst); 82 | std::string translateVU_VIOR(const Instruction &inst); 83 | std::string translateVU_VCALLMS(const Instruction &inst); 84 | std::string translateVU_VCALLMSR(const Instruction &inst); 85 | std::string translateVU_VRNEXT(const Instruction &inst); 86 | std::string translateVU_VRGET(const Instruction &inst); 87 | std::string translateVU_VRINIT(const Instruction &inst); 88 | std::string translateVU_VRXOR(const Instruction &inst); 89 | std::string translateVU_VMADD_Field(const Instruction &inst); 90 | std::string translateVU_VMINI_Field(const Instruction &inst); 91 | std::string translateVU_VMADD(const Instruction &inst); 92 | std::string translateVU_VMAX(const Instruction &inst); 93 | std::string translateVU_VOPMSUB(const Instruction &inst); 94 | std::string translateVU_VMINI(const Instruction &inst); 95 | 96 | // Jump Table Generation 97 | std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, 98 | const std::vector &entries); 99 | 100 | Symbol *findSymbolByAddress(uint32_t address); 101 | }; 102 | 103 | } 104 | 105 | #endif // PS2RECOMP_CODE_GENERATOR_H -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/config_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_CONFIG_MANAGER_H 2 | #define PS2RECOMP_CONFIG_MANAGER_H 3 | 4 | #include "ps2recomp/types.h" 5 | #include 6 | 7 | namespace ps2recomp 8 | { 9 | 10 | class ConfigManager 11 | { 12 | public: 13 | ConfigManager(const std::string &configPath); 14 | ~ConfigManager(); 15 | 16 | RecompilerConfig loadConfig(); 17 | void saveConfig(const RecompilerConfig &config); 18 | 19 | private: 20 | std::string m_configPath; 21 | }; 22 | 23 | } 24 | 25 | #endif // PS2RECOMP_CONFIG_MANAGER_H -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/elf_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_ELF_PARSER_H 2 | #define PS2RECOMP_ELF_PARSER_H 3 | 4 | #include "ps2recomp/types.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ps2recomp 11 | { 12 | 13 | class ElfParser 14 | { 15 | public: 16 | ElfParser(const std::string &filePath); 17 | ~ElfParser(); 18 | 19 | bool parse(); 20 | 21 | std::vector extractFunctions(); 22 | std::vector extractSymbols(); 23 | std::vector
getSections(); 24 | std::vector getRelocations(); 25 | 26 | // Helper methods 27 | bool isValidAddress(uint32_t address) const; 28 | uint32_t readWord(uint32_t address) const; 29 | uint8_t *getSectionData(const std::string §ionName); 30 | uint32_t getSectionAddress(const std::string §ionName); 31 | uint32_t getSectionSize(const std::string §ionName); 32 | 33 | private: 34 | std::string m_filePath; 35 | std::unique_ptr m_elf; 36 | 37 | std::vector
m_sections; 38 | std::vector m_symbols; 39 | std::vector m_relocations; 40 | 41 | void loadSections(); 42 | void loadSymbols(); 43 | void loadRelocations(); 44 | bool isExecutableSection(const ELFIO::section *section) const; 45 | bool isDataSection(const ELFIO::section *section) const; 46 | }; 47 | 48 | } // namespace ps2recomp 49 | 50 | #endif // PS2RECOMP_ELF_PARSER_H -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/instructions.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_INSTRUCTIONS_H 2 | #define PS2RECOMP_INSTRUCTIONS_H 3 | 4 | #include 5 | 6 | namespace ps2recomp 7 | { 8 | // Basic MIPS opcodes (shared with R4300i) 9 | enum MipsOpcodes 10 | { 11 | OPCODE_SPECIAL = 0x00, // Special instructions group (see SpecialFunctions) 12 | OPCODE_REGIMM = 0x01, // RegImm instructions group (see RegimmFunctions) 13 | OPCODE_J = 0x02, // Jump 14 | OPCODE_JAL = 0x03, // Jump and Link 15 | OPCODE_BEQ = 0x04, // Branch on Equal 16 | OPCODE_BNE = 0x05, // Branch on Not Equal 17 | OPCODE_BLEZ = 0x06, // Branch on Less Than or Equal to Zero 18 | OPCODE_BGTZ = 0x07, // Branch on Greater Than Zero 19 | 20 | OPCODE_ADDI = 0x08, // Add Immediate Word 21 | OPCODE_ADDIU = 0x09, // Add Immediate Unsigned Word 22 | OPCODE_SLTI = 0x0A, // Set on Less Than Immediate 23 | OPCODE_SLTIU = 0x0B, // Set on Less Than Immediate Unsigned 24 | OPCODE_ANDI = 0x0C, // AND Immediate 25 | OPCODE_ORI = 0x0D, // OR Immediate 26 | OPCODE_XORI = 0x0E, // XOR Immediate 27 | OPCODE_LUI = 0x0F, // Load Upper Immediate 28 | 29 | OPCODE_COP0 = 0x10, // Coprocessor 0 instructions (see Cop0Format) 30 | OPCODE_COP1 = 0x11, // Coprocessor 1 (FPU) instructions (see Cop1Format) 31 | OPCODE_COP2 = 0x12, // Coprocessor 2 (VU0 Macro) instructions (see Cop2Format) 32 | OPCODE_COP3 = 0x13, // Unused on PS2 33 | 34 | OPCODE_BEQL = 0x14, // Branch on Equal Likely 35 | OPCODE_BNEL = 0x15, // Branch on Not Equal Likely 36 | OPCODE_BLEZL = 0x16, // Branch on Less Than or Equal to Zero Likely 37 | OPCODE_BGTZL = 0x17, // Branch on Greater Than Zero Likely 38 | 39 | OPCODE_DADDI = 0x18, // Doubleword Add Immediate 40 | OPCODE_DADDIU = 0x19, // Doubleword Add Immediate Unsigned 41 | OPCODE_LDL = 0x1A, // Load Doubleword Left 42 | OPCODE_LDR = 0x1B, // Load Doubleword Right 43 | OPCODE_MMI = 0x1C, // PS2 specific MMI instructions group (see MMIFunctions) 44 | 45 | OPCODE_LQ = 0x1E, // PS2 specific 128-bit load 46 | OPCODE_SQ = 0x1F, // PS2 specific 128-bit store 47 | 48 | OPCODE_LB = 0x20, // Load Byte 49 | OPCODE_LH = 0x21, // Load Halfword 50 | OPCODE_LWL = 0x22, // Load Word Left 51 | OPCODE_LW = 0x23, // Load Word 52 | OPCODE_LBU = 0x24, // Load Byte Unsigned 53 | OPCODE_LHU = 0x25, // Load Halfword Unsigned 54 | OPCODE_LWR = 0x26, // Load Word Right 55 | OPCODE_LWU = 0x27, // Load Word Unsigned 56 | 57 | OPCODE_SB = 0x28, // Store Byte 58 | OPCODE_SH = 0x29, // Store Halfword 59 | OPCODE_SWL = 0x2A, // Store Word Left 60 | OPCODE_SW = 0x2B, // Store Word 61 | OPCODE_SDL = 0x2C, // Store Doubleword Left 62 | OPCODE_SDR = 0x2D, // Store Doubleword Right 63 | OPCODE_SWR = 0x2E, // Store Word Right 64 | OPCODE_CACHE = 0x2F, // Cache Operation 65 | 66 | OPCODE_LL = 0x30, // LL - Load Linked Word, maybe omitted/unused 67 | OPCODE_LWC1 = 0x31, // Load Word to Coprocessor 1 (FPU) 68 | OPCODE_LWC2 = 0x32, // Load Word to Coprocessor 2, maybe omitted/unused 69 | OPCODE_PREF = 0x33, // Prefetch 70 | OPCODE_LLD = 0x34, // Load Linked Doubleword, maybe omitted/unused 71 | OPCODE_LDC1 = 0x35, // Load Doubleword to Coprocessor 1, FPU is single-precision 72 | OPCODE_LDC2 = 0x36, // Load Quadword to Coprocessor 2 (VU0) - Overrides standard MIPS LDC2 73 | OPCODE_LD = 0x37, // Load Doubleword 74 | 75 | OPCODE_SC = 0x38, // Store Conditional Word, maybe omitted/unused? 76 | OPCODE_SWC1 = 0x39, // Store Word from Coprocessor 1 (FPU) 77 | OPCODE_SWC2 = 0x3A, // SWC2 - Store Word from Coprocessor 2, maybe omitted/unused 78 | OPCODE_SCD = 0x3C, // SCD - Store Conditional Doubleword, maybe omitted/unused? 79 | OPCODE_SDC1 = 0x3D, // SDC1 - Store Doubleword from Coprocessor 1, FPU is single-precision 80 | OPCODE_SDC2 = 0x3E, // PS2 specific Store Quadword from Coprocessor 2 (VU0) - Overrides standard MIPS SDC2 81 | OPCODE_SD = 0x3F, // Store Doubleword 82 | 83 | // OPCODE_LQC2 = 0x36, 84 | // OPCODE_SQC2 = 0x3E 85 | }; 86 | 87 | // SPECIAL Function (bits 5-0) for OPCODE_SPECIAL 88 | enum SpecialFunctions 89 | { 90 | SPECIAL_SLL = 0x00, // Shift Word Left Logical 91 | 92 | SPECIAL_SRL = 0x02, // Shift Word Right Logical 93 | SPECIAL_SRA = 0x03, // Shift Word Right Arithmetic 94 | SPECIAL_SLLV = 0x04, // Shift Word Left Logical Variable 95 | 96 | SPECIAL_SRLV = 0x06, // Shift Word Right Logical Variable 97 | SPECIAL_SRAV = 0x07, // Shift Word Right Arithmetic Variable 98 | 99 | SPECIAL_JR = 0x08, // Jump Register 100 | SPECIAL_JALR = 0x09, // Jump and Link Register 101 | SPECIAL_MOVZ = 0x0A, // Move Conditional on Zero 102 | SPECIAL_MOVN = 0x0B, // Move Conditional on Not Zero 103 | SPECIAL_SYSCALL = 0x0C, // System Call 104 | SPECIAL_BREAK = 0x0D, // Breakpoint 105 | 106 | SPECIAL_SYNC = 0x0F, // Synchronize Shared Memory 107 | 108 | SPECIAL_MFHI = 0x10, // Move From HI Register 109 | SPECIAL_MTHI = 0x11, // Move To HI Register 110 | SPECIAL_MFLO = 0x12, // Move From LO Register 111 | SPECIAL_MTLO = 0x13, // Move To LO Register 112 | SPECIAL_DSLLV = 0x14, // Doubleword Shift Left Logical Variable 113 | 114 | SPECIAL_DSRLV = 0x16, // Doubleword Shift Right Logical Variable 115 | SPECIAL_DSRAV = 0x17, // Doubleword Shift Right Arithmetic Variable 116 | 117 | SPECIAL_MULT = 0x18, // Multiply Word 118 | SPECIAL_MULTU = 0x19, // Multiply Unsigned Word 119 | SPECIAL_DIV = 0x1A, // Divide Word 120 | SPECIAL_DIVU = 0x1B, // Divide Unsigned Word 121 | 122 | SPECIAL_ADD = 0x20, // Add Word 123 | SPECIAL_ADDU = 0x21, // Add Unsigned Word 124 | SPECIAL_SUB = 0x22, // Subtract Word 125 | SPECIAL_SUBU = 0x23, // Subtract Unsigned Word 126 | SPECIAL_AND = 0x24, // AND 127 | SPECIAL_OR = 0x25, // OR 128 | SPECIAL_XOR = 0x26, // XOR 129 | SPECIAL_NOR = 0x27, // NOR 130 | 131 | SPECIAL_MFSA = 0x28, // Move From SA Register (PS2 specific) 132 | SPECIAL_MTSA = 0x29, // Move To SA Register (PS2 specific) 133 | SPECIAL_SLT = 0x2A, // Set on Less Than 134 | SPECIAL_SLTU = 0x2B, // Set on Less Than Unsigned 135 | SPECIAL_DADD = 0x2C, // Doubleword Add 136 | SPECIAL_DADDU = 0x2D, // Doubleword Add Unsigned 137 | SPECIAL_DSUB = 0x2E, // Doubleword Subtract 138 | SPECIAL_DSUBU = 0x2F, // Doubleword Subtract Unsigned 139 | 140 | SPECIAL_TGE = 0x30, // Trap if Greater or Equal 141 | SPECIAL_TGEU = 0x31, // Trap if Greater or Equal Unsigned 142 | SPECIAL_TLT = 0x32, // Trap if Less Than 143 | SPECIAL_TLTU = 0x33, // Trap if Less Than Unsigned 144 | SPECIAL_TEQ = 0x34, // Trap if Equal 145 | 146 | SPECIAL_TNE = 0x36, // Trap if Not Equal 147 | 148 | SPECIAL_DSLL = 0x38, // Doubleword Shift Left Logical 149 | 150 | SPECIAL_DSRL = 0x3A, // Doubleword Shift Right Logical 151 | SPECIAL_DSRA = 0x3B, // Doubleword Shift Right Arithmetic 152 | SPECIAL_DSLL32 = 0x3C, // Doubleword Shift Left Logical +32 153 | 154 | SPECIAL_DSRL32 = 0x3E, // Doubleword Shift Right Logical +32 155 | SPECIAL_DSRA32 = 0x3F, // Doubleword Shift Right Arithmetic +32 156 | }; 157 | 158 | // REGIMM RT Field (bits 20-16) for OPCODE_REGIMM 159 | enum RegimmFunctions 160 | { 161 | REGIMM_BLTZ = 0x00, // Branch on Less Than Zero 162 | REGIMM_BGEZ = 0x01, // Branch on Greater Than or Equal to Zero 163 | REGIMM_BLTZL = 0x02, // Branch on Less Than Zero Likely 164 | REGIMM_BGEZL = 0x03, // Branch on Greater Than or Equal to Zero Likely 165 | 166 | REGIMM_TGEI = 0x08, // Trap if Greater or Equal Immediate 167 | REGIMM_TGEIU = 0x09, // Trap if Greater or Equal Immediate Unsigned 168 | REGIMM_TLTI = 0x0A, // Trap if Less Than Immediate 169 | REGIMM_TLTIU = 0x0B, // Trap if Less Than Immediate Unsigned 170 | REGIMM_TEQI = 0x0C, // Trap if Equal Immediate 171 | 172 | REGIMM_TNEI = 0x0E, // Trap if Not Equal Immediate 173 | 174 | REGIMM_BLTZAL = 0x10, // Branch on Less Than Zero and Link 175 | REGIMM_BGEZAL = 0x11, // Branch on Greater Than or Equal to Zero and Link 176 | REGIMM_BLTZALL = 0x12, // Branch on Less Than Zero and Link Likely 177 | REGIMM_BGEZALL = 0x13, // Branch on Greater Than or Equal to Zero and Link Likely 178 | 179 | REGIMM_MTSAB = 0x18, // Move To SA Register Byte (PS2 specific) 180 | REGIMM_MTSAH = 0x19, // Move To SA Register Halfword (PS2 specific) 181 | }; 182 | 183 | // MMI Function Field (bits 5-0) for OPCODE_MMI (0x1C) 184 | enum MMIFunctions 185 | { 186 | MMI_MADD = 0x00, // Multiply Add Word (to LO/HI) 187 | MMI_MADDU = 0x01, // Multiply Add Unsigned Word (to LO/HI) 188 | MMI_MSUB = 0x02, // Multiply Subtract Word (Not listed in provided doc table, but valid MMI) 189 | MMI_MSUBU = 0x03, // Multiply Subtract Unsigned Word (Not listed in provided doc table, but valid MMI) 190 | MMI_PLZCW = 0x04, // Parallel Leading Zero/One Count Word 191 | 192 | MMI_MMI0 = 0x08, // Group MMI0 instructions (see MMI0Functions) 193 | MMI_MMI2 = 0x09, // Group MMI2 instructions (see MMI2Functions) 194 | 195 | MMI_MFHI1 = 0x10, // Move From HI1 Register 196 | MMI_MTHI1 = 0x11, // Move To HI1 Register 197 | MMI_MFLO1 = 0x12, // Move From LO1 Register 198 | MMI_MTLO1 = 0x13, // Move To LO1 Register 199 | 200 | MMI_MULT1 = 0x18, // Multiply Word to LO1/HI1 201 | MMI_MULTU1 = 0x19, // Multiply Unsigned Word to LO1/HI1 202 | MMI_DIV1 = 0x1A, // Divide Word using LO1/HI1 203 | MMI_DIVU1 = 0x1B, // Divide Unsigned Word using LO1/HI1 204 | 205 | MMI_MADD1 = 0x20, // Multiply Add Word to LO1/HI1 206 | MMI_MADDU1 = 0x21, // Multiply Add Unsigned Word to LO1/HI1 207 | 208 | MMI_MMI1 = 0x28, // Group MMI1 instructions (see MMI1Functions) 209 | MMI_MMI3 = 0x29, // Group MMI3 instructions (see MMI3Functions) 210 | 211 | MMI_PMFHL = 0x30, // Parallel Move From HI/LO Register (see PMFHLFunctions for 'sa' field) 212 | MMI_PMTHL = 0x31, // Parallel Move To HI/LO Register (see PMFHLFunctions for 'sa' field) 213 | 214 | MMI_PSLLH = 0x34, // Parallel Shift Left Logical Halfword 215 | 216 | MMI_PSRLH = 0x36, // Parallel Shift Right Logical Halfword 217 | MMI_PSRAH = 0x37, // Parallel Shift Right Arithmetic Halfword 218 | 219 | MMI_PSLLW = 0x3C, // Parallel Shift Left Logical Word 220 | 221 | MMI_PSRLW = 0x3E, // Parallel Shift Right Logical Word 222 | MMI_PSRAW = 0x3F // Parallel Shift Right Arithmetic Word 223 | }; 224 | 225 | // PS2-specific MMI0 Sub-Function Field (bits 0-5 within MMI_MMI0 group) 226 | enum MMI0Functions 227 | { 228 | MMI0_PADDW = 0x00, 229 | MMI0_PSUBW = 0x01, 230 | MMI0_PCGTW = 0x02, 231 | MMI0_PMAXW = 0x03, 232 | MMI0_PADDH = 0x04, 233 | MMI0_PSUBH = 0x05, 234 | MMI0_PCGTH = 0x06, 235 | MMI0_PMAXH = 0x07, 236 | MMI0_PADDB = 0x08, 237 | MMI0_PSUBB = 0x09, 238 | MMI0_PCGTB = 0x0A, 239 | MMI0_PADDSW = 0x10, 240 | MMI0_PSUBSW = 0x11, 241 | MMI0_PEXTLW = 0x12, 242 | MMI0_PPACW = 0x13, 243 | MMI0_PADDSH = 0x14, 244 | MMI0_PSUBSH = 0x15, 245 | MMI0_PEXTLH = 0x16, 246 | MMI0_PPACH = 0x17, 247 | MMI0_PADDSB = 0x18, 248 | MMI0_PSUBSB = 0x19, 249 | MMI0_PEXTLB = 0x1A, 250 | MMI0_PPACB = 0x1B, 251 | MMI0_PEXT5 = 0x1E, 252 | MMI0_PPAC5 = 0x1F 253 | }; 254 | 255 | // PS2-specific MMI1 Sub-Function Field (bits 0-5 within MMI_MMI1 group) 256 | enum MMI1Functions 257 | { 258 | MMI1_PABSW = 0x01, 259 | MMI1_PCEQW = 0x02, 260 | MMI1_PMINW = 0x03, 261 | MMI1_PADSBH = 0x04, 262 | MMI1_PABSH = 0x05, 263 | MMI1_PCEQH = 0x06, 264 | MMI1_PMINH = 0x07, 265 | MMI1_PCEQB = 0x0A, 266 | MMI1_PADDUW = 0x10, 267 | MMI1_PSUBUW = 0x11, 268 | MMI1_PEXTUW = 0x12, 269 | MMI1_PADDUH = 0x14, 270 | MMI1_PSUBUH = 0x15, 271 | MMI1_PEXTUH = 0x16, 272 | MMI1_PADDUB = 0x18, 273 | MMI1_PSUBUB = 0x19, 274 | MMI1_PEXTUB = 0x1A, 275 | MMI1_QFSRV = 0x1B 276 | }; 277 | 278 | // PS2-specific MMI2 Sub-Function Field (bits 0-5 within MMI_MMI2 group) 279 | enum MMI2Functions 280 | { 281 | MMI2_PMADDW = 0x00, 282 | MMI2_PSLLVW = 0x02, 283 | MMI2_PSRLVW = 0x03, 284 | MMI2_PMSUBW = 0x04, 285 | MMI2_PMFHI = 0x08, 286 | MMI2_PMFLO = 0x09, 287 | MMI2_PINTH = 0x0A, 288 | MMI2_PMULTW = 0x0C, 289 | MMI2_PDIVW = 0x0D, 290 | MMI2_PCPYLD = 0x0E, 291 | MMI2_PAND = 0x12, 292 | MMI2_PXOR = 0x13, 293 | MMI2_PMADDH = 0x14, 294 | MMI2_PHMADH = 0x15, 295 | MMI2_PMSUBH = 0x18, 296 | MMI2_PHMSBH = 0x19, 297 | MMI2_PEXEH = 0x1A, 298 | MMI2_PREVH = 0x1B, 299 | MMI2_PMULTH = 0x1C, 300 | MMI2_PDIVBW = 0x1D, 301 | MMI2_PEXEW = 0x1E, 302 | MMI2_PROT3W = 0x1F 303 | }; 304 | 305 | // PS2-specific MMI3 Sub-Function Field (bits 0-5 within MMI_MMI3 group) 306 | enum MMI3Functions 307 | { 308 | MMI3_PMADDUW = 0x00, 309 | MMI3_PSRAVW = 0x03, 310 | MMI3_PMTHI = 0x08, // Move To HI register 311 | MMI3_PMTLO = 0x09, // Move To LO register 312 | MMI3_PINTEH = 0x0A, 313 | MMI3_PMULTUW = 0x0C, 314 | MMI3_PDIVUW = 0x0D, 315 | MMI3_PCPYUD = 0x0E, 316 | MMI3_POR = 0x12, 317 | MMI3_PNOR = 0x13, 318 | MMI3_PEXCH = 0x1A, 319 | MMI3_PCPYH = 0x1B, 320 | MMI3_PEXCW = 0x1E 321 | }; 322 | 323 | // PMFHL/PMTHL Sub-Function Field ('sa' field, bits 10-6) 324 | enum PMFHLFunctions 325 | { 326 | PMFHL_LW = 0x00, // Load Word / Lower Word 327 | PMFHL_UW = 0x01, // Upper Word 328 | PMFHL_SLW = 0x02, // Signed Lower Word 329 | PMFHL_LH = 0x03, // Load Halfword / Lower Halfword 330 | PMFHL_SH = 0x04 // Store Halfword / Signed Upper Halfword? (Doc name varies) 331 | }; 332 | 333 | // COP0 Format Field ('fmt' or 'rs' field, bits 25-21) for OPCODE_COP0 334 | enum COP0Format 335 | { 336 | COP0_MF = 0x00, // Move From COP0 - MFC0 (fmt=00000) 337 | COP0_MT = 0x04, // Move To COP0 - MTC0 (fmt=00100) 338 | COP0_BC = 0x08, // BC0 group (fmt=01000) (see Cop0BranchCondition) 339 | COP0_CO = 0x10 // COP0 Operation group (fmt=10000) (see Cop0CoFunctions) 340 | }; 341 | 342 | // COP0 CO Function Field (bits 5-0) for COP0_CO Format 343 | enum Cop0CoFunctions 344 | { 345 | COP0_CO_TLBR = 0x01, // TLB Read 346 | COP0_CO_TLBWI = 0x02, // TLB Write Indexed 347 | 348 | COP0_CO_TLBWR = 0x06, // TLB Write Random 349 | 350 | COP0_CO_TLBP = 0x08, // TLB Probe 351 | 352 | COP0_CO_ERET = 0x18, // Return from Exception 353 | 354 | COP0_CO_EI = 0x38, // Enable Interrupts 355 | COP0_CO_DI = 0x39 // Disable Interrupts 356 | }; 357 | 358 | // COP0 BC Condition Field ('fmt' or 'rt' field, bits 20-16) for COP0_BC Format 359 | enum Cop0BranchCondition 360 | { 361 | COP0_BC_BCF = 0x00, // Branch on Condition False 362 | COP0_BC_BCT = 0x01, // Branch on Condition True 363 | COP0_BC_BCFL = 0x02, // Branch on Condition False Likely 364 | COP0_BC_BCTL = 0x03, // Branch on Condition True Likely 365 | }; 366 | 367 | // COP0 Register Numbers (used with MFC0/MTC0) 368 | enum COP0Reg 369 | { 370 | COP0_REG_INDEX = 0, // Index into TLB array 371 | COP0_REG_RANDOM = 1, // Randomly generated index into TLB array 372 | COP0_REG_ENTRYLO0 = 2, // Low half of TLB entry for even-numbered virtual pages 373 | COP0_REG_ENTRYLO1 = 3, // Low half of TLB entry for odd-numbered virtual pages 374 | COP0_REG_CONTEXT = 4, // Context 375 | COP0_REG_PAGEMASK = 5, // TLB page size mask 376 | COP0_REG_WIRED = 6, // Controls which TLB entries are affected by random replacement 377 | 378 | COP0_REG_BADVADDR = 8, // Virtual address of most recent address-related exception 379 | COP0_REG_COUNT = 9, // Timer count 380 | COP0_REG_ENTRYHI = 10, // High half of TLB entry 381 | COP0_REG_COMPARE = 11, // Timer compare value 382 | COP0_REG_STATUS = 12, // Processor status and control 383 | COP0_REG_CAUSE = 13, // Cause of last exception 384 | COP0_REG_EPC = 14, // Exception program counter 385 | COP0_REG_PRID = 15, // Processor identification and revision 386 | COP0_REG_CONFIG = 16, // Configuration register 387 | 388 | COP0_REG_BADPADDR = 23, // Bad physical address 389 | COP0_REG_DEBUG = 24, // Debug register 390 | COP0_REG_PERF = 25, // Performance counter 391 | 392 | COP0_REG_TAGLO = 28, // Cache TagLo (I-Cache & D-Cache) 393 | COP0_REG_TAGHI = 29, // High bits of cache tag ( Cache TagHi ) 394 | COP0_REG_ERROREPC = 30 // Error exception program counter 395 | }; 396 | 397 | // COP1 (FPU) Format Field ('fmt' or 'rs' field, bits 25-21) for OPCODE_COP1 398 | enum Cop1Format 399 | { 400 | COP1_MF = 0x00, // Move From FPU Register 401 | COP1_CF = 0x02, // Move From FPU Control Register 402 | COP1_MT = 0x04, // Move To FPU Register 403 | COP1_CT = 0x06, // Move To FPU Control Register 404 | COP1_BC = 0x08, // Branch on FPU Condition (see Cop1BranchCondition) 405 | 406 | COP1_D = 0x11, // Standard MIPS Double format; EE FPU is single-precision, (unused on PS2 FPU?) 407 | COP1_L = 0x15, // Long (64-bit integer) Operation (unused on PS2 FPU?) 408 | 409 | COP1_S = 0x10, // Single Precision Operation (see Cop1FunctionsS) 410 | COP1_W = 0x14 // Word (integer) Operation (see Cop1FunctionsW) 411 | }; 412 | 413 | // COP1 Function Field (bits 5-0) for COP1_S Format (Single-Precision) 414 | enum Cop1FunctionsS 415 | { 416 | COP1_S_ADD = 0x00, // ADD.S 417 | COP1_S_SUB = 0x01, // SUB.S 418 | COP1_S_MUL = 0x02, // MUL.S 419 | COP1_S_DIV = 0x03, // DIV.S 420 | COP1_S_SQRT = 0x04, // SQRT.S 421 | COP1_S_ABS = 0x05, // ABS.S 422 | COP1_S_MOV = 0x06, // MOV.S 423 | COP1_S_NEG = 0x07, // NEG.S 424 | 425 | COP1_S_ROUND_L = 0x08, // ROUND.L.S - Requires 64-bit int dest, likely unused 426 | COP1_S_TRUNC_L = 0x09, // TRUNC.L.S - Requires 64-bit int dest, likely unused 427 | COP1_S_CEIL_L = 0x0A, // CEIL.L.S - Requires 64-bit int dest, likely unused 428 | COP1_S_FLOOR_L = 0x0B, // FLOOR.L.S - Requires 64-bit int dest, likely unused 429 | COP1_S_ROUND_W = 0x0C, // ROUND.W.S 430 | COP1_S_TRUNC_W = 0x0D, // TRUNC.W.S 431 | COP1_S_CEIL_W = 0x0E, // CEIL.W.S 432 | COP1_S_FLOOR_W = 0x0F, // FLOOR.W.S 433 | 434 | // --- Additional Arithmetic/Functions (from FPU.S table) --- 435 | COP1_S_RSQRT = 0x16, // RSQRT.S (Reciprocal Square Root) 436 | COP1_S_ADDA = 0x18, // ADDA.S (Add Accumulator) 437 | COP1_S_SUBA = 0x19, // SUBA.S (Subtract Accumulator) 438 | COP1_S_MULA = 0x1A, // MULA.S (Multiply Accumulator) 439 | COP1_S_MADD = 0x1C, // MADD.S (Multiply Add) 440 | COP1_S_MSUB = 0x1D, // MSUB.S (Multiply Subtract) 441 | COP1_S_MADDA = 0x1E, // MADDA.S (Multiply Add Accumulator) 442 | COP1_S_MSUBA = 0x1F, // MSUBA.S (Multiply Subtract Accumulator) 443 | 444 | // --- Conversions (from FPU.S table) --- 445 | COP1_S_CVT_D = 0x21, // CVT.D.S - Requires double-precision FPU 446 | COP1_S_CVT_W = 0x24, // CVT.W.S (Convert to Word) 447 | COP1_S_CVT_L = 0x25, // CVT.L.S - Requires 64-bit int dest, likely unused 448 | 449 | // --- Min/Max (from FPU.S table) --- 450 | COP1_S_MAX = 0x28, // MAX.S 451 | COP1_S_MIN = 0x29, // MIN.S 452 | 453 | COP1_S_C_F = 0x30, // C.F.S (False) 454 | COP1_S_C_UN = 0x31, // C.UN.S (Unordered) 455 | COP1_S_C_EQ = 0x32, // C.EQ.S (Equal) 456 | COP1_S_C_UEQ = 0x33, // C.UEQ.S (Unordered or Equal) 457 | COP1_S_C_OLT = 0x34, // C.OLT.S (Ordered Less Than) 458 | COP1_S_C_ULT = 0x35, // C.ULT.S (Unordered or Less Than) 459 | COP1_S_C_OLE = 0x36, // C.OLE.S (Ordered Less Than or Equal) 460 | COP1_S_C_ULE = 0x37, // C.ULE.S (Unordered or Less Than or Equal) 461 | COP1_S_C_SF = 0x38, // C.SF.S (Signaling False) 462 | COP1_S_C_NGLE = 0x39, // C.NGLE.S (Not Greater or Less or Equal - Unordered) 463 | COP1_S_C_SEQ = 0x3A, // C.SEQ.S (Signaling Equal) 464 | COP1_S_C_NGL = 0x3B, // C.NGL.S (Not Greater or Less - Unordered or Equal) 465 | COP1_S_C_LT = 0x3C, // C.LT.S (Signaling Less Than) 466 | COP1_S_C_NGE = 0x3D, // C.NGE.S (Not Greater or Equal - Unordered or Less Than) 467 | COP1_S_C_LE = 0x3E, // C.LE.S (Signaling Less Than or Equal) 468 | COP1_S_C_NGT = 0x3F, // C.NGT.S (Not Greater Than - Unordered or Less Than or Equal) 469 | }; 470 | 471 | // COP1 Function Field (bits 5-0) for COP1_W Format (Word/Integer source) 472 | enum Cop1FunctionsW 473 | { 474 | COP1_W_CVT_S = 0x20, // CVT.S.W (Convert Word Integer to Single Float) 475 | // 0x21 --- (Standard MIPS: CVT.D.W - requires double-precision) 476 | // 0x24 --- (Standard MIPS: CVT.W.W - Nop?) 477 | // 0x25 --- (Standard MIPS: CVT.L.W - requires 64-bit int dest) 478 | }; 479 | 480 | // COP1 BC Condition Field ('fmt' or 'rt' field, bits 20-16) for COP1_BC Format 481 | enum Cop1BranchCondition 482 | { 483 | COP1_BC_BCF = 0x00, // Branch on FPU Condition False (BC1F) 484 | COP1_BC_BCT = 0x01, // Branch on FPU Condition True (BC1T) 485 | COP1_BC_BCFL = 0x02, // Branch on FPU Condition False Likely (BC1FL) 486 | COP1_BC_BCTL = 0x03, // Branch on FPU Condition True Likely (BC1TL) 487 | }; 488 | 489 | // COP2 (VU0 Macro) Format Field ('fmt' or 'rs' field, bits 25-21) for OPCODE_COP2 490 | enum Cop2Format 491 | { 492 | // Note: Standard MIPS MFC2/MTC2 use fmt 0x00/0x04, EE uses QMFC2/QMTC2 493 | COP2_QMFC2 = 0x01, // QMFC2 (fmt=00001) - Load Quadword From Coprocessor 2 494 | COP2_CFC2 = 0x02, // CFC2 (fmt=00010) - Load Control Word From Coprocessor 2 495 | COP2_QMTC2 = 0x05, // QMTC2 (fmt=00101) - Store Quadword To Coprocessor 2 496 | COP2_CTC2 = 0x06, // CTC2 (fmt=00110) - Store Control Word To Coprocessor 2 497 | COP2_BC = 0x08, // BC2 group (fmt=01000) (see Cop2BranchCondition) 498 | COP2_CO = 0x10, // VU0 Macro Operation group (fmt=1xxxx) 499 | }; 500 | 501 | // COP2 BC Condition Field ('fmt' or 'rt' field, bits 20-16) for COP2_BC Format 502 | enum Cop2BranchCondition 503 | { 504 | COP2_BC_BCF = 0x00, // Branch on VU0 Condition False (BC2F) 505 | COP2_BC_BCT = 0x01, // Branch on VU0 Condition True (BC2T) 506 | COP2_BC_BCFL = 0x02, // Branch on VU0 Condition False Likely (BC2FL) 507 | COP2_BC_BCTL = 0x03, // Branch on VU0 Condition True Likely (BC2TL) 508 | }; 509 | 510 | // VU0 Macro Function Field (bits 5-0) for COP2_CO Format (Special1 Table) 511 | enum VU0MacroSpecial1Functions : uint8_t 512 | { 513 | VU0_S1_VADDx = 0x00, 514 | VU0_S1_VADDy = 0x01, 515 | VU0_S1_VADDz = 0x02, 516 | VU0_S1_VADDw = 0x03, 517 | VU0_S1_VSUBx = 0x04, 518 | VU0_S1_VSUBy = 0x05, 519 | VU0_S1_VSUBz = 0x06, 520 | VU0_S1_VSUBw = 0x07, 521 | VU0_S1_VMADDx = 0x08, 522 | VU0_S1_VMADDy = 0x09, 523 | VU0_S1_VMADDz = 0x0A, 524 | VU0_S1_VMADDw = 0x0B, 525 | VU0_S1_VMSUBx = 0x0C, 526 | VU0_S1_VMSUBy = 0x0D, 527 | VU0_S1_VMSUBz = 0x0E, 528 | VU0_S1_VMSUBw = 0x0F, 529 | VU0_S1_VMAXx = 0x10, 530 | VU0_S1_VMAXy = 0x11, 531 | VU0_S1_VMAXz = 0x12, 532 | VU0_S1_VMAXw = 0x13, 533 | VU0_S1_VMINIx = 0x14, 534 | VU0_S1_VMINIy = 0x15, 535 | VU0_S1_VMINIz = 0x16, 536 | VU0_S1_VMINIw = 0x17, 537 | VU0_S1_VMULx = 0x18, 538 | VU0_S1_VMULy = 0x19, 539 | VU0_S1_VMULz = 0x1A, 540 | VU0_S1_VMULw = 0x1B, 541 | VU0_S1_VMULq = 0x1C, 542 | VU0_S1_VMAXi = 0x1D, 543 | VU0_S1_VMULi = 0x1E, 544 | VU0_S1_VMINIi = 0x1F, 545 | VU0_S1_VADDq = 0x20, 546 | VU0_S1_VMADDq = 0x21, 547 | VU0_S1_VADDi = 0x22, 548 | VU0_S1_VMADDi = 0x23, 549 | VU0_S1_VSUBq = 0x24, 550 | VU0_S1_VMSUBq = 0x25, 551 | VU0_S1_VSUBi = 0x26, 552 | VU0_S1_VMSUBi = 0x27, 553 | VU0_S1_VADD = 0x28, 554 | VU0_S1_VMADD = 0x29, 555 | VU0_S1_VMUL = 0x2A, 556 | VU0_S1_VMAX = 0x2B, 557 | VU0_S1_VSUB = 0x2C, 558 | VU0_S1_VMSUB = 0x2D, 559 | VU0_S1_VOPMSUB = 0x2E, 560 | VU0_S1_VMINI = 0x2F, 561 | VU0_S1_VIADD = 0x30, 562 | VU0_S1_VISUB = 0x31, 563 | VU0_S1_VIADDI = 0x32, 564 | VU0_S1_VIAND = 0x34, 565 | VU0_S1_VIOR = 0x35, 566 | VU0_S1_VCALLMS = 0x38, 567 | VU0_S1_VCALLMSR = 0x39, 568 | }; 569 | 570 | // VU0 Macro Function Field (bits 5-0) for COP2_CO Format (Special2 Table) 571 | enum VU0MacroSpecial2Functions : uint8_t 572 | { 573 | VU0_S2_VADDAx = 0x00, 574 | VU0_S2_VADDAy = 0x01, 575 | VU0_S2_VADDAz = 0x02, 576 | VU0_S2_VADDAw = 0x03, 577 | VU0_S2_VSUBAx = 0x04, 578 | VU0_S2_VSUBAy = 0x05, 579 | VU0_S2_VSUBAz = 0x06, 580 | VU0_S2_VSUBAw = 0x07, 581 | VU0_S2_VMADDAx = 0x08, 582 | VU0_S2_VMADDAy = 0x09, 583 | VU0_S2_VMADDAz = 0x0A, 584 | VU0_S2_VMADDAw = 0x0B, 585 | VU0_S2_VMSUBAx = 0x0C, 586 | VU0_S2_VMSUBAy = 0x0D, 587 | VU0_S2_VMSUBAz = 0x0E, 588 | VU0_S2_VMSUBAw = 0x0F, 589 | VU0_S2_VITOF0 = 0x10, 590 | VU0_S2_VITOF4 = 0x11, 591 | VU0_S2_VITOF12 = 0x12, 592 | VU0_S2_VITOF15 = 0x13, 593 | VU0_S2_VFTOI0 = 0x14, 594 | VU0_S2_VFTOI4 = 0x15, 595 | VU0_S2_VFTOI12 = 0x16, 596 | VU0_S2_VFTOI15 = 0x17, 597 | VU0_S2_VMULAx = 0x18, 598 | VU0_S2_VMULAy = 0x19, 599 | VU0_S2_VMULAz = 0x1A, 600 | VU0_S2_VMULAw = 0x1B, 601 | VU0_S2_VMULAq = 0x1C, 602 | VU0_S2_VABS = 0x1D, 603 | VU0_S2_VMULAi = 0x1E, 604 | VU0_S2_VCLIPw = 0x1F, 605 | VU0_S2_VADDAq = 0x20, 606 | VU0_S2_VMADDAq = 0x21, 607 | VU0_S2_VADDAi = 0x22, 608 | VU0_S2_VMADDAi = 0x23, 609 | VU0_S2_VSUBAq = 0x24, 610 | VU0_S2_VMSUBAq = 0x25, 611 | VU0_S2_VSUBAi = 0x26, 612 | VU0_S2_VMSUBAi = 0x27, 613 | VU0_S2_VADDA = 0x28, 614 | VU0_S2_VMADDA = 0x29, 615 | VU0_S2_VMULA = 0x2A, 616 | VU0_S2_VSUBA = 0x2C, 617 | VU0_S2_VMSUBA = 0x2D, 618 | VU0_S2_VOPMULA = 0x2E, 619 | VU0_S2_VNOP = 0x2F, 620 | VU0_S2_VMOVE = 0x30, 621 | VU0_S2_VMR32 = 0x31, 622 | VU0_S2_VLQI = 0x34, 623 | VU0_S2_VSQI = 0x35, 624 | VU0_S2_VLQD = 0x36, 625 | VU0_S2_VSQD = 0x37, 626 | VU0_S2_VDIV = 0x38, 627 | VU0_S2_VSQRT = 0x39, 628 | VU0_S2_VRSQRT = 0x3A, 629 | VU0_S2_VWAITQ = 0x3B, 630 | VU0_S2_VMTIR = 0x3C, 631 | VU0_S2_VMFIR = 0x3D, 632 | VU0_S2_VILWR = 0x3E, 633 | VU0_S2_VISWR = 0x3F, 634 | VU0_S2_VRNEXT = 0x40, 635 | VU0_S2_VRGET = 0x41, 636 | VU0_S2_VRINIT = 0x42, 637 | VU0_S2_VRXOR = 0x43 638 | }; 639 | 640 | // // VU0 macro instruction function codes (subset - there are many more) 641 | // enum VU0MacroFunctions 642 | // { 643 | // VU0_VADD = 0x00, 644 | // VU0_VSUB = 0x01, 645 | // VU0_VMUL = 0x02, 646 | // VU0_VDIV = 0x03, 647 | // VU0_VSQRT = 0x04, 648 | // VU0_VRSQRT = 0x05, 649 | // VU0_VMULQ = 0x06, 650 | // VU0_VIADD = 0x10, 651 | // VU0_VISUB = 0x11, 652 | // VU0_VIADDI = 0x12, 653 | // VU0_VIAND = 0x13, 654 | // VU0_VIOR = 0x14, 655 | // VU0_VILWR = 0x15, 656 | // VU0_VISWR = 0x16, 657 | // VU0_VCALLMS = 0x20, 658 | // VU0_VCALLMSR = 0x21, 659 | // VU0_VRGET = 0x3F, // Get VU0 R register 660 | // VU0_VSUB_XYZ = 0x38 // Subtract with component selection 661 | // }; 662 | 663 | // enum VU0SpecialFormats 664 | // { 665 | // VU0_BC2F = 0x11, // Branch on VU0 condition false 666 | // VU0_MTVUCF = 0x13, // Move To VU0 control/flag register 667 | // VU0_CTCVU = 0x18, // Control To VU0 with specific functionality 668 | // VU0_VMTIR = 0x1C, // Move To VU0 I Register 669 | // VU0_VCLIP = 0x1E, // VU0 Clipping operation 670 | // VU0_VLDQ = 0x1F // VU0 Load/Store Quad with Decrement 671 | // }; 672 | 673 | // VU0 Control Register Numbers (used with CFC2/CTC2) 674 | enum VU0ControlRegisters 675 | { 676 | VU0_CR_STATUS = 0, // Status/Control register 677 | VU0_CR_MAC = 1, // MAC flags register 678 | VU0_CR_CLIP = 5, // Clipping flags register 679 | VU0_CR_R = 3, // R register (Random number) 680 | VU0_CR_I = 4, // I register (Immediate) 681 | 682 | // Add missing registers 683 | VU0_CR_VPU_STAT = 2, // VPU-STAT register 684 | VU0_CR_TPC = 6, // T (program counter) register 685 | VU0_CR_CMSAR0 = 7, // Call/return address 0 686 | VU0_CR_FBRST = 8, // VIF/VU reset register 687 | VU0_CR_VPU_STAT2 = 9, // VPU-STAT register 2 688 | VU0_CR_TPC2 = 10, // T (program counter) register 2 689 | VU0_CR_CMSAR1 = 11, // Call/return address 1 690 | VU0_CR_FBRST2 = 12, // VIF/VU reset register 2 691 | VU0_CR_VPU_STAT3 = 13, // VPU-STAT register 3 692 | VU0_CR_CMSAR2 = 14, // Call/return address 2 693 | VU0_CR_FBRST3 = 15, // VIF/VU reset register 3 694 | VU0_CR_VPU_STAT4 = 16, // VPU-STAT register 4 695 | VU0_CR_CMSAR3 = 17, // Call/return address 3 696 | VU0_CR_FBRST4 = 18, // VIF/VU reset register 4 697 | VU0_CR_ACC = 20, // Accumulator register 698 | VU0_CR_INFO = 21, // Information register 699 | VU0_CR_CLIP2 = 22, // Clipping flags register 2 700 | VU0_CR_P = 26, // P register 701 | VU0_CR_XITOP = 27, // XITOP register 702 | VU0_CR_ITOP = 28, // ITOP register 703 | VU0_CR_TOP = 29 // TOP register 704 | }; 705 | enum VU0OPSFunctions 706 | { 707 | VU0OPS_QMFC2_NI = 0x00, // Non-incrementing QMFC2 708 | VU0OPS_QMFC2_I = 0x01, // Incrementing QMFC2 709 | VU0OPS_QMTC2_NI = 0x02, // Non-incrementing QMTC2 710 | VU0OPS_QMTC2_I = 0x03, // Incrementing QMTC2 711 | VU0OPS_VMFIR = 0x04, // Move From Integer Register 712 | VU0OPS_VXITOP = 0x08, // Execute Interrupt on VU0 713 | VU0OPS_VWAITQ = 0x3B, // Wait for Q register operations to complete 714 | VU0OPS_VMFHL = 0x1C, // Move from High / Low(similar to PMFHL) 715 | VU0OPS_VMTIR = 0x3C // Move to VU0 I Register with function code 716 | }; 717 | 718 | enum VU0OPSFunctionsExtra 719 | { 720 | VU0_VCALLMS_DIRECT = 0x00, 721 | VU0_VCALLMS_REG = 0x01 722 | }; 723 | 724 | enum VU0SpecialMasks 725 | { 726 | VU0_FIELD_MASK = 0xF, // Mask for vector field selection 727 | VU0_FIELD_SHIFT = 21, // Shift amount for field selection 728 | VU0_STORE_BIT = 0x80, // Bit indicating store operation 729 | VU0_SUBOP_MASK = 0x1F, // Mask for sub-operation code 730 | VU0_SUBOP_SHIFT = 6, // Shift amount for sub-operation 731 | VU0_SQC0 = 0x8 // SQC0 instruction code 732 | }; 733 | 734 | enum VU0FormatsExtra 735 | { 736 | VU0_FMT_MACRO_MOVE = 0x1, // Move between VU registers format 737 | VU0_FMT_VIF_STATUS = 0x5, // VIF status operations 738 | VU0_FMT_VCALLMS = 0x14, // VU0 microprogram call format 739 | VU0_FMT_LQSQ_COP0 = 0x1B // Load/store quad VU0 format 740 | }; 741 | 742 | // Instruction decoding helper macros 743 | #define OPCODE(inst) ((inst >> 26) & 0x3F) 744 | #define RS(inst) ((inst >> 21) & 0x1F) 745 | #define RT(inst) ((inst >> 16) & 0x1F) 746 | #define RD(inst) ((inst >> 11) & 0x1F) 747 | #define SA(inst) ((inst >> 6) & 0x1F) 748 | #define FUNCTION(inst) ((inst) & 0x3F) 749 | #define IMMEDIATE(inst) ((inst) & 0xFFFF) 750 | #define SIMMEDIATE(inst) ((int32_t)(int16_t)((inst) & 0xFFFF)) 751 | #define TARGET(inst) ((inst) & 0x3FFFFFF) 752 | #define COP_FUNCT(inst) ((uint8_t)((inst) & 0x3F)) // Function field for COPz CO instructions 753 | #define FPU_FMT(inst) ((uint8_t)(((inst) >> 21) & 0x1F)) // Format field for FPU instructions 754 | #define FT(inst) RT(inst) // Target FPU register 755 | #define FS(inst) RD(inst) // Source FPU register 756 | #define FD(inst) SA(inst) // Destination FPU register 757 | #define VU_I(inst) ((inst >> 25) & 0x1) // VU Upper I bit 758 | #define VU_E(inst) ((inst >> 24) & 0x1) // VU Upper E bit 759 | #define VU_M(inst) ((inst >> 23) & 0x1) // VU Upper M bit (VU0 only) 760 | #define VU_D(inst) ((inst >> 22) & 0x1) // VU Upper D bit 761 | #define VU_T(inst) ((inst >> 21) & 0x1) // VU Upper T bit 762 | #define VU_DEST(inst) ((uint8_t)(((inst) >> 16) & 0xF)) // VU Destination mask 763 | #define VU_FSF(inst) ((uint8_t)(((inst) >> 10) & 0x3)) // VU Source Field F 764 | #define VU_FTF(inst) ((uint8_t)(((inst) >> 8) & 0x3)) // VU Target Field F 765 | #define VU_FD(inst) ((uint8_t)(((inst) >> 11) & 0x1F)) // VU Destination Vector Register 766 | #define VU_FS(inst) ((uint8_t)(((inst) >> 6) & 0x1F)) // VU Source Vector Register 767 | #define VU_FT(inst) ((uint8_t)((inst) & 0x1F)) // VU Target Vector Register 768 | #define VU_IS(inst) RT(inst) // VU Source Integer Register 769 | #define VU_IT(inst) RD(inst) // VU Target Integer Register 770 | #define VU_ID(inst) SA(inst) // VU Destination Integer Register 771 | #define VU_IMM5(inst) ((uint8_t)((inst >> 6) & 0x1F)) // VU 5-bit immediate 772 | #define VU_IMM11(inst) ((uint16_t)((inst) & 0x7FF)) // VU 11-bit immediate 773 | #define VU_SIMM11(inst) ((int16_t)(((inst & 0x7FF) ^ 0x400) - 0x400)) // VU 11-bit signed immediate 774 | #define VU_IMM15(inst) ((uint16_t)((inst) & 0x7FFF)) // VU 15-bit immediate 775 | 776 | } // namespace ps2recomp 777 | 778 | #endif // PS2RECOMP_INSTRUCTIONS_H -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/ps2_recompiler.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_PS2_RECOMPILER_H 2 | #define PS2RECOMP_PS2_RECOMPILER_H 3 | 4 | #include "ps2recomp/types.h" 5 | #include "ps2recomp/elf_parser.h" 6 | #include "ps2recomp/r5900_decoder.h" 7 | #include "ps2recomp/code_generator.h" 8 | #include "ps2recomp/config_manager.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace ps2recomp 15 | { 16 | 17 | class PS2Recompiler 18 | { 19 | public: 20 | PS2Recompiler(const std::string &configPath); 21 | ~PS2Recompiler() = default; 22 | 23 | bool initialize(); 24 | bool recompile(); 25 | void generateOutput(); 26 | 27 | private: 28 | ConfigManager m_configManager; 29 | std::unique_ptr m_elfParser; 30 | std::unique_ptr m_decoder; 31 | std::unique_ptr m_codeGenerator; 32 | RecompilerConfig m_config; 33 | 34 | std::vector m_functions; 35 | std::vector m_symbols; 36 | std::vector
m_sections; 37 | std::vector m_relocations; 38 | 39 | std::unordered_map> m_decodedFunctions; 40 | std::unordered_map m_skipFunctions; 41 | std::map m_generatedStubs; 42 | 43 | bool decodeFunction(Function &function); 44 | bool shouldSkipFunction(const std::string &name) const; 45 | std::string generateRuntimeHeader(); 46 | bool generateFunctionHeader(); 47 | bool writeToFile(const std::string &path, const std::string &content); 48 | std::filesystem::path getOutputPath(const Function &function) const; 49 | }; 50 | 51 | } 52 | 53 | #endif -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/r5900_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_R5900_DECODER_H 2 | #define PS2RECOMP_R5900_DECODER_H 3 | 4 | #include "ps2recomp/types.h" 5 | #include "ps2recomp/instructions.h" 6 | #include 7 | 8 | namespace ps2recomp 9 | { 10 | 11 | class R5900Decoder 12 | { 13 | public: 14 | R5900Decoder(); 15 | ~R5900Decoder(); 16 | 17 | Instruction decodeInstruction(uint32_t address, uint32_t rawInstruction); 18 | 19 | bool isBranchInstruction(const Instruction &inst) const; 20 | bool isJumpInstruction(const Instruction &inst) const; 21 | bool isCallInstruction(const Instruction &inst) const; 22 | bool isReturnInstruction(const Instruction &inst) const; 23 | bool isMMIInstruction(const Instruction &inst) const; 24 | bool isVUInstruction(const Instruction &inst) const; 25 | bool isStore(const Instruction &inst) const; 26 | bool isLoad(const Instruction &inst) const; 27 | bool hasDelaySlot(const Instruction &inst) const; 28 | 29 | uint32_t getBranchTarget(const Instruction &inst) const; 30 | uint32_t getJumpTarget(const Instruction &inst) const; 31 | 32 | private: 33 | void decodeRType(Instruction &inst) const; 34 | void decodeIType(Instruction &inst) const; 35 | void decodeJType(Instruction &inst) const; 36 | 37 | void decodeSpecial(Instruction &inst) const; 38 | void decodeRegimm(Instruction &inst) const; 39 | 40 | void decodeCOP0(Instruction& inst) const; 41 | void decodeCOP1(Instruction &inst) const; 42 | void decodeCOP2(Instruction &inst) const; 43 | 44 | void decodeMMI(Instruction &inst) const; 45 | void decodeMMI0(Instruction &inst) const; 46 | void decodeMMI1(Instruction &inst) const; 47 | void decodeMMI2(Instruction &inst) const; 48 | void decodeMMI3(Instruction &inst) const; 49 | 50 | void decodePMFHL(Instruction &inst) const; 51 | }; 52 | 53 | } // namespace ps2recomp 54 | 55 | #endif // PS2RECOMP_R5900_DECODER_H -------------------------------------------------------------------------------- /ps2xRecomp/include/ps2recomp/types.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2RECOMP_TYPES_H 2 | #define PS2RECOMP_TYPES_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ps2recomp 11 | { 12 | 13 | // Instruction representation 14 | struct Instruction 15 | { 16 | uint32_t address; 17 | uint32_t opcode; 18 | uint32_t rs; // Source register 19 | uint32_t rt; // Target register 20 | uint32_t rd; // Destination register 21 | uint32_t sa; // Shift amount 22 | uint32_t function; // Function code for R-type instructions 23 | uint32_t immediate; // Immediate value for I-type instructions 24 | uint32_t simmediate; // Sign-extended immediate value (extended to 32 bits) 25 | uint32_t target; // Jump target for J-type instructions 26 | uint32_t raw; // Raw instruction value 27 | 28 | // Instruction type flags 29 | bool isMMI; // Is MMI instruction (PS2 specific) 30 | bool isVU; // Is VU instruction (PS2 specific) 31 | bool isBranch; // Is branch instruction 32 | bool isJump; // Is jump instruction 33 | bool isCall; // Is function call 34 | bool isReturn; // Is return instruction 35 | bool hasDelaySlot; // Has delay slot 36 | bool isMultimedia; // PS2-specific multimedia operations 37 | bool isStore; // Is store instruction 38 | bool isLoad; // Is load instruction 39 | 40 | // Additional PS2-specific fields 41 | uint8_t mmiType; // 0=MMI0, 1=MMI1, 2=MMI2, 3=MMI3 42 | uint8_t mmiFunction; // Function within MMI type 43 | uint8_t pmfhlVariation; // For PMFHL instructions 44 | uint8_t vuFunction; // For VU instructions 45 | 46 | struct 47 | { 48 | bool isVector; // Uses vector operations 49 | bool usesQReg; // Uses Q register 50 | bool usesPReg; // Uses P register 51 | bool modifiesMAC; // Modifies MAC flags 52 | uint8_t vectorField; // xyzw field mask 53 | uint8_t fsf; // Field select for FS reg (bits 10-11) 54 | uint8_t ftf; // Source Field select for FT reg (bits 8-9) 55 | } vectorInfo; 56 | 57 | struct 58 | { 59 | bool modifiesGPR; // Modifies general purpose register 60 | bool modifiesFPR; // Modifies floating point register 61 | bool modifiesVFR; // Modifies vector float register 62 | bool modifiesVIR; // Modifies vector integer register 63 | bool modifiesVIC; // Modifies vector integer control register 64 | bool modifiesMemory; // Modifies memory 65 | bool modifiesControl; // Modifies control register 66 | } modificationInfo; 67 | 68 | Instruction() : address(0), opcode(0), rs(0), rt(0), rd(0), sa(0), function(0), 69 | immediate(0), simmediate(0), target(0), raw(0), 70 | isMMI(false), isVU(false), isBranch(false), isJump(false), isCall(false), 71 | isReturn(false), hasDelaySlot(false), isMultimedia(false), isStore(false), isLoad(false), 72 | mmiType(0), mmiFunction(0), pmfhlVariation(0), vuFunction(0) 73 | { 74 | vectorInfo = {}; 75 | modificationInfo = {}; 76 | } 77 | }; 78 | 79 | // Function information 80 | struct Function 81 | { 82 | std::string name; 83 | uint32_t start; 84 | uint32_t end; 85 | std::vector instructions; 86 | std::vector callers; 87 | std::vector callees; 88 | bool isRecompiled; 89 | bool isStub; 90 | }; 91 | 92 | // Symbol information 93 | struct Symbol 94 | { 95 | std::string name; 96 | uint32_t address; 97 | uint32_t size; 98 | bool isFunction; 99 | bool isImported; 100 | bool isExported; 101 | }; 102 | 103 | // Section information 104 | struct Section 105 | { 106 | std::string name; 107 | uint32_t address; 108 | uint32_t size; 109 | uint32_t offset; 110 | bool isCode; 111 | bool isData; 112 | bool isBSS; 113 | bool isReadOnly; 114 | uint8_t *data; 115 | }; 116 | 117 | // Relocation information 118 | struct Relocation 119 | { 120 | uint32_t offset; 121 | uint32_t info; 122 | uint32_t symbol; 123 | uint32_t type; 124 | int32_t addend; 125 | }; 126 | 127 | // Jump table entry 128 | struct JumpTableEntry 129 | { 130 | uint32_t index; 131 | uint32_t target; 132 | }; 133 | 134 | // Jump table 135 | struct JumpTable 136 | { 137 | uint32_t address; 138 | uint32_t baseRegister; 139 | std::vector entries; 140 | }; 141 | 142 | // Control flow graph 143 | struct CFGNode 144 | { 145 | uint32_t startAddress; 146 | uint32_t endAddress; 147 | std::vector instructions; 148 | std::vector predecessors; 149 | std::vector successors; 150 | bool isJumpTarget; 151 | bool hasJumpTable; 152 | JumpTable jumpTable; 153 | }; 154 | 155 | using CFG = std::unordered_map; 156 | 157 | // Function call 158 | struct FunctionCall 159 | { 160 | uint32_t callerAddress; 161 | uint32_t calleeAddress; 162 | std::string calleeName; 163 | }; 164 | 165 | // Recompiler configuration 166 | struct RecompilerConfig 167 | { 168 | std::string inputPath; 169 | std::string outputPath; 170 | bool singleFileOutput; 171 | std::vector skipFunctions; 172 | std::unordered_map patches; 173 | std::map stubImplementations; 174 | }; 175 | 176 | } // namespace ps2recomp 177 | 178 | #endif // PS2RECOMP_TYPES_H -------------------------------------------------------------------------------- /ps2xRecomp/src/config_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2recomp/config_manager.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ps2recomp 8 | { 9 | 10 | ConfigManager::ConfigManager(const std::string &configPath) 11 | : m_configPath(configPath) 12 | { 13 | } 14 | 15 | ConfigManager::~ConfigManager() = default; 16 | 17 | RecompilerConfig ConfigManager::loadConfig() 18 | { 19 | RecompilerConfig config; 20 | 21 | try 22 | { 23 | auto data = toml::parse(m_configPath); 24 | 25 | config.inputPath = toml::find(data, "general", "input"); 26 | config.outputPath = toml::find(data, "general", "output"); 27 | config.singleFileOutput = toml::find(data, "general", "single_file_output"); 28 | 29 | config.skipFunctions = toml::find>(data, "general", "skip"); 30 | 31 | if (data.contains("patches") && data.at("patches").is_table()) 32 | { 33 | const auto &patches = toml::find(data, "patches"); 34 | 35 | if (patches.contains("instructions") && patches.at("instructions").is_array()) 36 | { 37 | const auto &instPatches = toml::find(patches, "instructions").as_array(); 38 | for (const auto &patch : instPatches) 39 | { 40 | if (patch.contains("address") && patch.contains("value")) 41 | { 42 | uint32_t address = std::stoul(toml::find(patch, "address"), nullptr, 0); 43 | std::string value = toml::find(patch, "value"); 44 | config.patches[address] = value; 45 | } 46 | } 47 | } 48 | } 49 | 50 | if (data.contains("stub_implementations") && data.at("stub_implementations").is_table()) 51 | { 52 | const auto &stubImpls = toml::find(data, "stub_implementations"); 53 | for (const auto &item : stubImpls.as_table()) 54 | { 55 | const std::string &funcName = item.first; 56 | const std::string &implementation = toml::find(stubImpls, funcName); 57 | config.stubImplementations[funcName] = implementation; 58 | } 59 | } 60 | } 61 | catch (const std::exception &e) 62 | { 63 | std::cerr << "Error parsing configuration file: " << e.what() << std::endl; 64 | throw; 65 | } 66 | 67 | return config; 68 | } 69 | 70 | void ConfigManager::saveConfig(const RecompilerConfig &config) 71 | { 72 | toml::value data; 73 | 74 | toml::table general; 75 | general["input"] = config.inputPath; 76 | general["output"] = config.outputPath; 77 | general["single_file_output"] = config.singleFileOutput; 78 | data["general"] = general; 79 | 80 | toml::array skips; 81 | for (const auto &skip : config.skipFunctions) 82 | { 83 | skips.push_back(skip); 84 | } 85 | data["skip"] = skips; 86 | 87 | toml::table patches; 88 | toml::array instPatches; 89 | for (const auto &patch : config.patches) 90 | { 91 | toml::table p; 92 | p["address"] = "0x" + std::to_string(patch.first); 93 | p["value"] = patch.second; 94 | instPatches.push_back(p); 95 | } 96 | patches["instructions"] = instPatches; 97 | data["patches"] = patches; 98 | 99 | if (!config.stubImplementations.empty()) 100 | { 101 | toml::table stubImpls; 102 | for (const auto &impl : config.stubImplementations) 103 | { 104 | stubImpls[impl.first] = impl.second; 105 | } 106 | data["stub_implementations"] = stubImpls; 107 | } 108 | 109 | std::ofstream file(m_configPath); 110 | if (!file) 111 | { 112 | throw std::runtime_error("Failed to open file for writing: " + m_configPath); 113 | } 114 | 115 | file << data; 116 | } 117 | 118 | } // namespace ps2recomp -------------------------------------------------------------------------------- /ps2xRecomp/src/elf_parser.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2recomp/elf_parser.h" 2 | #include 3 | #include 4 | 5 | namespace ps2recomp 6 | { 7 | 8 | ElfParser::ElfParser(const std::string &filePath) 9 | : m_filePath(filePath), m_elf(new ELFIO::elfio()) 10 | { 11 | } 12 | 13 | bool ElfParser::isExecutableSection(const ELFIO::section *section) const 14 | { 15 | return (section->get_flags() & ELFIO::SHF_EXECINSTR) != 0; 16 | } 17 | 18 | bool ElfParser::isDataSection(const ELFIO::section *section) const 19 | { 20 | return (section->get_flags() & ELFIO::SHF_ALLOC) != 0 && 21 | !(section->get_flags() & ELFIO::SHF_EXECINSTR); 22 | } 23 | 24 | std::vector ElfParser::extractFunctions() 25 | { 26 | std::vector functions; 27 | 28 | for (const auto &symbol : m_symbols) 29 | { 30 | if (symbol.isFunction && symbol.size > 0) 31 | { 32 | Function func; 33 | func.name = symbol.name; 34 | func.start = symbol.address; 35 | func.end = symbol.address + symbol.size; 36 | func.isRecompiled = false; 37 | func.isStub = false; 38 | 39 | functions.push_back(func); 40 | } 41 | } 42 | 43 | std::sort(functions.begin(), functions.end(), 44 | [](const Function &a, const Function &b) 45 | { return a.start < b.start; }); 46 | 47 | return functions; 48 | } 49 | 50 | std::vector ElfParser::extractSymbols() 51 | { 52 | return m_symbols; 53 | } 54 | 55 | std::vector
ElfParser::getSections() 56 | { 57 | return m_sections; 58 | } 59 | 60 | std::vector ElfParser::getRelocations() 61 | { 62 | return m_relocations; 63 | } 64 | 65 | bool ElfParser::isValidAddress(uint32_t address) const 66 | { 67 | for (const auto §ion : m_sections) 68 | { 69 | if (address >= section.address && address < (section.address + section.size)) 70 | { 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | 78 | uint32_t ElfParser::readWord(uint32_t address) const 79 | { 80 | for (const auto §ion : m_sections) 81 | { 82 | if (address >= section.address && address < (section.address + section.size)) 83 | { 84 | if (section.data) 85 | { 86 | uint32_t offset = address - section.address; 87 | return *reinterpret_cast(section.data + offset); 88 | } 89 | } 90 | } 91 | 92 | throw std::runtime_error("Invalid address for readWord: " + std::to_string(address)); 93 | } 94 | 95 | uint8_t *ElfParser::getSectionData(const std::string §ionName) 96 | { 97 | for (const auto §ion : m_sections) 98 | { 99 | if (section.name == sectionName) 100 | { 101 | return section.data; 102 | } 103 | } 104 | 105 | return nullptr; 106 | } 107 | 108 | uint32_t ElfParser::getSectionAddress(const std::string §ionName) 109 | { 110 | for (const auto §ion : m_sections) 111 | { 112 | if (section.name == sectionName) 113 | { 114 | return section.address; 115 | } 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | uint32_t ElfParser::getSectionSize(const std::string §ionName) 122 | { 123 | for (const auto §ion : m_sections) 124 | { 125 | if (section.name == sectionName) 126 | { 127 | return section.size; 128 | } 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | ElfParser::~ElfParser() = default; 135 | 136 | bool ElfParser::parse() 137 | { 138 | if (!m_elf->load(m_filePath)) 139 | { 140 | std::cerr << "Error: Could not load ELF file: " << m_filePath << std::endl; 141 | return false; 142 | } 143 | 144 | // Check if this is a PS2 ELF (MIPS R5900) 145 | if (m_elf->get_machine() != ELFIO::EM_MIPS) 146 | { 147 | std::cerr << "Error: Not a MIPS ELF file" << std::endl; 148 | return false; 149 | } 150 | 151 | loadSections(); 152 | loadSymbols(); 153 | loadRelocations(); 154 | 155 | return true; 156 | } 157 | 158 | void ElfParser::loadSections() 159 | { 160 | m_sections.clear(); 161 | 162 | ELFIO::Elf_Half sec_num = m_elf->sections.size(); 163 | 164 | for (ELFIO::Elf_Half i = 0; i < sec_num; ++i) 165 | { 166 | ELFIO::section *psec = m_elf->sections[i]; 167 | 168 | Section section; 169 | section.name = psec->get_name(); 170 | section.address = psec->get_address(); 171 | section.size = psec->get_size(); 172 | section.offset = psec->get_offset(); 173 | section.isCode = isExecutableSection(psec); 174 | section.isData = isDataSection(psec); 175 | section.isBSS = (psec->get_type() == ELFIO::SHT_NOBITS); 176 | section.isReadOnly = !(psec->get_flags() & ELFIO::SHF_WRITE); 177 | 178 | if (psec->get_size() > 0 && psec->get_type() != ELFIO::SHT_NOBITS) 179 | { 180 | section.data = (uint8_t *)psec->get_data(); 181 | } 182 | else 183 | { 184 | section.data = nullptr; 185 | } 186 | 187 | m_sections.push_back(section); 188 | } 189 | } 190 | 191 | void ElfParser::loadSymbols() 192 | { 193 | m_symbols.clear(); 194 | 195 | for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i) 196 | { 197 | ELFIO::section *psec = m_elf->sections[i]; 198 | 199 | if (psec->get_type() == ELFIO::SHT_SYMTAB || psec->get_type() == ELFIO::SHT_DYNSYM) 200 | { 201 | ELFIO::symbol_section_accessor symbols(*m_elf, psec); 202 | 203 | ELFIO::Elf_Xword sym_num = symbols.get_symbols_num(); 204 | 205 | ELFIO::section *pstrSec = m_elf->sections[psec->get_link()]; 206 | ELFIO::string_section_accessor strings(pstrSec); 207 | 208 | for (ELFIO::Elf_Xword j = 0; j < sym_num; ++j) 209 | { 210 | std::string name; 211 | ELFIO::Elf64_Addr value; 212 | ELFIO::Elf_Xword size; 213 | unsigned char bind; 214 | unsigned char type; 215 | ELFIO::Elf_Half section_index; 216 | unsigned char other; 217 | 218 | symbols.get_symbol(j, name, value, size, bind, type, section_index, other); 219 | 220 | // Skip empty symbols or those with invalid section index 221 | if (name.empty() || section_index == ELFIO::SHN_UNDEF) 222 | { 223 | continue; 224 | } 225 | 226 | Symbol symbol; 227 | symbol.name = name; 228 | symbol.address = static_cast(value); 229 | symbol.size = static_cast(size); 230 | symbol.isFunction = (type == ELFIO::STT_FUNC); 231 | symbol.isImported = (bind == ELFIO::STB_GLOBAL && section_index == ELFIO::SHN_UNDEF); 232 | symbol.isExported = (bind == ELFIO::STB_GLOBAL && section_index != ELFIO::SHN_UNDEF); 233 | 234 | m_symbols.push_back(symbol); 235 | } 236 | } 237 | } 238 | } 239 | 240 | void ElfParser::loadRelocations() 241 | { 242 | m_relocations.clear(); 243 | 244 | for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i) 245 | { 246 | ELFIO::section *psec = m_elf->sections[i]; 247 | 248 | if (psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA) 249 | { 250 | ELFIO::relocation_section_accessor relocs(*m_elf, psec); 251 | 252 | ELFIO::section *symSec = m_elf->sections[psec->get_link()]; 253 | ELFIO::symbol_section_accessor symbols(*m_elf, symSec); 254 | 255 | ELFIO::section *strSec = m_elf->sections[symSec->get_link()]; 256 | 257 | ELFIO::string_section_accessor strings(strSec); 258 | 259 | for (ELFIO::Elf_Xword j = 0; j < relocs.get_entries_num(); ++j) 260 | { 261 | ELFIO::Elf64_Addr offset; 262 | ELFIO::Elf_Word symbol; 263 | ELFIO::Elf_Word type; 264 | ELFIO::Elf_Sxword addend; 265 | 266 | // Always use the 5-parameter version 267 | if (psec->get_type() == ELFIO::SHT_REL) 268 | { 269 | // Pass addend even for REL sections 270 | relocs.get_entry(j, offset, symbol, type, addend); 271 | // Reset addend for REL sections since it's not part of the section 272 | addend = 0; 273 | } 274 | else 275 | { 276 | relocs.get_entry(j, offset, symbol, type, addend); 277 | } 278 | 279 | Relocation reloc; 280 | reloc.offset = static_cast(offset); 281 | reloc.info = (symbol << 8) | (type & 0xFF); 282 | reloc.symbol = symbol; 283 | reloc.type = type; 284 | reloc.addend = static_cast(addend); 285 | 286 | m_relocations.push_back(reloc); 287 | } 288 | } 289 | } 290 | } 291 | } -------------------------------------------------------------------------------- /ps2xRecomp/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2recomp/ps2_recompiler.h" 2 | #include 3 | #include 4 | 5 | using namespace ps2recomp; 6 | 7 | void printUsage() 8 | { 9 | std::cout << "PS2Recomp - A static recompiler for PlayStation 2 ELF files\n"; 10 | std::cout << "Usage: ps2recomp \n"; 11 | std::cout << " config.toml: Configuration file for the recompiler\n"; 12 | } 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | if (argc < 2) 17 | { 18 | printUsage(); 19 | return 1; 20 | } 21 | 22 | std::string configPath = argv[1]; 23 | 24 | try 25 | { 26 | PS2Recompiler recompiler(configPath); 27 | 28 | if (!recompiler.initialize()) 29 | { 30 | std::cerr << "Failed to initialize recompiler\n"; 31 | return 1; 32 | } 33 | 34 | if (!recompiler.recompile()) 35 | { 36 | std::cerr << "Recompilation failed\n"; 37 | return 1; 38 | } 39 | 40 | recompiler.generateOutput(); 41 | 42 | std::cout << "Recompilation completed successfully\n"; 43 | return 0; 44 | } 45 | catch (const std::exception &e) 46 | { 47 | std::cerr << "Error: " << e.what() << std::endl; 48 | return 1; 49 | } 50 | } -------------------------------------------------------------------------------- /ps2xRecomp/src/ps2_recompiler.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2recomp/ps2_recompiler.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace fs = std::filesystem; 10 | 11 | namespace ps2recomp 12 | { 13 | 14 | PS2Recompiler::PS2Recompiler(const std::string &configPath) 15 | : m_configManager(configPath) 16 | { 17 | } 18 | 19 | bool PS2Recompiler::initialize() 20 | { 21 | try 22 | { 23 | m_config = m_configManager.loadConfig(); 24 | 25 | for (const auto &name : m_config.skipFunctions) 26 | { 27 | m_skipFunctions[name] = true; 28 | } 29 | 30 | m_elfParser = std::make_unique(m_config.inputPath); 31 | if (!m_elfParser->parse()) 32 | { 33 | std::cerr << "Failed to parse ELF file: " << m_config.inputPath << std::endl; 34 | return false; 35 | } 36 | 37 | m_functions = m_elfParser->extractFunctions(); 38 | m_symbols = m_elfParser->extractSymbols(); 39 | m_sections = m_elfParser->getSections(); 40 | m_relocations = m_elfParser->getRelocations(); 41 | 42 | std::cout << "Extracted " << m_functions.size() << " functions, " 43 | << m_symbols.size() << " symbols, " 44 | << m_sections.size() << " sections, " 45 | << m_relocations.size() << " relocations." << std::endl; 46 | 47 | m_decoder = std::make_unique(); 48 | m_codeGenerator = std::make_unique(m_symbols); 49 | 50 | fs::create_directories(m_config.outputPath); 51 | 52 | return true; 53 | } 54 | catch (const std::exception &e) 55 | { 56 | std::cerr << "Error during initialization: " << e.what() << std::endl; 57 | return false; 58 | } 59 | } 60 | 61 | bool PS2Recompiler::recompile() 62 | { 63 | try 64 | { 65 | std::cout << "Recompiling " << m_functions.size() << " functions..." << std::endl; 66 | 67 | std::string runtimeHeader = generateRuntimeHeader(); 68 | fs::path runtimeHeaderPath = fs::path(m_config.outputPath) / "ps2_runtime_macros.h"; 69 | 70 | writeToFile(runtimeHeaderPath.string(), runtimeHeader); 71 | 72 | size_t processedCount = 0; 73 | for (auto &function : m_functions) 74 | { 75 | std::cout << "processing function: " << function.name << std::endl; 76 | 77 | if (shouldSkipFunction(function.name)) 78 | { 79 | std::cout << "Skipping function: " << function.name << std::endl; 80 | continue; 81 | } 82 | 83 | if (!decodeFunction(function)) 84 | { 85 | std::cerr << "Failed to decode function: " << function.name << std::endl; 86 | return false; 87 | } 88 | 89 | function.isRecompiled = true; 90 | #if _DEBUG 91 | processedCount++; 92 | if (processedCount % 100 == 0) 93 | { 94 | std::cout << "Processed " << processedCount << " functions." << std::endl; 95 | } 96 | #endif 97 | } 98 | 99 | std::cout << "Recompilation completed successfully." << std::endl; 100 | return true; 101 | } 102 | catch (const std::exception &e) 103 | { 104 | std::cerr << "Error during recompilation: " << e.what() << std::endl; 105 | return false; 106 | } 107 | } 108 | 109 | void PS2Recompiler::generateOutput() 110 | { 111 | try 112 | { 113 | if (m_config.singleFileOutput) 114 | { 115 | std::stringstream combinedOutput; 116 | 117 | combinedOutput << "#include \"ps2_runtime_macros.h\"\n"; 118 | combinedOutput << "#include \"ps2_runtime.h\"\n\n"; 119 | 120 | for (const auto &function : m_functions) 121 | { 122 | if (!function.isRecompiled) 123 | { 124 | continue; 125 | } 126 | 127 | if (function.isStub) 128 | { 129 | combinedOutput << m_generatedStubs[function.start] << "\n\n"; 130 | } 131 | else 132 | { 133 | const auto &instructions = m_decodedFunctions[function.start]; 134 | std::string code = m_codeGenerator->generateFunction(function, instructions, false); 135 | combinedOutput << code << "\n\n"; 136 | } 137 | } 138 | 139 | fs::path outputPath = fs::path(m_config.outputPath) / "recompiled.cpp"; 140 | writeToFile(outputPath.string(), combinedOutput.str()); 141 | std::cout << "Wrote recompiled to combined output to: " << outputPath << std::endl; 142 | } 143 | else 144 | { 145 | generateFunctionHeader(); 146 | for (const auto &function : m_functions) 147 | { 148 | if (!function.isRecompiled || function.isStub) 149 | { 150 | continue; 151 | } 152 | 153 | std::string code; 154 | if (function.isStub) 155 | { 156 | code = m_generatedStubs[function.start]; 157 | } 158 | else 159 | { 160 | const auto &instructions = m_decodedFunctions[function.start]; 161 | code = m_codeGenerator->generateFunction(function, instructions, true); 162 | } 163 | 164 | fs::path outputPath = getOutputPath(function); 165 | fs::create_directories(outputPath.parent_path()); 166 | writeToFile(outputPath.string(), code); 167 | } 168 | 169 | std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl; 170 | } 171 | 172 | std::string registerFunctions = m_codeGenerator->generateFunctionRegistration( 173 | m_functions, m_generatedStubs); 174 | 175 | fs::path registerPath = fs::path(m_config.outputPath) / "register_functions.cpp"; 176 | writeToFile(registerPath.string(), registerFunctions); 177 | std::cout << "Generated function registration file: " << registerPath << std::endl; 178 | } 179 | catch (const std::exception &e) 180 | { 181 | std::cerr << "Error during output generation: " << e.what() << std::endl; 182 | } 183 | } 184 | 185 | bool PS2Recompiler::generateFunctionHeader() 186 | { 187 | try 188 | { 189 | std::stringstream ss; 190 | 191 | ss << "#ifndef PS2_RECOMPILED_FUNCTIONS_H\n"; 192 | ss << "#define PS2_RECOMPILED_FUNCTIONS_H\n\n"; 193 | 194 | ss << "#include \n\n"; 195 | ss << "struct R5900Context;\n\n"; 196 | 197 | for (const auto &function : m_functions) 198 | { 199 | if (function.isRecompiled) 200 | { 201 | ss << "void " << function.name << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n"; 202 | } 203 | } 204 | 205 | ss << "\n#endif // PS2_RECOMPILED_FUNCTIONS_H\n"; 206 | 207 | fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.h"; 208 | writeToFile(headerPath.string(), ss.str()); 209 | 210 | std::cout << "Generated function header file: " << headerPath << std::endl; 211 | return true; 212 | } 213 | catch (const std::exception &e) 214 | { 215 | std::cerr << "Error generating function header: " << e.what() << std::endl; 216 | return false; 217 | } 218 | } 219 | 220 | bool PS2Recompiler::decodeFunction(Function &function) 221 | { 222 | std::vector instructions; 223 | 224 | uint32_t start = function.start; 225 | uint32_t end = function.end; 226 | 227 | for (uint32_t address = start; address < end; address += 4) 228 | { 229 | try 230 | { 231 | if (!m_elfParser->isValidAddress(address)) 232 | { 233 | std::cerr << "Invalid address: 0x" << std::hex << address << std::dec 234 | << " in function: " << function.name << std::endl; 235 | return false; 236 | } 237 | 238 | uint32_t rawInstruction = m_elfParser->readWord(address); 239 | 240 | auto patchIt = m_config.patches.find(address); 241 | if (patchIt != m_config.patches.end()) 242 | { 243 | rawInstruction = std::stoul(patchIt->second, nullptr, 0); 244 | std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl; 245 | } 246 | 247 | Instruction inst = m_decoder->decodeInstruction(address, rawInstruction); 248 | 249 | instructions.push_back(inst); 250 | } 251 | catch (const std::exception &e) 252 | { 253 | std::cerr << "Error decoding instruction at 0x" << std::hex << address << std::dec 254 | << " in function: " << function.name << ": " << e.what() << std::endl; 255 | return false; 256 | } 257 | } 258 | 259 | m_decodedFunctions[function.start] = instructions; 260 | 261 | return true; 262 | } 263 | 264 | bool PS2Recompiler::shouldSkipFunction(const std::string &name) const 265 | { 266 | return m_skipFunctions.find(name) != m_skipFunctions.end(); 267 | } 268 | 269 | std::string PS2Recompiler::generateRuntimeHeader() 270 | { 271 | return m_codeGenerator->generateMacroHeader(); 272 | } 273 | 274 | bool PS2Recompiler::writeToFile(const std::string &path, const std::string &content) 275 | { 276 | std::ofstream file(path); 277 | if (!file) 278 | { 279 | std::cerr << "Failed to open file for writing: " << path << std::endl; 280 | return false; 281 | } 282 | 283 | file << content; 284 | file.close(); 285 | 286 | return true; 287 | } 288 | 289 | std::filesystem::path PS2Recompiler::getOutputPath(const Function &function) const 290 | { 291 | std::string safeName = function.name; 292 | 293 | std::replace_if(safeName.begin(), safeName.end(), [](char c) 294 | { return c == '/' || c == '\\' || c == ':' || c == '*' || 295 | c == '?' || c == '"' || c == '<' || c == '>' || 296 | c == '|' || c == '$'; }, '_'); 297 | 298 | if (safeName.empty()) 299 | { 300 | std::stringstream ss; 301 | ss << "func_" << std::hex << function.start; 302 | safeName = ss.str(); 303 | } 304 | 305 | std::filesystem::path outputPath = m_config.outputPath; 306 | outputPath /= safeName + ".cpp"; 307 | 308 | return outputPath; 309 | } 310 | } -------------------------------------------------------------------------------- /ps2xRecomp/src/r5900_decoder.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2recomp/r5900_decoder.h" 2 | #include 3 | 4 | namespace ps2recomp 5 | { 6 | 7 | R5900Decoder::R5900Decoder() 8 | { 9 | } 10 | 11 | R5900Decoder::~R5900Decoder() 12 | { 13 | } 14 | 15 | Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction) 16 | { 17 | Instruction inst; 18 | 19 | inst.address = address; 20 | inst.raw = rawInstruction; 21 | inst.opcode = OPCODE(rawInstruction); 22 | inst.rs = RS(rawInstruction); 23 | inst.rt = RT(rawInstruction); 24 | inst.rd = RD(rawInstruction); 25 | inst.sa = SA(rawInstruction); 26 | inst.function = FUNCTION(rawInstruction); 27 | inst.immediate = IMMEDIATE(rawInstruction); 28 | inst.simmediate = SIMMEDIATE(rawInstruction); 29 | inst.target = TARGET(rawInstruction); 30 | 31 | inst.isMMI = false; 32 | inst.isVU = false; 33 | inst.isBranch = false; 34 | inst.isJump = false; 35 | inst.isCall = false; 36 | inst.isReturn = false; 37 | inst.hasDelaySlot = false; 38 | inst.isMultimedia = false; 39 | inst.isLoad = false; 40 | inst.isStore = false; 41 | 42 | // Initialize the enhanced fields 43 | inst.mmiType = 0; 44 | inst.mmiFunction = 0; 45 | inst.pmfhlVariation = 0; 46 | inst.vuFunction = 0; 47 | 48 | inst.vectorInfo.isVector = false; 49 | inst.vectorInfo.usesQReg = false; 50 | inst.vectorInfo.usesPReg = false; 51 | inst.vectorInfo.modifiesMAC = false; 52 | inst.vectorInfo.vectorField = 0xF; // All fields (xyzw) 53 | 54 | inst.modificationInfo.modifiesGPR = false; 55 | inst.modificationInfo.modifiesFPR = false; 56 | inst.modificationInfo.modifiesVFR = false; 57 | inst.modificationInfo.modifiesVIR = false; 58 | inst.modificationInfo.modifiesVIC = false; 59 | inst.modificationInfo.modifiesMemory = false; 60 | inst.modificationInfo.modifiesControl = false; 61 | 62 | switch (inst.opcode) 63 | { 64 | case OPCODE_SPECIAL: 65 | decodeSpecial(inst); 66 | break; 67 | 68 | case OPCODE_REGIMM: 69 | decodeRegimm(inst); 70 | break; 71 | 72 | case OPCODE_J: 73 | decodeJType(inst); 74 | break; 75 | 76 | case OPCODE_JAL: 77 | decodeJType(inst); 78 | break; 79 | 80 | case OPCODE_BEQ: 81 | case OPCODE_BNE: 82 | case OPCODE_BLEZ: 83 | case OPCODE_BGTZ: 84 | case OPCODE_BEQL: 85 | case OPCODE_BNEL: 86 | case OPCODE_BLEZL: 87 | case OPCODE_BGTZL: 88 | decodeIType(inst); 89 | inst.isBranch = true; 90 | inst.hasDelaySlot = true; 91 | inst.modificationInfo.modifiesControl = true; // PC potentially 92 | break; 93 | 94 | case OPCODE_DADDI: 95 | case OPCODE_DADDIU: 96 | decodeIType(inst); 97 | if (inst.rt != 0) 98 | inst.modificationInfo.modifiesGPR = true; 99 | break; 100 | 101 | case OPCODE_MMI: 102 | decodeMMI(inst); 103 | break; 104 | 105 | case OPCODE_LQ: 106 | decodeIType(inst); 107 | inst.isLoad = true; 108 | inst.isMultimedia = true; // 128-bit load 109 | break; 110 | 111 | case OPCODE_SQ: 112 | decodeIType(inst); 113 | inst.isStore = true; 114 | inst.isMultimedia = true; // 128-bit store 115 | inst.modificationInfo.modifiesMemory = true; 116 | break; 117 | 118 | case OPCODE_LB: 119 | case OPCODE_LH: 120 | case OPCODE_LW: 121 | case OPCODE_LBU: 122 | case OPCODE_LHU: 123 | case OPCODE_LWU: 124 | case OPCODE_LD: 125 | inst.isLoad = true; 126 | if (inst.rt != 0) 127 | inst.modificationInfo.modifiesGPR = true; 128 | break; 129 | 130 | case OPCODE_LWL: 131 | case OPCODE_LWR: 132 | case OPCODE_LDL: 133 | case OPCODE_LDR: 134 | inst.isLoad = true; 135 | if (inst.rt != 0) 136 | inst.modificationInfo.modifiesGPR = true; 137 | inst.modificationInfo.modifiesMemory = true; 138 | break; 139 | 140 | case OPCODE_LWC1: 141 | inst.isLoad = true; 142 | inst.modificationInfo.modifiesFPR = true; 143 | break; 144 | 145 | case OPCODE_LDC1: // Not present/used on EE FPU 146 | case OPCODE_LWC2: // Maybe unused 147 | case OPCODE_LDC2: // VU Load 148 | inst.isLoad = true; 149 | inst.isVU = true; 150 | inst.modificationInfo.modifiesVFR = true; 151 | break; 152 | 153 | case OPCODE_SB: 154 | case OPCODE_SH: 155 | case OPCODE_SW: 156 | case OPCODE_SD: 157 | inst.isStore = true; 158 | inst.modificationInfo.modifiesMemory = true; 159 | break; 160 | 161 | case OPCODE_SWL: 162 | case OPCODE_SWR: 163 | case OPCODE_SDL: 164 | case OPCODE_SDR: 165 | inst.isStore = true; 166 | inst.modificationInfo.modifiesMemory = true; 167 | break; 168 | 169 | case OPCODE_SWC1: 170 | inst.isStore = true; 171 | inst.modificationInfo.modifiesMemory = true; 172 | break; 173 | 174 | case OPCODE_SDC1: // Not present/used on EE FPU 175 | case OPCODE_SWC2: // Potentially unused 176 | case OPCODE_SDC2: 177 | inst.isStore = true; 178 | inst.isVU = true; 179 | inst.modificationInfo.modifiesMemory = true; 180 | break; 181 | 182 | case OPCODE_SC: 183 | case OPCODE_SCD: 184 | inst.isStore = true; 185 | inst.modificationInfo.modifiesMemory = true; 186 | if (inst.rt != 0) 187 | inst.modificationInfo.modifiesGPR = true; // Writes success/fail to rt 188 | inst.modificationInfo.modifiesControl = true; // Reads/Clears LLBit 189 | break; 190 | 191 | case OPCODE_ADDI: 192 | case OPCODE_ADDIU: 193 | case OPCODE_SLTI: 194 | case OPCODE_SLTIU: 195 | case OPCODE_ANDI: 196 | case OPCODE_ORI: 197 | case OPCODE_XORI: 198 | case OPCODE_LUI: 199 | decodeIType(inst); 200 | if (inst.rt != 0) 201 | inst.modificationInfo.modifiesGPR = true; 202 | break; 203 | 204 | case OPCODE_CACHE: 205 | decodeIType(inst); 206 | inst.modificationInfo.modifiesControl = true; // Cache state 207 | break; 208 | 209 | case OPCODE_PREF: 210 | decodeIType(inst); 211 | break; 212 | 213 | case OPCODE_COP0: 214 | decodeCOP0(inst); 215 | break; 216 | 217 | case OPCODE_COP1: 218 | decodeCOP1(inst); 219 | break; 220 | 221 | case OPCODE_COP2: 222 | decodeCOP2(inst); 223 | break; 224 | 225 | default: 226 | // Default to I-type for most other instructions 227 | decodeIType(inst); 228 | break; 229 | } 230 | 231 | // Generic multimedia flag if MMI or VU 232 | if (inst.isMMI || inst.isVU) 233 | { 234 | inst.isMultimedia = true; 235 | inst.vectorInfo.isVector = inst.isVU; // Only VU ops are truly vector 236 | } 237 | 238 | return inst; 239 | } 240 | 241 | void R5900Decoder::decodeRType(Instruction &inst) const 242 | { 243 | // R-type instructions already have all fields set correctly 244 | if (inst.rd != 0) 245 | inst.modificationInfo.modifiesGPR = true; 246 | } 247 | 248 | void R5900Decoder::decodeIType(Instruction &inst) const 249 | { 250 | // I-type instructions already have all fields set correctly 251 | if (!inst.isStore && !inst.isBranch && inst.rt != 0) 252 | { 253 | inst.modificationInfo.modifiesGPR = true; 254 | } 255 | } 256 | 257 | void R5900Decoder::decodeJType(Instruction &inst) const 258 | { 259 | // J-type instructions already have all fields set correctly 260 | inst.isJump = true; 261 | inst.hasDelaySlot = true; 262 | inst.modificationInfo.modifiesControl = true; // PC 263 | if (inst.opcode == OPCODE_JAL) 264 | { 265 | inst.isCall = true; 266 | inst.modificationInfo.modifiesGPR = true; // $ra (r[31]) 267 | } 268 | } 269 | 270 | void R5900Decoder::decodeSpecial(Instruction &inst) const 271 | { 272 | if (inst.rd != 0) 273 | inst.modificationInfo.modifiesGPR = true; 274 | 275 | switch (inst.function) 276 | { 277 | case SPECIAL_JR: 278 | inst.isJump = true; 279 | inst.hasDelaySlot = true; 280 | inst.modificationInfo.modifiesGPR = false; // Doesn't modify GPR itself 281 | inst.modificationInfo.modifiesControl = true; // PC 282 | if (inst.rs == 31) 283 | { 284 | // jr $ra is typically a return 285 | inst.isReturn = true; 286 | } 287 | break; 288 | 289 | case SPECIAL_JALR: 290 | inst.isJump = true; 291 | inst.isCall = true; 292 | inst.hasDelaySlot = true; 293 | if (inst.rd == 0) 294 | inst.modificationInfo.modifiesGPR = false; // JALR $zero, $rs is like JR $rs 295 | else 296 | inst.modificationInfo.modifiesGPR = true; 297 | inst.modificationInfo.modifiesControl = true; // PC 298 | break; 299 | 300 | case SPECIAL_SYSCALL: 301 | case SPECIAL_BREAK: 302 | // Special handling for syscall/break 303 | inst.modificationInfo.modifiesGPR = false; // No GPR change 304 | inst.modificationInfo.modifiesControl = true; // Changes control flow, potentially registers via handler 305 | break; 306 | 307 | case SPECIAL_MFHI: 308 | case SPECIAL_MFLO: 309 | if (inst.rd == 0) 310 | inst.modificationInfo.modifiesGPR = false; 311 | break; 312 | case SPECIAL_MTHI: 313 | case SPECIAL_MTLO: 314 | // HI/LO register operations 315 | inst.modificationInfo.modifiesGPR = false; // Doesn't modify rd 316 | inst.modificationInfo.modifiesControl = true; // HI/LO 317 | break; 318 | 319 | case SPECIAL_MULT: 320 | case SPECIAL_MULTU: 321 | case SPECIAL_DIV: 322 | case SPECIAL_DIVU: 323 | // Multiplication and division operations 324 | inst.modificationInfo.modifiesGPR = false; // Doesn't modify rd 325 | inst.modificationInfo.modifiesControl = true; // HI/LO 326 | break; 327 | 328 | case SPECIAL_ADD: 329 | case SPECIAL_ADDU: 330 | case SPECIAL_SUB: 331 | case SPECIAL_SUBU: 332 | case SPECIAL_AND: 333 | case SPECIAL_OR: 334 | case SPECIAL_XOR: 335 | case SPECIAL_NOR: 336 | // ALU operations 337 | if (inst.rd == 0) 338 | inst.modificationInfo.modifiesGPR = false; 339 | break; 340 | 341 | case SPECIAL_SLL: 342 | case SPECIAL_SRL: 343 | case SPECIAL_SRA: 344 | case SPECIAL_SLLV: 345 | case SPECIAL_SRLV: 346 | case SPECIAL_SRAV: 347 | // Shift operations 348 | if (inst.rd == 0) 349 | inst.modificationInfo.modifiesGPR = false; 350 | break; 351 | 352 | // 64-bit specific operations 353 | case SPECIAL_DADD: 354 | case SPECIAL_DADDU: 355 | case SPECIAL_DSUB: 356 | case SPECIAL_DSUBU: 357 | case SPECIAL_DSLL: 358 | case SPECIAL_DSRL: 359 | case SPECIAL_DSRA: 360 | case SPECIAL_DSLL32: 361 | case SPECIAL_DSRL32: 362 | case SPECIAL_DSRA32: 363 | case SPECIAL_DSLLV: 364 | case SPECIAL_DSRLV: 365 | case SPECIAL_DSRAV: 366 | // 64-bit operations 367 | if (inst.rd == 0) 368 | inst.modificationInfo.modifiesGPR = false; 369 | break; 370 | 371 | case SPECIAL_MOVZ: 372 | case SPECIAL_MOVN: 373 | if (inst.rd == 0) 374 | inst.modificationInfo.modifiesGPR = false; 375 | break; 376 | 377 | // Set on Less Than 378 | case SPECIAL_SLT: 379 | case SPECIAL_SLTU: 380 | if (inst.rd == 0) 381 | inst.modificationInfo.modifiesGPR = false; 382 | break; 383 | 384 | case SPECIAL_TGE: 385 | case SPECIAL_TGEU: 386 | case SPECIAL_TLT: 387 | case SPECIAL_TLTU: 388 | case SPECIAL_TEQ: 389 | case SPECIAL_TNE: 390 | inst.modificationInfo.modifiesGPR = false; 391 | inst.modificationInfo.modifiesControl = true; // Control flow change via trap 392 | break; 393 | 394 | // PS2 Specific (MFSA/MTSA) 395 | case SPECIAL_MFSA: // Modifies rd GPR 396 | if (inst.rd == 0) 397 | inst.modificationInfo.modifiesGPR = false; 398 | break; 399 | case SPECIAL_MTSA: // Modifies SA control register 400 | inst.modificationInfo.modifiesGPR = false; 401 | inst.modificationInfo.modifiesControl = true; // SA reg 402 | break; 403 | 404 | case SPECIAL_SYNC: 405 | inst.modificationInfo.modifiesGPR = false; 406 | inst.modificationInfo.modifiesControl = true; 407 | break; 408 | } 409 | } 410 | 411 | void R5900Decoder::decodeRegimm(Instruction &inst) const 412 | { 413 | uint32_t rt = inst.rt; 414 | 415 | switch (rt) 416 | { 417 | case REGIMM_BLTZ: 418 | case REGIMM_BGEZ: 419 | case REGIMM_BLTZL: 420 | case REGIMM_BGEZL: 421 | // Branches 422 | inst.isBranch = true; 423 | inst.hasDelaySlot = true; 424 | inst.modificationInfo.modifiesControl = true; // PC potentially 425 | break; 426 | 427 | case REGIMM_BLTZAL: 428 | case REGIMM_BGEZAL: 429 | case REGIMM_BLTZALL: 430 | case REGIMM_BGEZALL: 431 | // Branch and Link 432 | inst.isBranch = true; 433 | inst.isCall = true; 434 | inst.hasDelaySlot = true; 435 | inst.modificationInfo.modifiesGPR = true; // $ra (r[31]) 436 | inst.modificationInfo.modifiesControl = true; // PC 437 | break; 438 | 439 | case REGIMM_TGEI: 440 | case REGIMM_TGEIU: 441 | case REGIMM_TLTI: 442 | case REGIMM_TLTIU: 443 | case REGIMM_TEQI: 444 | case REGIMM_TNEI: 445 | // Trap Instructions 446 | inst.modificationInfo.modifiesControl = true; 447 | break; 448 | 449 | case REGIMM_MTSAB: 450 | case REGIMM_MTSAH: 451 | // PS2 specific MTSAB/MTSAH instructions (for QMFC2/QMTC2) 452 | inst.isMultimedia = true; 453 | inst.modificationInfo.modifiesControl = true; // SA register 454 | break; 455 | 456 | default: 457 | // Unknown REGIMM instructions 458 | std::cerr << "Unknown REGIMM instruction: " << std::hex << inst.raw << std::endl; 459 | break; 460 | } 461 | } 462 | 463 | void R5900Decoder::decodeMMI(Instruction &inst) const 464 | { 465 | inst.isMMI = true; 466 | inst.isMultimedia = true; 467 | inst.modificationInfo.modifiesGPR = true; // Most MMI ops write to rd GPR 468 | if (inst.rd == 0) 469 | inst.modificationInfo.modifiesGPR = false; // Except if rd is $zero 470 | 471 | // The function field is actually determined by the lowest 6 bits (as in R-type) 472 | uint32_t mmiFunction = inst.function; 473 | 474 | uint32_t rs = inst.rs; 475 | 476 | if (mmiFunction == MMI_MMI0) 477 | { 478 | decodeMMI0(inst); 479 | return; 480 | } 481 | if (mmiFunction == MMI_MMI1) 482 | { 483 | decodeMMI1(inst); 484 | return; 485 | } 486 | if (mmiFunction == MMI_MMI2) 487 | { 488 | decodeMMI2(inst); 489 | return; 490 | } 491 | if (mmiFunction == MMI_MMI3) 492 | { 493 | decodeMMI3(inst); 494 | return; 495 | } 496 | 497 | switch (mmiFunction) 498 | { 499 | // Thouse comments has the same value 500 | case MMI_MADD: 501 | case MMI_MADDU: 502 | case MMI_MSUB: 503 | case MMI_MSUBU: 504 | case MMI_MADD1: 505 | case MMI_MADDU1: 506 | case MMI_MULT1: 507 | case MMI_MULTU1: 508 | case MMI_DIV1: 509 | case MMI_DIVU1: 510 | // case MMI2_PMADDW: 511 | case MMI2_PMSUBW: 512 | case MMI2_PMULTW: 513 | case MMI2_PDIVW: 514 | case MMI2_PDIVBW: 515 | case MMI2_PMADDH: 516 | case MMI2_PHMADH: 517 | // case MMI2_PMSUBH: 518 | // case MMI2_PHMSBH: 519 | case MMI2_PMULTH: 520 | // case MMI3_PMADDUW: 521 | // case MMI3_PMULTUW: 522 | // case MMI3_PDIVUW: 523 | inst.modificationInfo.modifiesGPR = false; // Writes to HI/LO or HI1/LO1 524 | inst.modificationInfo.modifiesControl = true; 525 | break; 526 | case MMI_MTHI1: 527 | case MMI_MTLO1: 528 | case MMI_PMTHL: 529 | case MMI3_PMTHI: 530 | case MMI3_PMTLO: 531 | inst.modificationInfo.modifiesGPR = false; // Writes to HI/LO or HI1/LO1 532 | inst.modificationInfo.modifiesControl = true; 533 | break; 534 | case MMI_PMFHL: 535 | decodePMFHL(inst); 536 | break; 537 | default: 538 | // Unknown or unsupported MMI function 539 | std::cerr << "Unknown MMI function: " << std::hex << mmiFunction << std::endl; 540 | break; 541 | } 542 | } 543 | 544 | void R5900Decoder::decodeMMI0(Instruction &inst) const 545 | { 546 | inst.mmiType = 0; 547 | inst.mmiFunction = inst.sa; 548 | 549 | uint32_t sa = inst.sa; 550 | 551 | switch (sa) 552 | { 553 | case MMI0_PADDW: 554 | case MMI0_PSUBW: 555 | case MMI0_PCGTW: 556 | case MMI0_PMAXW: 557 | case MMI0_PADDH: 558 | case MMI0_PSUBH: 559 | case MMI0_PCGTH: 560 | case MMI0_PMAXH: 561 | case MMI0_PADDB: 562 | case MMI0_PSUBB: 563 | case MMI0_PCGTB: 564 | // Arithmetic and comparison operations 565 | break; 566 | 567 | case MMI0_PADDSW: 568 | case MMI0_PSUBSW: 569 | case MMI0_PADDSH: 570 | case MMI0_PSUBSH: 571 | case MMI0_PADDSB: 572 | case MMI0_PSUBSB: 573 | // Saturated arithmetic operations 574 | break; 575 | 576 | case MMI0_PEXTLW: 577 | case MMI0_PPACW: 578 | case MMI0_PEXTLH: 579 | case MMI0_PPACH: 580 | case MMI0_PEXTLB: 581 | case MMI0_PPACB: 582 | case MMI0_PEXT5: 583 | case MMI0_PPAC5: 584 | // Data packing/unpacking operations 585 | break; 586 | 587 | default: 588 | // Unknown MMI0 operation 589 | break; 590 | } 591 | } 592 | 593 | void R5900Decoder::decodeMMI1(Instruction &inst) const 594 | { 595 | inst.mmiType = 1; 596 | inst.mmiFunction = inst.sa; 597 | 598 | uint32_t subFunction = inst.function & 0x3F; 599 | 600 | uint32_t sa = inst.sa; 601 | 602 | switch (sa) 603 | { 604 | case MMI1_PABSW: 605 | case MMI1_PABSH: 606 | // Absolute value operations 607 | break; 608 | 609 | case MMI1_PCEQW: 610 | case MMI1_PCEQH: 611 | case MMI1_PCEQB: 612 | // Equality comparison operations 613 | break; 614 | 615 | case MMI1_PMINW: 616 | case MMI1_PMINH: 617 | // Minimum value operations 618 | break; 619 | 620 | case MMI1_PADDUW: 621 | case MMI1_PSUBUW: 622 | case MMI1_PEXTUW: 623 | case MMI1_PADDUH: 624 | case MMI1_PSUBUH: 625 | case MMI1_PEXTUH: 626 | case MMI1_PADDUB: 627 | case MMI1_PSUBUB: 628 | case MMI1_PEXTUB: 629 | // Unsigned arithmetic and extension operations 630 | break; 631 | 632 | case MMI1_QFSRV: 633 | // Quadword funnel shift right variable 634 | inst.isMultimedia = true; 635 | break; 636 | 637 | default: 638 | // Unknown MMI1 operation 639 | break; 640 | } 641 | } 642 | 643 | void R5900Decoder::decodeMMI2(Instruction &inst) const 644 | { 645 | inst.mmiType = 2; 646 | inst.mmiFunction = inst.sa; 647 | 648 | if (inst.sa == MMI2_PMADDW || inst.sa == MMI2_PMSUBW || 649 | inst.sa == MMI2_PMULTW || inst.sa == MMI2_PDIVW || inst.sa == MMI2_PDIVBW || 650 | inst.sa == MMI2_PMADDH || inst.sa == MMI2_PHMADH || 651 | inst.sa == MMI2_PMSUBH || inst.sa == MMI2_PHMSBH || inst.sa == MMI2_PMULTH) 652 | { 653 | inst.modificationInfo.modifiesControl = true; // HI/LO 654 | } 655 | 656 | uint32_t sa = inst.sa; 657 | 658 | switch (sa) 659 | { 660 | case MMI2_PMADDW: 661 | case MMI2_PMSUBW: 662 | case MMI2_PMADDH: 663 | case MMI2_PHMADH: 664 | case MMI2_PMSUBH: 665 | case MMI2_PHMSBH: 666 | case MMI2_PMULTH: 667 | // Multiply/multiply-add operations 668 | inst.isMultimedia = true; 669 | break; 670 | 671 | case MMI2_PSLLVW: 672 | case MMI2_PSRLVW: 673 | // Variable shift operations 674 | break; 675 | 676 | case MMI2_PMFHI: 677 | case MMI2_PMFLO: 678 | // Move from HI/LO registers 679 | break; 680 | 681 | case MMI2_PINTH: 682 | // Interleave half words 683 | break; 684 | 685 | case MMI2_PMULTW: 686 | case MMI2_PDIVW: 687 | case MMI2_PDIVBW: 688 | // Multiply/divide operations 689 | inst.isMultimedia = true; 690 | break; 691 | 692 | case MMI2_PCPYLD: 693 | // Copy lower doubleword 694 | break; 695 | 696 | case MMI2_PAND: 697 | case MMI2_PXOR: 698 | // Logical operations 699 | break; 700 | 701 | case MMI2_PEXEH: 702 | case MMI2_PREVH: 703 | case MMI2_PEXEW: 704 | case MMI2_PROT3W: 705 | // Data permutation operations 706 | break; 707 | 708 | default: 709 | // Unknown MMI2 operation 710 | break; 711 | } 712 | } 713 | 714 | void R5900Decoder::decodeMMI3(Instruction &inst) const 715 | { 716 | inst.mmiType = 3; 717 | inst.mmiFunction = inst.sa; 718 | 719 | uint32_t sa = inst.sa; 720 | 721 | switch (sa) 722 | { 723 | case MMI3_PMADDUW: 724 | // Unsigned multiply-add 725 | inst.isMultimedia = true; 726 | inst.modificationInfo.modifiesControl = true; // HI/LO 727 | break; 728 | 729 | case MMI3_PSRAVW: 730 | // Packed shift right arithmetic variable word 731 | break; 732 | 733 | case MMI3_PMTHI: 734 | inst.mmiFunction = MMI3_PMTHI; 735 | inst.modificationInfo.modifiesGPR = false; 736 | inst.modificationInfo.modifiesControl = true; // HI register is modified 737 | break; 738 | 739 | case MMI3_PMTLO: 740 | inst.mmiFunction = MMI3_PMTLO; 741 | inst.modificationInfo.modifiesGPR = false; 742 | inst.modificationInfo.modifiesControl = true; // LO register is modified 743 | break; 744 | 745 | case MMI3_PINTEH: 746 | // Interleave even halfwords 747 | break; 748 | 749 | case MMI3_PMULTUW: 750 | case MMI3_PDIVUW: 751 | // Unsigned multiply/divide operations 752 | inst.isMultimedia = true; 753 | inst.modificationInfo.modifiesControl = true; // HI/LO 754 | break; 755 | 756 | case MMI3_PCPYUD: 757 | // Copy upper doubleword 758 | break; 759 | 760 | case MMI3_POR: 761 | case MMI3_PNOR: 762 | // Logical operations 763 | break; 764 | 765 | case MMI3_PEXCH: 766 | case MMI3_PCPYH: 767 | case MMI3_PEXCW: 768 | // Data permutation operations 769 | break; 770 | 771 | default: 772 | // Unknown MMI3 operation 773 | break; 774 | } 775 | } 776 | 777 | void R5900Decoder::decodeCOP0(Instruction &inst) const 778 | { 779 | // COP0 (System Control) instructions 780 | uint8_t format = inst.rs; 781 | inst.modificationInfo.modifiesControl = true; // Assume COP0 always modifies some control state 782 | if (format == COP0_MF && inst.rt != 0) 783 | { 784 | inst.modificationInfo.modifiesGPR = true; 785 | } 786 | else if (format == COP0_CO && inst.function == COP0_CO_ERET) 787 | { 788 | inst.isReturn = true; 789 | inst.hasDelaySlot = false; 790 | } // ERET is special 791 | else if (format == COP0_BC) 792 | { 793 | inst.isBranch = true; 794 | inst.hasDelaySlot = true; 795 | } 796 | } 797 | 798 | void R5900Decoder::decodeCOP1(Instruction &inst) const 799 | { 800 | uint8_t format = inst.rs; 801 | if (format == COP1_MF || format == COP1_CF) 802 | { 803 | if (inst.rt != 0) 804 | inst.modificationInfo.modifiesGPR = true; 805 | } 806 | else if (format == COP1_BC) 807 | { 808 | inst.isBranch = true; 809 | inst.hasDelaySlot = true; 810 | inst.modificationInfo.modifiesControl = true; 811 | } 812 | else if (format == COP1_S || format == COP1_W || format == COP1_L) 813 | { 814 | inst.modificationInfo.modifiesFPR = true; 815 | } 816 | if (format == COP1_CT || (format == COP1_S && inst.function >= COP1_S_C_F)) 817 | { 818 | inst.modificationInfo.modifiesControl = true; 819 | } // FCR31 820 | } 821 | 822 | void R5900Decoder::decodeCOP2(Instruction &inst) const 823 | { 824 | uint8_t format = inst.rs; 825 | inst.isVU = true; 826 | inst.isMultimedia = true; 827 | 828 | switch (format) 829 | { 830 | case COP2_QMFC2: 831 | case COP2_CFC2: 832 | if (inst.rt != 0) 833 | inst.modificationInfo.modifiesGPR = true; 834 | break; 835 | case COP2_QMTC2: 836 | inst.modificationInfo.modifiesVFR = true; 837 | break; // Modifies VU state 838 | case COP2_CTC2: 839 | inst.modificationInfo.modifiesControl = true; 840 | break; // Modifies VU control state 841 | case COP2_BC: 842 | inst.isBranch = true; 843 | inst.hasDelaySlot = true; 844 | inst.modificationInfo.modifiesControl = true; 845 | break; 846 | case COP2_CO: // VU Macro instructions (format >= 0x10) 847 | case COP2_CO + 1: 848 | case COP2_CO + 2: 849 | case COP2_CO + 3: 850 | case COP2_CO + 4: 851 | case COP2_CO + 5: 852 | case COP2_CO + 6: 853 | case COP2_CO + 7: 854 | case COP2_CO + 8: 855 | case COP2_CO + 9: 856 | case COP2_CO + 10: 857 | case COP2_CO + 11: 858 | case COP2_CO + 12: 859 | case COP2_CO + 13: 860 | case COP2_CO + 14: 861 | case COP2_CO + 15: 862 | { // Refine based on specific VU function 863 | uint8_t vu_func = inst.function; 864 | 865 | if (vu_func == VU0_S2_VDIV || vu_func == VU0_S2_VSQRT || vu_func == VU0_S2_VRSQRT) 866 | { 867 | inst.vectorInfo.fsf = (inst.raw >> 10) & 0x3; // Extract bits 10-11 868 | inst.vectorInfo.ftf = (inst.raw >> 8) & 0x3; // Extract bits 8-9 869 | } 870 | 871 | inst.vectorInfo.vectorField = (inst.raw >> 21) & 0xF; 872 | 873 | inst.modificationInfo.modifiesVFR = true; // Default: Modifies Vector Float Reg 874 | inst.modificationInfo.modifiesControl = true; // Default: Modifies Flags/Special Regs (Q, P, I, MAC, Clip...) 875 | 876 | if (vu_func >= 0x3C) // Special2 Table 877 | { 878 | switch (vu_func) 879 | { 880 | case VU0_S2_VDIV: 881 | case VU0_S2_VSQRT: 882 | case VU0_S2_VRSQRT: 883 | inst.vectorInfo.usesQReg = true; 884 | break; 885 | case VU0_S2_VMTIR: 886 | inst.modificationInfo.modifiesVFR = false; 887 | inst.modificationInfo.modifiesVIC = true; 888 | break; 889 | case VU0_S2_VMFIR: 890 | inst.modificationInfo.modifiesVIR = false; 891 | break; // Reads VI, writes VF 892 | case VU0_S2_VILWR: 893 | inst.modificationInfo.modifiesVFR = false; 894 | inst.modificationInfo.modifiesVIR = true; 895 | inst.isLoad = true; 896 | break; 897 | case VU0_S2_VISWR: 898 | inst.modificationInfo.modifiesVFR = false; 899 | inst.modificationInfo.modifiesVIR = false; 900 | inst.isStore = true; 901 | inst.modificationInfo.modifiesMemory = true; 902 | break; 903 | 904 | case VU0_S2_VRINIT: 905 | case VU0_S2_VRXOR: 906 | inst.modificationInfo.modifiesVFR = false; /* Modifies R */ 907 | break; // Modifies R 908 | case VU0_S2_VRGET: 909 | inst.modificationInfo.modifiesControl = false; /* Reads R, writes VF */ 910 | break; 911 | case VU0_S2_VRNEXT: 912 | inst.modificationInfo.modifiesControl = true; /* Modifies R */ 913 | inst.modificationInfo.modifiesVFR = false; 914 | break; // Writes R 915 | case VU0_S2_VABS: 916 | case VU0_S2_VMOVE: 917 | case VU0_S2_VMR32: 918 | inst.modificationInfo.modifiesControl = false; 919 | break; // Only VF 920 | case VU0_S2_VNOP: 921 | inst.modificationInfo.modifiesVFR = false; 922 | inst.modificationInfo.modifiesControl = false; 923 | break; 924 | case VU0_S2_VCLIPw: 925 | inst.modificationInfo.modifiesVFR = false; /* Modifies Clip flags */ 926 | break; 927 | } 928 | } 929 | else // Special1 Table 930 | { 931 | if (vu_func >= VU0_S1_VIADD && vu_func <= VU0_S1_VIOR) 932 | { 933 | inst.modificationInfo.modifiesVFR = false; 934 | inst.modificationInfo.modifiesVIR = true; 935 | } // Integer ops 936 | if (vu_func == VU0_S1_VIADDI) 937 | { 938 | inst.modificationInfo.modifiesVFR = false; 939 | inst.modificationInfo.modifiesVIR = true; 940 | } 941 | if (vu_func == VU0_S2_VMFIR) 942 | { 943 | inst.modificationInfo.modifiesVIR = false; 944 | } // Only reads VIR 945 | if (vu_func == VU0_S2_VMTIR) 946 | { 947 | inst.modificationInfo.modifiesVFR = false; 948 | inst.modificationInfo.modifiesVIC = true; 949 | } // Modifies I reg 950 | if (vu_func == VU0_S2_VILWR) 951 | { 952 | inst.modificationInfo.modifiesVFR = false; 953 | inst.modificationInfo.modifiesVIR = true; 954 | inst.isLoad = true; 955 | } 956 | if (vu_func == VU0_S2_VISWR) 957 | { 958 | inst.modificationInfo.modifiesVFR = false; 959 | inst.modificationInfo.modifiesVIR = false; 960 | inst.isStore = true; 961 | inst.modificationInfo.modifiesMemory = true; 962 | } 963 | if (vu_func == VU0_S2_VDIV || vu_func == VU0_S2_VSQRT || vu_func == VU0_S2_VRSQRT) 964 | { 965 | inst.vectorInfo.usesQReg = true; 966 | } 967 | } 968 | break; 969 | } 970 | } 971 | } 972 | 973 | void R5900Decoder::decodePMFHL(Instruction &inst) const 974 | { 975 | uint32_t saField = inst.sa; 976 | 977 | switch (saField) 978 | { 979 | case PMFHL_LW: 980 | inst.pmfhlVariation = PMFHL_LW; 981 | break; 982 | case PMFHL_UW: 983 | inst.pmfhlVariation = PMFHL_UW; 984 | break; 985 | case PMFHL_SLW: 986 | inst.pmfhlVariation = PMFHL_SLW; 987 | break; 988 | case PMFHL_LH: 989 | inst.pmfhlVariation = PMFHL_LH; 990 | break; 991 | case PMFHL_SH: 992 | inst.pmfhlVariation = PMFHL_SH; 993 | break; 994 | default: 995 | inst.pmfhlVariation = 0xFF; 996 | break; 997 | } 998 | } 999 | 1000 | bool R5900Decoder::isBranchInstruction(const Instruction &inst) const 1001 | { 1002 | return inst.isBranch; 1003 | } 1004 | 1005 | bool R5900Decoder::isJumpInstruction(const Instruction &inst) const 1006 | { 1007 | return inst.isJump; 1008 | } 1009 | 1010 | bool R5900Decoder::isCallInstruction(const Instruction &inst) const 1011 | { 1012 | return inst.isCall; 1013 | } 1014 | 1015 | bool R5900Decoder::isReturnInstruction(const Instruction &inst) const 1016 | { 1017 | return inst.isReturn; 1018 | } 1019 | 1020 | bool R5900Decoder::isMMIInstruction(const Instruction &inst) const 1021 | { 1022 | return inst.isMMI; 1023 | } 1024 | 1025 | bool R5900Decoder::isVUInstruction(const Instruction &inst) const 1026 | { 1027 | return inst.isVU; 1028 | } 1029 | 1030 | bool R5900Decoder::isStore(const Instruction &inst) const 1031 | { 1032 | return inst.isStore; 1033 | } 1034 | 1035 | bool R5900Decoder::isLoad(const Instruction &inst) const 1036 | { 1037 | return inst.isLoad; 1038 | } 1039 | 1040 | bool R5900Decoder::hasDelaySlot(const Instruction &inst) const 1041 | { 1042 | return inst.hasDelaySlot; 1043 | } 1044 | 1045 | uint32_t R5900Decoder::getBranchTarget(const Instruction &inst) const 1046 | { 1047 | if (!inst.isBranch) 1048 | { 1049 | return 0; 1050 | } 1051 | 1052 | int32_t offset = inst.simmediate << 2; 1053 | return inst.address + 4 + offset; 1054 | } 1055 | 1056 | uint32_t R5900Decoder::getJumpTarget(const Instruction &inst) const 1057 | { 1058 | if (!inst.isJump) 1059 | { 1060 | return 0; 1061 | } 1062 | 1063 | if (inst.opcode == OPCODE_SPECIAL && 1064 | (inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR)) 1065 | { 1066 | // JR/JALR: target is in the rs register (can't be determined statically) 1067 | return 0; 1068 | } 1069 | 1070 | if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) 1071 | { 1072 | // J/JAL: target is in the lower 26 bits, shifted left by 2 1073 | // and combined with the upper 4 bits of PC + 4 1074 | uint32_t pc_upper = (inst.address + 4) & 0xF0000000; 1075 | return pc_upper | (inst.target << 2); 1076 | } 1077 | 1078 | return 0; 1079 | } 1080 | 1081 | } // namespace ps2recomp -------------------------------------------------------------------------------- /ps2xRuntime/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(PS2Runtime VERSION 0.1.0 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | include(FetchContent) 9 | set(FETCHCONTENT_QUIET FALSE) 10 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 11 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) 12 | 13 | FetchContent_Declare( 14 | raylib 15 | GIT_REPOSITORY "https://github.com/raysan5/raylib.git" 16 | GIT_TAG "5.5" # we will migrate to 4.2.0 later, trust me it will be better 17 | GIT_PROGRESS TRUE 18 | ) 19 | FetchContent_MakeAvailable(raylib) 20 | 21 | add_library(ps2_runtime STATIC 22 | src/ps2_memory.cpp 23 | src/ps2_runtime.cpp 24 | src/ps2_stubs.cpp 25 | src/ps2_syscalls.cpp 26 | ) 27 | 28 | add_executable(ps2EntryRunner 29 | src/register_functions.cpp 30 | src/main.cpp 31 | ) 32 | 33 | target_include_directories(ps2_runtime PUBLIC 34 | ${CMAKE_CURRENT_SOURCE_DIR}/include 35 | ) 36 | 37 | target_link_libraries(ps2_runtime PRIVATE raylib) 38 | target_link_libraries(ps2EntryRunner PRIVATE raylib) 39 | target_link_libraries(ps2EntryRunner PRIVATE ps2_runtime) 40 | 41 | install(TARGETS ps2_runtime 42 | LIBRARY DESTINATION lib 43 | ARCHIVE DESTINATION lib 44 | ) 45 | 46 | install(DIRECTORY include/ 47 | DESTINATION include 48 | ) -------------------------------------------------------------------------------- /ps2xRuntime/Readme.md: -------------------------------------------------------------------------------- 1 | # Runtime Library 2 | The runtime library provides the execution environment for recompiled code, including: 3 | 4 | * Memory management (32MB main RAM, scratchpad, etc.) 5 | * Register context (128-bit GPRs, VU0 registers, etc.) 6 | * Function table for dynamic linking 7 | * Basic PS2 system call stubs 8 | 9 | ## Adding Custom Function Implementations 10 | You can add custom implementations for PS2 system calls or game functions by: 11 | 12 | 1. Creating function implementations that match the signature: 13 | ```cpp 14 | void function_name(uint8_t* rdram, R5900Context* ctx); 15 | ``` 16 | 17 | 2. Registering them with the runtime: 18 | ```cpp 19 | runtime.registerFunction(address, function_name); 20 | ``` 21 | 22 | ## Advanced Features 23 | Memory Translation 24 | The runtime handles PS2's memory addressing, including: 25 | 26 | * KSEG0/KSEG1 direct mapping 27 | * TLB lookups for user memory 28 | * Special memory areas (scratchpad, I/O registers) 29 | 30 | ## Vector Unit Support 31 | PS2-specific 128-bit MMI instructions and VU0 macro mode instructions are supported via SSE/AVX intrinsics. 32 | 33 | ## Instruction Patching 34 | You can patch specific instructions in the recompiled code to fix game issues or implement custom behavior. 35 | 36 | ## Limitations 37 | 38 | * Graphics and sound output require external implementations 39 | * Some PS2-specific hardware features may not be fully supported 40 | * Performance may vary based on the complexity of the game -------------------------------------------------------------------------------- /ps2xRuntime/include/ps2_runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2_RUNTIME_H 2 | #define PS2_RUNTIME_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include // For SSE/AVX instructions 10 | #include 11 | #include 12 | 13 | constexpr uint32_t PS2_RAM_SIZE = 32 * 1024 * 1024; // 32MB 14 | constexpr uint32_t PS2_RAM_MASK = 0x1FFFFFF; // Mask for 32MB alignment 15 | constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM 16 | constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000; 17 | constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16 * 1024; // 16KB 18 | constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC) 19 | constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB 20 | constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG 21 | constexpr uint32_t PS2_BIOS_SIZE = 4 * 1024 * 1024; // 4MB 22 | 23 | constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; // Base address as seen from EE 24 | constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000; 25 | constexpr uint32_t PS2_VU0_CODE_SIZE = 4 * 1024; // 4KB Micro Memory 26 | constexpr uint32_t PS2_VU0_DATA_SIZE = 4 * 1024; // 4KB Data Memory (VU Mem) 27 | 28 | constexpr uint32_t PS2_VU1_MEM_BASE = 0x11008000; // Base address as seen from EE 29 | constexpr uint32_t PS2_VU1_CODE_SIZE = 16 * 1024; // 16KB Micro Memory 30 | constexpr uint32_t PS2_VU1_DATA_SIZE = 16 * 1024; // 16KB Data Memory (VU Mem) 31 | constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000; 32 | constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000; 33 | 34 | constexpr uint32_t PS2_GS_BASE = 0x12000000; 35 | constexpr uint32_t PS2_GS_PRIV_REG_BASE = 0x12000000; // GS Privileged Registers 36 | constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000; 37 | 38 | #define PS2_FIO_O_RDONLY 0x0001 39 | #define PS2_FIO_O_WRONLY 0x0002 40 | #define PS2_FIO_O_RDWR 0x0003 41 | #define PS2_FIO_O_APPEND 0x0100 42 | #define PS2_FIO_O_CREAT 0x0200 43 | #define PS2_FIO_O_TRUNC 0x0400 44 | #define PS2_FIO_O_EXCL 0x0800 45 | 46 | #define PS2_FIO_SEEK_SET 0 47 | #define PS2_FIO_SEEK_CUR 1 48 | #define PS2_FIO_SEEK_END 2 49 | 50 | #define PS2_FIO_S_IFDIR 0x1000 51 | #define PS2_FIO_S_IFREG 0x2000 52 | 53 | enum PS2Exception 54 | { 55 | EXCEPTION_INTEGER_OVERFLOW = 0x0C, // From MIPS spec 56 | }; 57 | 58 | // PS2 CPU context (R5900) 59 | struct R5900Context 60 | { 61 | // General Purpose Registers (128-bit) 62 | __m128i r[32]; // Main registers 63 | 64 | // Control registers 65 | uint32_t pc; // Program counter 66 | uint64_t insn_count; // Instruction counter 67 | uint32_t hi, lo; // HI/LO registers for mult/div results 68 | uint32_t hi1, lo1; // Secondary HI/LO registers for MULT1/DIV1 69 | uint32_t sa; // Shift amount register 70 | 71 | // VU0 registers (when used in macro mode) 72 | __m128 vu0_vf[32]; // VU0 vector float registers 73 | uint16_t vi[16]; // VU0 vector integer registers 74 | float vu0_q; // VU0 Q register (quotient) 75 | float vu0_p; // VU0 P register (EFU result) 76 | float vu0_i; // VU0 I register (integer value) 77 | __m128 vu0_r; // VU0 R register 78 | uint16_t vu0_status; // VU0 status register 79 | uint32_t vu0_mac_flags; // VU0 MAC flags 80 | uint32_t vu0_clip_flags; // VU0 clipping flags 81 | uint32_t vu0_cmsar0; // VU0 microprogram start address 82 | uint32_t vu0_fbrst; // VIF/VU reset register 83 | float vu0_cf[4]; // VU0 FMAC control floating-point registers 84 | 85 | // COP0 System control registers 86 | uint32_t cop0_index; 87 | uint32_t cop0_random; 88 | uint32_t cop0_entrylo0; 89 | uint32_t cop0_entrylo1; 90 | uint32_t cop0_context; 91 | uint32_t cop0_pagemask; 92 | uint32_t cop0_wired; 93 | uint32_t cop0_badvaddr; 94 | uint32_t cop0_count; 95 | uint32_t cop0_entryhi; 96 | uint32_t cop0_compare; 97 | uint32_t cop0_status; 98 | uint32_t cop0_cause; 99 | uint32_t cop0_epc; 100 | uint32_t cop0_prid; 101 | uint32_t cop0_config; 102 | uint32_t cop0_badpaddr; 103 | uint32_t cop0_debug; 104 | uint32_t cop0_perf; 105 | uint32_t cop0_taglo; 106 | uint32_t cop0_taghi; 107 | uint32_t cop0_errorepc; 108 | 109 | // FPU registers (COP1) 110 | float f[32]; 111 | uint32_t fcr31; // Control/status register 112 | 113 | R5900Context() 114 | { 115 | for (int i = 0; i < 32; i++) 116 | { 117 | r[i] = _mm_setzero_si128(); 118 | f[i] = 0.0f; 119 | vu0_vf[i] = _mm_setzero_ps(); 120 | } 121 | 122 | for (int i = 0; i < 4; i++) 123 | { 124 | vu0_cf[i] = 0.0f; 125 | } 126 | 127 | for (int i = 0; i < 16; ++i) 128 | { 129 | vi[i] = 0; 130 | } 131 | 132 | pc = 0; 133 | insn_count = 0; 134 | lo = hi = lo1 = hi1 = 0; 135 | sa = 0; 136 | 137 | // Initialize VU0 registers 138 | vu0_q = 1.0f; // Q register usually initialized to 1.0 139 | vu0_p = 0.0f; 140 | vu0_i = 0.0f; 141 | vu0_r = _mm_setzero_ps(); 142 | vu0_status = 0; 143 | vu0_mac_flags = 0; 144 | vu0_clip_flags = 0; 145 | vu0_cmsar0 = 0; 146 | vu0_fbrst = 0; 147 | 148 | // Reset COP0 registers 149 | cop0_index = 0; 150 | cop0_random = 47; // Start at maximum value 151 | cop0_entrylo0 = 0; 152 | cop0_entrylo1 = 0; 153 | cop0_context = 0; 154 | cop0_pagemask = 0; 155 | cop0_wired = 0; 156 | cop0_badvaddr = 0; 157 | cop0_count = 0; 158 | cop0_entryhi = 0; 159 | cop0_compare = 0; 160 | cop0_status = 0x400000; // BEV set, ERL clear, kernel mode 161 | cop0_cause = 0; 162 | cop0_epc = 0; 163 | cop0_prid = 0x00002e20; // CPU ID for R5900 164 | cop0_config = 0; 165 | cop0_badpaddr = 0; 166 | cop0_debug = 0; 167 | cop0_perf = 0; 168 | cop0_taglo = 0; 169 | cop0_taghi = 0; 170 | cop0_errorepc = 0; 171 | 172 | // Reset COP1 state 173 | fcr31 = 0; 174 | } 175 | 176 | void dump() const 177 | { 178 | std::ios_base::fmtflags flags = std::cout.flags(); 179 | std::cout << std::hex << std::setfill('0'); 180 | std::cout << "--- R5900 Context Dump ---\n"; 181 | std::cout << "PC: 0x" << std::setw(8) << pc << "\n"; 182 | std::cout << "HI: 0x" << std::setw(8) << hi << " LO: 0x" << std::setw(8) << lo << "\n"; 183 | std::cout << "HI1:0x" << std::setw(8) << hi1 << " LO1:0x" << std::setw(8) << lo1 << "\n"; 184 | std::cout << "SA: 0x" << std::setw(8) << sa << "\n"; 185 | for (int i = 0; i < 32; ++i) 186 | { 187 | std::cout << "R" << std::setw(2) << std::dec << i << ": 0x" << std::hex 188 | << std::setw(8) << r[i].m128i_u32[3] << std::setw(8) << r[i].m128i_u32[2] << "_" 189 | << std::setw(8) << r[i].m128i_u32[1] << std::setw(8) << r[i].m128i_u32[0] << "\n"; 190 | } 191 | std::cout << "Status: 0x" << std::setw(8) << cop0_status 192 | << " Cause: 0x" << std::setw(8) << cop0_cause 193 | << " EPC: 0x" << std::setw(8) << cop0_epc << "\n"; 194 | std::cout << "--- End Context Dump ---\n"; 195 | std::cout.flags(flags); // Restore format flags 196 | } 197 | 198 | ~R5900Context() = default; 199 | }; 200 | 201 | inline uint32_t getRegU32(const R5900Context *ctx, int reg) 202 | { 203 | // Check if reg is valid (0-31) 204 | if (reg < 0 || reg > 31) 205 | return 0; 206 | return ctx->r[reg].m128i_u32[0]; 207 | } 208 | 209 | inline void setReturnU32(R5900Context *ctx, uint32_t value) 210 | { 211 | ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0 212 | } 213 | 214 | inline void setReturnS32(R5900Context *ctx, int32_t value) 215 | { 216 | ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0 Sign extension handled by cast? TODO Check MIPS ABI. 217 | } 218 | 219 | inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr) 220 | { 221 | constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1; 222 | return rdram + (addr & PS2_RAM_MASK); 223 | } 224 | 225 | inline const uint8_t *getConstMemPtr(uint8_t *rdram, uint32_t addr) 226 | { 227 | constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1; 228 | return rdram + (addr & PS2_RAM_MASK); 229 | } 230 | 231 | // PS2 GS (Graphics Synthesizer) registers 232 | struct GSRegisters 233 | { 234 | uint64_t pmode; // Pixel mode 235 | uint64_t smode1; // Sync mode 1 236 | uint64_t smode2; // Sync mode 2 237 | uint64_t srfsh; // Refresh control 238 | uint64_t synch1; // Synchronization control 1 239 | uint64_t synch2; // Synchronization control 2 240 | uint64_t syncv; // Synchronization control V 241 | uint64_t dispfb1; // Display buffer 1 242 | uint64_t display1; // Display area 1 243 | uint64_t dispfb2; // Display buffer 2 244 | uint64_t display2; // Display area 2 245 | uint64_t extbuf; // External buffer 246 | uint64_t extdata; // External data 247 | uint64_t extwrite; // External write 248 | uint64_t bgcolor; // Background color 249 | uint64_t csr; // Status 250 | uint64_t imr; // Interrupt mask 251 | uint64_t busdir; // Bus direction 252 | uint64_t siglblid; // Signal label ID 253 | }; 254 | 255 | // PS2 VIF (VPU Interface) registers 256 | struct VIFRegisters 257 | { 258 | uint32_t stat; // Status 259 | uint32_t fbrst; // VIF Force Break 260 | uint32_t err; // Error status 261 | uint32_t mark; // Interrupt control 262 | uint32_t cycle; // Transfer mode 263 | uint32_t mode; // Mode control 264 | uint32_t num; // Data amount counter 265 | uint32_t mask; // Data mask 266 | uint32_t code; // VIFcode 267 | uint32_t itops; // ITOP save 268 | uint32_t base; // Base address 269 | uint32_t ofst; // Offset 270 | uint32_t tops; // TOPS 271 | uint32_t itop; // ITOP 272 | uint32_t top; // TOP 273 | uint32_t row[4]; // Transfer row data 274 | uint32_t col[4]; // Transfer column data 275 | }; 276 | 277 | // PS2 DMA registers 278 | struct DMARegisters 279 | { 280 | uint32_t chcr; // Channel control 281 | uint32_t madr; // Memory address 282 | uint32_t qwc; // Quadword count 283 | uint32_t tadr; // Tag address 284 | uint32_t asr0; // Address stack 0 285 | uint32_t asr1; // Address stack 1 286 | uint32_t sadr; // Source address 287 | }; 288 | 289 | struct JumpTable 290 | { 291 | uint32_t address; // Base address of the jump table 292 | uint32_t baseRegister; // Register used for index 293 | std::vector targets; // Jump targets 294 | }; 295 | 296 | class PS2Memory 297 | { 298 | public: 299 | PS2Memory(); 300 | ~PS2Memory(); 301 | 302 | // Initialize memory 303 | bool initialize(size_t ramSize = PS2_RAM_SIZE); 304 | 305 | // Memory access methods 306 | uint8_t *getRDRAM() { return m_rdram; } 307 | uint8_t *getScratchpad() { return m_scratchpad; } 308 | uint8_t *getIOPRAM() { return iop_ram; } 309 | 310 | // Read/write memory 311 | uint8_t read8(uint32_t address); 312 | uint16_t read16(uint32_t address); 313 | uint32_t read32(uint32_t address); 314 | uint64_t read64(uint32_t address); 315 | __m128i read128(uint32_t address); 316 | 317 | void write8(uint32_t address, uint8_t value); 318 | void write16(uint32_t address, uint16_t value); 319 | void write32(uint32_t address, uint32_t value); 320 | void write64(uint32_t address, uint64_t value); 321 | void write128(uint32_t address, __m128i value); 322 | 323 | // TLB handling 324 | uint32_t translateAddress(uint32_t virtualAddress); 325 | 326 | // Hardware register interface 327 | bool writeIORegister(uint32_t address, uint32_t value); 328 | uint32_t readIORegister(uint32_t address); 329 | 330 | // Track code modifications for self-modifying code 331 | void registerCodeRegion(uint32_t start, uint32_t end); 332 | bool isCodeModified(uint32_t address, uint32_t size); 333 | void clearModifiedFlag(uint32_t address, uint32_t size); 334 | 335 | private: 336 | // Main RAM (32MB) 337 | uint8_t *m_rdram; 338 | 339 | // Scratchpad memory (16KB) 340 | uint8_t *m_scratchpad; 341 | 342 | // IOP RAM (2MB) 343 | uint8_t *iop_ram; 344 | 345 | // I/O registers 346 | std::unordered_map m_ioRegisters; 347 | 348 | // Registers 349 | GSRegisters gs_regs; 350 | VIFRegisters vif0_regs; 351 | VIFRegisters vif1_regs; 352 | DMARegisters dma_regs[10]; // 10 DMA channels 353 | 354 | // TLB entries 355 | struct TLBEntry 356 | { 357 | uint32_t vpn; 358 | uint32_t pfn; 359 | uint32_t mask; 360 | bool valid; 361 | }; 362 | 363 | std::vector m_tlbEntries; 364 | 365 | struct CodeRegion 366 | { 367 | uint32_t start; 368 | uint32_t end; 369 | std::vector modified; // Bitmap of modified 4-byte blocks 370 | }; 371 | std::vector m_codeRegions; 372 | 373 | bool isAddressInRegion(uint32_t address, const CodeRegion ®ion); 374 | void markModified(uint32_t address, uint32_t size); 375 | }; 376 | 377 | class PS2Runtime 378 | { 379 | public: 380 | PS2Runtime(); 381 | ~PS2Runtime(); 382 | 383 | bool initialize(const char *title = "PS2 Game"); 384 | bool loadELF(const std::string &elfPath); 385 | void run(); 386 | 387 | using RecompiledFunction = void (*)(uint8_t *, R5900Context *, PS2Runtime *); 388 | 389 | void registerFunction(uint32_t address, RecompiledFunction func); 390 | RecompiledFunction lookupFunction(uint32_t address); 391 | 392 | void SignalException(R5900Context *ctx, PS2Exception exception); 393 | 394 | void executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address); 395 | void vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address); 396 | 397 | public: 398 | void handleSyscall(uint8_t *rdram, R5900Context *ctx); 399 | void handleBreak(uint8_t *rdram, R5900Context *ctx); 400 | 401 | void handleTrap(uint8_t *rdram, R5900Context *ctx); 402 | void handleTLBR(uint8_t *rdram, R5900Context *ctx); 403 | void handleTLBWI(uint8_t *rdram, R5900Context *ctx); 404 | void handleTLBWR(uint8_t *rdram, R5900Context *ctx); 405 | void handleTLBP(uint8_t *rdram, R5900Context *ctx); 406 | void clearLLBit(R5900Context *ctx); 407 | 408 | public: 409 | inline R5900Context &cpu() { return m_cpuContext; } 410 | inline const R5900Context &cpu() const { return m_cpuContext; } 411 | 412 | inline PS2Memory &memory() { return m_memory; } 413 | inline const PS2Memory &memory() const { return m_memory; } 414 | 415 | public: 416 | bool check_overflow = false; 417 | 418 | private: 419 | void HandleIntegerOverflow(R5900Context *ctx); 420 | 421 | private: 422 | PS2Memory m_memory; 423 | R5900Context m_cpuContext; 424 | 425 | std::unordered_map m_functionTable; 426 | 427 | struct LoadedModule 428 | { 429 | std::string name; 430 | uint32_t baseAddress; 431 | size_t size; 432 | bool active; 433 | }; 434 | 435 | std::vector m_loadedModules; 436 | }; 437 | 438 | #endif // PS2_RUNTIME_H -------------------------------------------------------------------------------- /ps2xRuntime/include/ps2_stubs.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2_STUBS_H 2 | #define PS2_STUBS_H 3 | 4 | #include "ps2_runtime.h" 5 | #include 6 | 7 | namespace ps2_stubs 8 | { 9 | 10 | // Memory operations 11 | void malloc(uint8_t *rdram, R5900Context *ctx); 12 | void free(uint8_t *rdram, R5900Context *ctx); 13 | void calloc(uint8_t *rdram, R5900Context *ctx); 14 | void realloc(uint8_t *rdram, R5900Context *ctx); 15 | void memcpy(uint8_t *rdram, R5900Context *ctx); 16 | void memset(uint8_t *rdram, R5900Context *ctx); 17 | void memmove(uint8_t *rdram, R5900Context *ctx); 18 | void memcmp(uint8_t *rdram, R5900Context *ctx); 19 | 20 | // String operations 21 | void strcpy(uint8_t *rdram, R5900Context *ctx); 22 | void strncpy(uint8_t *rdram, R5900Context *ctx); 23 | void strlen(uint8_t *rdram, R5900Context *ctx); 24 | void strcmp(uint8_t *rdram, R5900Context *ctx); 25 | void strncmp(uint8_t *rdram, R5900Context *ctx); 26 | void strcat(uint8_t *rdram, R5900Context *ctx); 27 | void strncat(uint8_t *rdram, R5900Context *ctx); 28 | void strchr(uint8_t *rdram, R5900Context *ctx); 29 | void strrchr(uint8_t *rdram, R5900Context *ctx); 30 | void strstr(uint8_t *rdram, R5900Context *ctx); 31 | 32 | // I/O operations 33 | void printf(uint8_t *rdram, R5900Context *ctx); 34 | void sprintf(uint8_t *rdram, R5900Context *ctx); 35 | void snprintf(uint8_t *rdram, R5900Context *ctx); 36 | void puts(uint8_t *rdram, R5900Context *ctx); 37 | void fopen(uint8_t *rdram, R5900Context *ctx); 38 | void fclose(uint8_t *rdram, R5900Context *ctx); 39 | void fread(uint8_t *rdram, R5900Context *ctx); 40 | void fwrite(uint8_t *rdram, R5900Context *ctx); 41 | void fprintf(uint8_t *rdram, R5900Context *ctx); 42 | void fseek(uint8_t *rdram, R5900Context *ctx); 43 | void ftell(uint8_t *rdram, R5900Context *ctx); 44 | void fflush(uint8_t *rdram, R5900Context *ctx); 45 | 46 | // Math functions 47 | void sqrt(uint8_t *rdram, R5900Context *ctx); 48 | void sin(uint8_t *rdram, R5900Context *ctx); 49 | void cos(uint8_t *rdram, R5900Context *ctx); 50 | void tan(uint8_t *rdram, R5900Context *ctx); 51 | void atan2(uint8_t *rdram, R5900Context *ctx); 52 | void pow(uint8_t *rdram, R5900Context *ctx); 53 | void exp(uint8_t *rdram, R5900Context *ctx); 54 | void log(uint8_t *rdram, R5900Context *ctx); 55 | void log10(uint8_t *rdram, R5900Context *ctx); 56 | void ceil(uint8_t *rdram, R5900Context *ctx); 57 | void floor(uint8_t *rdram, R5900Context *ctx); 58 | void fabs(uint8_t *rdram, R5900Context *ctx); 59 | 60 | void TODO(uint8_t *rdram, R5900Context *ctx); 61 | } 62 | 63 | #endif // PS2_STUBS_H 64 | -------------------------------------------------------------------------------- /ps2xRuntime/include/ps2_syscalls.h: -------------------------------------------------------------------------------- 1 | #ifndef PS2_SYSCALLS_H 2 | #define PS2_SYSCALLS_H 3 | 4 | #include "ps2_runtime.h" 5 | #include 6 | 7 | static std::mutex g_sys_fd_mutex; 8 | 9 | #define PS2_FIO_O_RDONLY 0x0001 10 | #define PS2_FIO_O_WRONLY 0x0002 11 | #define PS2_FIO_O_RDWR 0x0003 12 | #define PS2_FIO_O_NBLOCK 0x0010 13 | #define PS2_FIO_O_APPEND 0x0100 14 | #define PS2_FIO_O_CREAT 0x0200 15 | #define PS2_FIO_O_TRUNC 0x0400 16 | #define PS2_FIO_O_EXCL 0x0800 17 | #define PS2_FIO_O_NOWAIT 0x8000 18 | 19 | #define PS2_SEEK_SET 0 20 | #define PS2_SEEK_CUR 1 21 | #define PS2_SEEK_END 2 22 | 23 | namespace ps2_syscalls 24 | { 25 | void FlushCache(uint8_t *rdram, R5900Context *ctx); 26 | void ResetEE(uint8_t *rdram, R5900Context *ctx); 27 | void SetMemoryMode(uint8_t *rdram, R5900Context *ctx); 28 | 29 | void CreateThread(uint8_t *rdram, R5900Context *ctx); 30 | void DeleteThread(uint8_t *rdram, R5900Context *ctx); 31 | void StartThread(uint8_t *rdram, R5900Context *ctx); 32 | void ExitThread(uint8_t *rdram, R5900Context *ctx); 33 | void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx); 34 | void TerminateThread(uint8_t *rdram, R5900Context *ctx); 35 | void SuspendThread(uint8_t *rdram, R5900Context *ctx); 36 | void ResumeThread(uint8_t *rdram, R5900Context *ctx); 37 | void GetThreadId(uint8_t *rdram, R5900Context *ctx); 38 | void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx); 39 | void SleepThread(uint8_t *rdram, R5900Context *ctx); 40 | void WakeupThread(uint8_t *rdram, R5900Context *ctx); 41 | void iWakeupThread(uint8_t *rdram, R5900Context *ctx); 42 | void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx); 43 | void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx); 44 | void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx); 45 | void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx); 46 | 47 | void CreateSema(uint8_t *rdram, R5900Context *ctx); 48 | void DeleteSema(uint8_t *rdram, R5900Context *ctx); 49 | void SignalSema(uint8_t *rdram, R5900Context *ctx); 50 | void iSignalSema(uint8_t *rdram, R5900Context *ctx); 51 | void WaitSema(uint8_t *rdram, R5900Context *ctx); 52 | void PollSema(uint8_t *rdram, R5900Context *ctx); 53 | void iPollSema(uint8_t *rdram, R5900Context *ctx); 54 | void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx); 55 | void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx); 56 | 57 | void CreateEventFlag(uint8_t *rdram, R5900Context *ctx); 58 | void DeleteEventFlag(uint8_t *rdram, R5900Context *ctx); 59 | void SetEventFlag(uint8_t *rdram, R5900Context *ctx); 60 | void iSetEventFlag(uint8_t *rdram, R5900Context *ctx); 61 | void ClearEventFlag(uint8_t *rdram, R5900Context *ctx); 62 | void iClearEventFlag(uint8_t *rdram, R5900Context *ctx); 63 | void WaitEventFlag(uint8_t *rdram, R5900Context *ctx); 64 | void PollEventFlag(uint8_t *rdram, R5900Context *ctx); 65 | void iPollEventFlag(uint8_t *rdram, R5900Context *ctx); 66 | void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx); 67 | void iReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx); 68 | 69 | void SetAlarm(uint8_t *rdram, R5900Context *ctx); 70 | void iSetAlarm(uint8_t *rdram, R5900Context *ctx); 71 | void CancelAlarm(uint8_t *rdram, R5900Context *ctx); 72 | void iCancelAlarm(uint8_t *rdram, R5900Context *ctx); 73 | 74 | void EnableIntc(uint8_t *rdram, R5900Context *ctx); 75 | void DisableIntc(uint8_t *rdram, R5900Context *ctx); 76 | void EnableDmac(uint8_t *rdram, R5900Context *ctx); 77 | void DisableDmac(uint8_t *rdram, R5900Context *ctx); 78 | 79 | void SifStopModule(uint8_t *rdram, R5900Context *ctx); 80 | void SifLoadModule(uint8_t *rdram, R5900Context *ctx); 81 | void SifInitRpc(uint8_t *rdram, R5900Context *ctx); 82 | void SifBindRpc(uint8_t *rdram, R5900Context *ctx); 83 | void SifCallRpc(uint8_t *rdram, R5900Context *ctx); 84 | void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx); 85 | void SifCheckStatRpc(uint8_t *rdram, R5900Context *ctx); 86 | void SifSetRpcQueue(uint8_t *rdram, R5900Context *ctx); 87 | void SifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx); 88 | void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx); 89 | 90 | void fioOpen(uint8_t *rdram, R5900Context *ctx); 91 | void fioClose(uint8_t *rdram, R5900Context *ctx); 92 | void fioRead(uint8_t *rdram, R5900Context *ctx); 93 | void fioWrite(uint8_t *rdram, R5900Context *ctx); 94 | void fioLseek(uint8_t *rdram, R5900Context *ctx); 95 | void fioMkdir(uint8_t *rdram, R5900Context *ctx); 96 | void fioChdir(uint8_t *rdram, R5900Context *ctx); 97 | void fioRmdir(uint8_t *rdram, R5900Context *ctx); 98 | void fioGetstat(uint8_t *rdram, R5900Context *ctx); 99 | void fioRemove(uint8_t *rdram, R5900Context *ctx); 100 | 101 | void GsSetCrt(uint8_t *rdram, R5900Context *ctx); 102 | void GsGetIMR(uint8_t *rdram, R5900Context *ctx); 103 | void GsPutIMR(uint8_t *rdram, R5900Context *ctx); 104 | void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx); 105 | 106 | void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx); 107 | void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx); 108 | void GetRomName(uint8_t *rdram, R5900Context *ctx); 109 | void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx); 110 | void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx); 111 | 112 | void TODO(uint8_t *rdram, R5900Context *ctx); 113 | } 114 | 115 | #endif // PS2_SYSCALLS_H -------------------------------------------------------------------------------- /ps2xRuntime/include/register_functions.h: -------------------------------------------------------------------------------- 1 | #ifndef REGISTER_FUNCTIONS_H 2 | #define REGISTER_FUNCTIONS_H 3 | 4 | #include "ps2_runtime.h" 5 | 6 | void registerAllFunctions(PS2Runtime &runtime); 7 | 8 | #endif // REGISTER_FUNCTIONS_H -------------------------------------------------------------------------------- /ps2xRuntime/main_example.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2_runtime.h" 2 | #include 3 | #include 4 | 5 | // Example of how to use the PS2 runtime with recompiled code 6 | 7 | // Stub implementation for PS2 syscalls 8 | void syscall(uint8_t *rdram, R5900Context *ctx) 9 | { 10 | uint32_t syscallNum = ctx->r[4].m128i_u32[0]; 11 | std::cout << "Syscall " << syscallNum << " called" << std::endl; 12 | 13 | switch (syscallNum) 14 | { 15 | case 0x01: // Exit program 16 | std::cout << "Program requested exit with code: " << ctx->r[5].m128i_u32[0] << std::endl; 17 | break; 18 | 19 | case 0x3C: // PutChar - print a character to stdout 20 | std::cout << (char)ctx->r[5].m128i_u32[0]; 21 | break; 22 | 23 | case 0x3D: // PutString - print a string to stdout 24 | { 25 | uint32_t strAddr = ctx->r[5].m128i_u32[0]; 26 | if (strAddr == 0) 27 | { 28 | std::cout << "(null)"; 29 | } 30 | else 31 | { 32 | uint32_t physAddr = strAddr & 0x1FFFFFFF; 33 | const char *str = reinterpret_cast(rdram + physAddr); 34 | std::cout << str; 35 | } 36 | } 37 | break; 38 | 39 | default: 40 | std::cout << "Unhandled syscall: " << syscallNum << std::endl; 41 | break; 42 | } 43 | } 44 | 45 | // Example implementation of FlushCache 46 | void FlushCache(uint8_t *rdram, R5900Context *ctx) 47 | { 48 | uint32_t cacheType = ctx->r[4].m128i_u32[0]; 49 | std::cout << "FlushCache called with type: " << cacheType << std::endl; 50 | } 51 | 52 | // Example implementation of a recompiled function 53 | void recompiled_main(uint8_t *rdram, R5900Context *ctx) 54 | { 55 | std::cout << "Running recompiled main function" << std::endl; 56 | 57 | // Example of memory access 58 | uint32_t addr = 0x100000; // Some address in memory 59 | uint32_t physAddr = addr & 0x1FFFFFFF; 60 | uint32_t value = *reinterpret_cast(rdram + physAddr); 61 | std::cout << "Value at 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl; 62 | 63 | // Example of register manipulation 64 | ctx->r[2] = _mm_set1_epi32(0x12345678); // Set register v0 65 | ctx->r[4] = _mm_set1_epi32(0x3D); // Set register a0 for syscall (PutString) 66 | ctx->r[5] = _mm_set1_epi32(0x10000); // Set register a1 with string address 67 | 68 | // Call a "syscall" function 69 | syscall(rdram, ctx); 70 | 71 | // Example of returning a value 72 | ctx->r[2] = _mm_set1_epi32(0); // Return 0 (success) 73 | } 74 | 75 | int main(int argc, char *argv[]) 76 | { 77 | if (argc < 2) 78 | { 79 | std::cout << "Usage: " << argv[0] << " " << std::endl; 80 | return 1; 81 | } 82 | 83 | std::string elfPath = argv[1]; 84 | 85 | PS2Runtime runtime; 86 | if (!runtime.initialize()) 87 | { 88 | std::cerr << "Failed to initialize PS2 runtime" << std::endl; 89 | return 1; 90 | } 91 | 92 | // Register built-in functions 93 | runtime.registerFunction(0x00000001, syscall); 94 | runtime.registerFunction(0x00000002, FlushCache); 95 | runtime.registerFunction(0x00100000, recompiled_main); // Example address for main 96 | 97 | // Load the ELF file 98 | if (!runtime.loadELF(elfPath)) 99 | { 100 | std::cerr << "Failed to load ELF file: " << elfPath << std::endl; 101 | return 1; 102 | } 103 | 104 | // Run the program 105 | runtime.run(); 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /ps2xRuntime/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2_runtime.h" 2 | #include "register_functions.h" 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | if (argc < 2) 9 | { 10 | std::cout << "Usage: " << argv[0] << " " << std::endl; 11 | return 1; 12 | } 13 | 14 | std::string elfPath = argv[1]; 15 | 16 | PS2Runtime runtime; 17 | if (!runtime.initialize("ps2xRuntime (Raylib host)")) 18 | { 19 | std::cerr << "Failed to initialize PS2 runtime" << std::endl; 20 | return 1; 21 | } 22 | 23 | registerAllFunctions(runtime); 24 | 25 | if (!runtime.loadELF(elfPath)) 26 | { 27 | std::cerr << "Failed to load ELF file: " << elfPath << std::endl; 28 | return 1; 29 | } 30 | 31 | runtime.run(); 32 | 33 | return 0; 34 | } -------------------------------------------------------------------------------- /ps2xRuntime/src/ps2_memory.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2_runtime.h" 2 | #include 3 | #include 4 | #include 5 | 6 | PS2Memory::PS2Memory() 7 | : m_rdram(nullptr), m_scratchpad(nullptr) 8 | { 9 | } 10 | 11 | PS2Memory::~PS2Memory() 12 | { 13 | if (m_rdram) 14 | { 15 | delete[] m_rdram; 16 | m_rdram = nullptr; 17 | } 18 | 19 | if (m_scratchpad) 20 | { 21 | delete[] m_scratchpad; 22 | m_scratchpad = nullptr; 23 | } 24 | } 25 | 26 | bool PS2Memory::initialize(size_t ramSize) 27 | { 28 | try 29 | { 30 | // Allocate main RAM 31 | m_rdram = new uint8_t[ramSize]; 32 | if (!m_rdram) 33 | { 34 | std::cerr << "Failed to allocate " << ramSize << " bytes for RDRAM" << std::endl; 35 | return false; 36 | } 37 | std::memset(m_rdram, 0, ramSize); 38 | 39 | // Allocate scratchpad 40 | m_scratchpad = new uint8_t[PS2_SCRATCHPAD_SIZE]; 41 | if (!m_scratchpad) 42 | { 43 | std::cerr << "Failed to allocate " << PS2_SCRATCHPAD_SIZE << " bytes for scratchpad" << std::endl; 44 | delete[] m_rdram; 45 | m_rdram = nullptr; 46 | return false; 47 | } 48 | std::memset(m_scratchpad, 0, PS2_SCRATCHPAD_SIZE); 49 | 50 | // Initialize TLB entries 51 | m_tlbEntries.clear(); 52 | 53 | // Allocate IOP RAM 54 | iop_ram = new uint8_t[2 * 1024 * 1024]; // 2MB 55 | if (!iop_ram) 56 | { 57 | delete[] m_rdram; 58 | delete[] m_scratchpad; 59 | m_rdram = nullptr; 60 | m_scratchpad = nullptr; 61 | return false; 62 | } 63 | 64 | // Initialize IOP RAM with zeros 65 | std::memset(iop_ram, 0, 2 * 1024 * 1024); 66 | 67 | // Initialize I/O registers 68 | m_ioRegisters.clear(); 69 | 70 | // Initialize GS registers 71 | memset(&gs_regs, 0, sizeof(gs_regs)); 72 | 73 | // Initialize VIF registers 74 | memset(&vif0_regs, 0, sizeof(vif0_regs)); 75 | memset(&vif1_regs, 0, sizeof(vif1_regs)); 76 | 77 | // Initialize DMA registers 78 | memset(dma_regs, 0, sizeof(dma_regs)); 79 | 80 | return true; 81 | } 82 | catch (const std::exception &e) 83 | { 84 | std::cerr << "Error initializing PS2 memory: " << e.what() << std::endl; 85 | return false; 86 | } 87 | } 88 | 89 | uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) 90 | { 91 | // Handle special memory regions 92 | if (virtualAddress >= PS2_SCRATCHPAD_BASE && virtualAddress < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 93 | { 94 | // Scratchpad is directly mapped 95 | return virtualAddress - PS2_SCRATCHPAD_BASE; 96 | } 97 | 98 | // For RDRAM, mask the address to get the physical address 99 | if (virtualAddress < PS2_RAM_SIZE || 100 | (virtualAddress >= 0x80000000 && virtualAddress < 0x80000000 + PS2_RAM_SIZE)) 101 | { 102 | // KSEG0 is directly mapped, just mask out the high bits 103 | return virtualAddress & 0x1FFFFFFF; 104 | } 105 | 106 | // For addresses that need TLB lookup 107 | if (virtualAddress >= 0xC0000000) 108 | { 109 | for (const auto &entry : m_tlbEntries) 110 | { 111 | if (entry.valid) 112 | { 113 | uint32_t vpn_masked = (virtualAddress >> 12) & ~entry.mask; 114 | uint32_t entry_vpn_masked = entry.vpn & ~entry.mask; 115 | 116 | if (vpn_masked == entry_vpn_masked) 117 | { 118 | // TLB hit 119 | uint32_t offset = virtualAddress & 0xFFF; // Page offset 120 | uint32_t page = entry.pfn | (virtualAddress & entry.mask); 121 | return (page << 12) | offset; 122 | } 123 | } 124 | } 125 | // TLB miss 126 | throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress)); 127 | } 128 | 129 | // Default to simple masking for other addresses 130 | return virtualAddress & 0x1FFFFFFF; 131 | } 132 | 133 | uint8_t PS2Memory::read8(uint32_t address) 134 | { 135 | uint32_t physAddr = translateAddress(address); 136 | 137 | if (physAddr < PS2_RAM_SIZE) 138 | { 139 | return m_rdram[physAddr]; 140 | } 141 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 142 | { 143 | return m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]; 144 | } 145 | else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) 146 | { 147 | // IO registers - often not handled byte by byte 148 | uint32_t regAddr = physAddr & ~0x3; // Align to word boundary 149 | if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) 150 | { 151 | uint32_t value = m_ioRegisters[regAddr]; 152 | uint32_t shift = (physAddr & 3) * 8; 153 | return (value >> shift) & 0xFF; 154 | } 155 | return 0; // Unimplemented IO register 156 | } 157 | 158 | // Handle other memory regions ,for now return 0 for unimplemented regions 159 | return 0; 160 | } 161 | 162 | uint16_t PS2Memory::read16(uint32_t address) 163 | { 164 | // Check alignment 165 | if (address & 1) 166 | { 167 | throw std::runtime_error("Unaligned 16-bit read at address: 0x" + std::to_string(address)); 168 | } 169 | 170 | uint32_t physAddr = translateAddress(address); 171 | 172 | if (physAddr < PS2_RAM_SIZE) 173 | { 174 | return *reinterpret_cast(&m_rdram[physAddr]); 175 | } 176 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 177 | { 178 | return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); 179 | } 180 | else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) 181 | { 182 | // IO registers - align to word boundary and extract relevant bits 183 | uint32_t regAddr = physAddr & ~0x3; 184 | if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) 185 | { 186 | uint32_t value = m_ioRegisters[regAddr]; 187 | uint32_t shift = (physAddr & 2) * 8; 188 | return (value >> shift) & 0xFFFF; 189 | } 190 | return 0; // Unimplemented IO register 191 | } 192 | 193 | return 0; 194 | } 195 | 196 | uint32_t PS2Memory::read32(uint32_t address) 197 | { 198 | // Check alignment 199 | if (address & 3) 200 | { 201 | throw std::runtime_error("Unaligned 32-bit read at address: 0x" + std::to_string(address)); 202 | } 203 | 204 | uint32_t physAddr = translateAddress(address); 205 | 206 | if (physAddr < PS2_RAM_SIZE) 207 | { 208 | return *reinterpret_cast(&m_rdram[physAddr]); 209 | } 210 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 211 | { 212 | return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); 213 | } 214 | else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) 215 | { 216 | // IO registers 217 | if (m_ioRegisters.find(physAddr) != m_ioRegisters.end()) 218 | { 219 | return m_ioRegisters[physAddr]; 220 | } 221 | return 0; // Unimplemented IO register 222 | } 223 | 224 | return 0; 225 | } 226 | 227 | uint64_t PS2Memory::read64(uint32_t address) 228 | { 229 | // Check alignment 230 | if (address & 7) 231 | { 232 | throw std::runtime_error("Unaligned 64-bit read at address: 0x" + std::to_string(address)); 233 | } 234 | 235 | uint32_t physAddr = translateAddress(address); 236 | 237 | if (physAddr < PS2_RAM_SIZE) 238 | { 239 | return *reinterpret_cast(&m_rdram[physAddr]); 240 | } 241 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 242 | { 243 | return *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]); 244 | } 245 | 246 | // 64-bit IO operations are not common, but who knows 247 | return (uint64_t)read32(address) | ((uint64_t)read32(address + 4) << 32); 248 | } 249 | 250 | __m128i PS2Memory::read128(uint32_t address) 251 | { 252 | // Check alignment 253 | if (address & 15) 254 | { 255 | throw std::runtime_error("Unaligned 128-bit read at address: 0x" + std::to_string(address)); 256 | } 257 | 258 | uint32_t physAddr = translateAddress(address); 259 | 260 | if (physAddr < PS2_RAM_SIZE) 261 | { 262 | return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr])); 263 | } 264 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 265 | { 266 | return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE])); 267 | } 268 | 269 | // 128-bit reads are primarily for quad-word loads in the EE, which are only valid for RAM areas 270 | // Return zeroes for unsupported areas 271 | return _mm_setzero_si128(); 272 | } 273 | 274 | void PS2Memory::write8(uint32_t address, uint8_t value) 275 | { 276 | uint32_t physAddr = translateAddress(address); 277 | 278 | if (physAddr < PS2_RAM_SIZE) 279 | { 280 | m_rdram[physAddr] = value; 281 | } 282 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 283 | { 284 | m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE] = value; 285 | } 286 | else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) 287 | { 288 | // IO registers - handle byte writes by modifying the appropriate byte in the word 289 | uint32_t regAddr = physAddr & ~0x3; 290 | uint32_t shift = (physAddr & 3) * 8; 291 | uint32_t mask = ~(0xFF << shift); 292 | uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); 293 | m_ioRegisters[regAddr] = newValue; 294 | 295 | // Handle potential side effects of IO register writes 296 | } 297 | } 298 | 299 | void PS2Memory::write16(uint32_t address, uint16_t value) 300 | { 301 | // Check alignment 302 | if (address & 1) 303 | { 304 | throw std::runtime_error("Unaligned 16-bit write at address: 0x" + std::to_string(address)); 305 | } 306 | 307 | uint32_t physAddr = translateAddress(address); 308 | 309 | if (physAddr < PS2_RAM_SIZE) 310 | { 311 | *reinterpret_cast(&m_rdram[physAddr]) = value; 312 | } 313 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 314 | { 315 | *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; 316 | } 317 | else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) 318 | { 319 | // IO registers - handle halfword writes 320 | uint32_t regAddr = physAddr & ~0x3; 321 | uint32_t shift = (physAddr & 2) * 8; 322 | uint32_t mask = ~(0xFFFF << shift); 323 | uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); 324 | m_ioRegisters[regAddr] = newValue; 325 | 326 | // Handle potential side effects of IO register writes 327 | } 328 | } 329 | 330 | void PS2Memory::write32(uint32_t address, uint32_t value) 331 | { 332 | // Check alignment 333 | if (address & 3) 334 | { 335 | throw std::runtime_error("Unaligned 32-bit write at address: 0x" + std::to_string(address)); 336 | } 337 | 338 | uint32_t physAddr = translateAddress(address); 339 | 340 | if (physAddr < PS2_RAM_SIZE) 341 | { 342 | // Check if this might be code modification 343 | markModified(address, 4); 344 | 345 | *reinterpret_cast(&m_rdram[physAddr]) = value; 346 | } 347 | else if (physAddr >= 0x70000000 && physAddr < 0x70004000) 348 | { // Scratchpad 349 | *reinterpret_cast(&m_scratchpad[physAddr - 0x70000000]) = value; 350 | } 351 | else if (physAddr >= 0x10000000 && physAddr < 0x10010000) 352 | { 353 | // Handle IO register writes with potential side effects 354 | writeIORegister(physAddr, value); 355 | } 356 | } 357 | 358 | void PS2Memory::write64(uint32_t address, uint64_t value) 359 | { 360 | // Check alignment 361 | if (address & 7) 362 | { 363 | throw std::runtime_error("Unaligned 64-bit write at address: 0x" + std::to_string(address)); 364 | } 365 | 366 | uint32_t physAddr = translateAddress(address); 367 | 368 | if (physAddr < PS2_RAM_SIZE) 369 | { 370 | *reinterpret_cast(&m_rdram[physAddr]) = value; 371 | } 372 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 373 | { 374 | *reinterpret_cast(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value; 375 | } 376 | else 377 | { 378 | // Split into two 32-bit writes for other memory regions 379 | write32(address, (uint32_t)value); 380 | write32(address + 4, (uint32_t)(value >> 32)); 381 | } 382 | } 383 | 384 | void PS2Memory::write128(uint32_t address, __m128i value) 385 | { 386 | // Check alignment 387 | if (address & 15) 388 | { 389 | throw std::runtime_error("Unaligned 128-bit write at address: 0x" + std::to_string(address)); 390 | } 391 | 392 | uint32_t physAddr = translateAddress(address); 393 | 394 | if (physAddr < PS2_RAM_SIZE) 395 | { 396 | _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value); 397 | } 398 | else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE) 399 | { 400 | _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]), value); 401 | } 402 | else 403 | { 404 | // Split into smaller writes for other memory regions 405 | // Extract the data using SSE intrinsics 406 | uint64_t lo = _mm_extract_epi64(value, 0); 407 | uint64_t hi = _mm_extract_epi64(value, 1); 408 | 409 | write64(address, lo); 410 | write64(address + 8, hi); 411 | } 412 | } 413 | 414 | bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) 415 | { 416 | m_ioRegisters[address] = value; 417 | 418 | // Now check if this is a special hardware register 419 | if (address >= 0x10000000 && address < 0x10010000) 420 | { 421 | // Timer/counter registers 422 | if (address >= 0x10000000 && address < 0x10000100) 423 | { 424 | std::cout << "Timer register write: " << std::hex << address << " = " << value << std::dec << std::endl; 425 | return true; 426 | } 427 | 428 | // DMA registers 429 | if (address >= 0x10008000 && address < 0x1000F000) 430 | { 431 | std::cout << "DMA register write: " << std::hex << address << " = " << value << std::dec << std::endl; 432 | 433 | // Check if we need to start a DMA transfer 434 | if ((address & 0xFF) == 0x00) 435 | { // CHCR registers 436 | if (value & 0x100) 437 | { 438 | uint32_t channelBase = address & 0xFFFFFF00; 439 | uint32_t madr = m_ioRegisters[channelBase + 0x10]; // Memory address 440 | uint32_t qwc = m_ioRegisters[channelBase + 0x20]; // Quadword count 441 | 442 | std::cout << "Starting DMA transfer on channel " << ((address >> 8) & 0xF) 443 | << ", MADR: " << std::hex << madr 444 | << ", QWC: " << qwc << std::dec << std::endl; 445 | 446 | // Would actually start DMA here 447 | } 448 | } 449 | return true; 450 | } 451 | 452 | // Interrupt control registers 453 | if (address >= 0x10000200 && address < 0x10000300) 454 | { 455 | std::cout << "Interrupt register write: " << std::hex << address << " = " << value << std::dec << std::endl; 456 | // Handle interrupt register side effects 457 | return true; 458 | } 459 | } 460 | else if (address >= 0x12000000 && address < 0x12001000) 461 | { 462 | // GS registers 463 | std::cout << "GS register write: " << std::hex << address << " = " << value << std::dec << std::endl; 464 | // Handle GS register side effects 465 | return true; 466 | } 467 | 468 | return false; 469 | } 470 | 471 | uint32_t PS2Memory::readIORegister(uint32_t address) 472 | { 473 | auto it = m_ioRegisters.find(address); 474 | if (it != m_ioRegisters.end()) 475 | { 476 | return it->second; 477 | } 478 | 479 | // Special cases for reads from hardware registers that have side effects 480 | if (address >= 0x10000000 && address < 0x10010000) 481 | { 482 | // Timer registers 483 | if (address >= 0x10000000 && address < 0x10000100) 484 | { 485 | if ((address & 0xF) == 0x00) 486 | { // COUNT registers 487 | uint32_t timerCount = 0; // Should calculate based on elapsed time 488 | std::cout << "Timer COUNT read: " << std::hex << address << " = " << timerCount << std::dec << std::endl; 489 | return timerCount; 490 | } 491 | } 492 | 493 | // DMA status registers 494 | if (address >= 0x10008000 && address < 0x1000F000) 495 | { 496 | if ((address & 0xFF) == 0x00) 497 | { // CHCR registers 498 | uint32_t channelStatus = m_ioRegisters[address] & ~0x100; // Clear busy bit 499 | std::cout << "DMA status read: " << std::hex << address << " = " << channelStatus << std::dec << std::endl; 500 | return channelStatus; 501 | } 502 | } 503 | 504 | // Interrupt status registers 505 | if (address >= 0x10000200 && address < 0x10000300) 506 | { 507 | std::cout << "Interrupt status read: " << std::hex << address << std::dec << std::endl; 508 | // Should calculate based on pending interrupts 509 | return 0; 510 | } 511 | } 512 | 513 | return 0; 514 | } 515 | 516 | void PS2Memory::registerCodeRegion(uint32_t start, uint32_t end) 517 | { 518 | CodeRegion region; 519 | region.start = start; 520 | region.end = end; 521 | 522 | // Initialize the modified bitmap (one bit per 4-byte word) 523 | size_t sizeInWords = (end - start) / 4; 524 | region.modified.resize(sizeInWords, false); 525 | 526 | m_codeRegions.push_back(region); 527 | std::cout << "Registered code region: " << std::hex << start << " - " << end << std::dec << std::endl; 528 | } 529 | 530 | bool PS2Memory::isAddressInRegion(uint32_t address, const CodeRegion ®ion) 531 | { 532 | return (address >= region.start && address < region.end); 533 | } 534 | 535 | void PS2Memory::markModified(uint32_t address, uint32_t size) 536 | { 537 | for (auto ®ion : m_codeRegions) 538 | { 539 | if (address + size <= region.start || address >= region.end) 540 | { 541 | continue; 542 | } 543 | 544 | uint32_t overlapStart = std::max(address, region.start); 545 | uint32_t overlapEnd = std::min(address + size, region.end); 546 | 547 | // Mark each 4-byte word in the overlap as modified 548 | for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) 549 | { 550 | size_t bitIndex = (addr - region.start) / 4; 551 | if (bitIndex < region.modified.size()) 552 | { 553 | region.modified[bitIndex] = true; 554 | std::cout << "Marked code at " << std::hex << addr << std::dec << " as modified" << std::endl; 555 | } 556 | } 557 | } 558 | } 559 | 560 | bool PS2Memory::isCodeModified(uint32_t address, uint32_t size) 561 | { 562 | for (const auto ®ion : m_codeRegions) 563 | { 564 | if (address + size <= region.start || address >= region.end) 565 | { 566 | continue; 567 | } 568 | 569 | // Calculate overlap 570 | uint32_t overlapStart = std::max(address, region.start); 571 | uint32_t overlapEnd = std::min(address + size, region.end); 572 | 573 | // Check each 4-byte word in the overlap 574 | for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) 575 | { 576 | size_t bitIndex = (addr - region.start) / 4; 577 | if (bitIndex < region.modified.size() && region.modified[bitIndex]) 578 | { 579 | return true; // Found modified code 580 | } 581 | } 582 | } 583 | 584 | return false; // No modifications found 585 | } 586 | 587 | void PS2Memory::clearModifiedFlag(uint32_t address, uint32_t size) 588 | { 589 | for (auto ®ion : m_codeRegions) 590 | { 591 | if (address + size <= region.start || address >= region.end) 592 | { 593 | continue; 594 | } 595 | 596 | // Calculate overlap 597 | uint32_t overlapStart = std::max(address, region.start); 598 | uint32_t overlapEnd = std::min(address + size, region.end); 599 | 600 | // Clear flags for each 4-byte word in the overlap 601 | for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) 602 | { 603 | size_t bitIndex = (addr - region.start) / 4; 604 | if (bitIndex < region.modified.size()) 605 | { 606 | region.modified[bitIndex] = false; 607 | } 608 | } 609 | } 610 | } -------------------------------------------------------------------------------- /ps2xRuntime/src/ps2_runtime.cpp: -------------------------------------------------------------------------------- 1 | #include "ps2_runtime.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "raylib.h" 7 | 8 | #define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian 9 | #define ET_EXEC 2 // Executable file 10 | 11 | #define EM_MIPS 8 // MIPS architecture 12 | 13 | struct ElfHeader 14 | { 15 | uint32_t magic; 16 | uint8_t elf_class; 17 | uint8_t endianness; 18 | uint8_t version; 19 | uint8_t os_abi; 20 | uint8_t abi_version; 21 | uint8_t padding[7]; 22 | uint16_t type; 23 | uint16_t machine; 24 | uint32_t version2; 25 | uint32_t entry; 26 | uint32_t phoff; 27 | uint32_t shoff; 28 | uint32_t flags; 29 | uint16_t ehsize; 30 | uint16_t phentsize; 31 | uint16_t phnum; 32 | uint16_t shentsize; 33 | uint16_t shnum; 34 | uint16_t shstrndx; 35 | }; 36 | 37 | struct ProgramHeader 38 | { 39 | uint32_t type; 40 | uint32_t offset; 41 | uint32_t vaddr; 42 | uint32_t paddr; 43 | uint32_t filesz; 44 | uint32_t memsz; 45 | uint32_t flags; 46 | uint32_t align; 47 | }; 48 | 49 | #define PT_LOAD 1 // Loadable segment 50 | 51 | static constexpr int FB_WIDTH = 640; 52 | static constexpr int FB_HEIGHT = 448; 53 | static constexpr uint32_t DEFAULT_FB_ADDR = 0x00100000; // location in RDRAM the guest will draw to 54 | 55 | static void UploadFrame(Texture2D &tex, PS2Runtime *rt) 56 | { 57 | uint8_t *src = rt->memory().getRDRAM() + (DEFAULT_FB_ADDR & 0x1FFFFFFF); 58 | UpdateTexture(tex, src); 59 | } 60 | 61 | PS2Runtime::PS2Runtime() 62 | { 63 | std::memset(&m_cpuContext, 0, sizeof(m_cpuContext)); 64 | 65 | // R0 is always zero in MIPS 66 | m_cpuContext.r[0] = _mm_set1_epi32(0); 67 | 68 | // Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF 69 | 70 | m_functionTable.clear(); 71 | 72 | m_loadedModules.clear(); 73 | } 74 | 75 | PS2Runtime::~PS2Runtime() 76 | { 77 | m_loadedModules.clear(); 78 | 79 | m_functionTable.clear(); 80 | } 81 | 82 | bool PS2Runtime::initialize(const char *title) 83 | { 84 | if (!m_memory.initialize()) 85 | { 86 | std::cerr << "Failed to initialize PS2 memory" << std::endl; 87 | return false; 88 | } 89 | 90 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 91 | InitWindow(FB_WIDTH, FB_HEIGHT, title); 92 | SetTargetFPS(60); 93 | 94 | return true; 95 | } 96 | 97 | bool PS2Runtime::loadELF(const std::string &elfPath) 98 | { 99 | std::ifstream file(elfPath, std::ios::binary); 100 | if (!file) 101 | { 102 | std::cerr << "Failed to open ELF file: " << elfPath << std::endl; 103 | return false; 104 | } 105 | 106 | ElfHeader header; 107 | file.read(reinterpret_cast(&header), sizeof(header)); 108 | 109 | if (header.magic != ELF_MAGIC) 110 | { 111 | std::cerr << "Invalid ELF magic number" << std::endl; 112 | return false; 113 | } 114 | 115 | if (header.machine != EM_MIPS || header.type != ET_EXEC) 116 | { 117 | std::cerr << "Not a MIPS executable ELF file" << std::endl; 118 | return false; 119 | } 120 | 121 | m_cpuContext.pc = header.entry; 122 | 123 | for (uint16_t i = 0; i < header.phnum; i++) 124 | { 125 | ProgramHeader ph; 126 | file.seekg(header.phoff + i * header.phentsize); 127 | file.read(reinterpret_cast(&ph), sizeof(ph)); 128 | 129 | if (ph.type == PT_LOAD && ph.filesz > 0) 130 | { 131 | std::cout << "Loading segment: 0x" << std::hex << ph.vaddr 132 | << " - 0x" << (ph.vaddr + ph.memsz) 133 | << " (size: 0x" << ph.memsz << ")" << std::dec << std::endl; 134 | 135 | // Allocate temporary buffer for the segment 136 | std::vector buffer(ph.filesz); 137 | 138 | // Read segment data 139 | file.seekg(ph.offset); 140 | file.read(reinterpret_cast(buffer.data()), ph.filesz); 141 | 142 | // Copy to memory 143 | uint32_t physAddr = m_memory.translateAddress(ph.vaddr); 144 | uint8_t *dest = m_memory.getRDRAM() + physAddr; 145 | std::memcpy(dest, buffer.data(), ph.filesz); 146 | 147 | if (ph.memsz > ph.filesz) 148 | { 149 | std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); 150 | } 151 | } 152 | } 153 | 154 | LoadedModule module; 155 | module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1); 156 | module.baseAddress = 0x00100000; // Typical base address for PS2 executables 157 | module.size = 0; // Would need to calculate from segments 158 | module.active = true; 159 | 160 | m_loadedModules.push_back(module); 161 | 162 | std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; 163 | return true; 164 | } 165 | 166 | void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func) 167 | { 168 | m_functionTable[address] = func; 169 | } 170 | 171 | PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address) 172 | { 173 | auto it = m_functionTable.find(address); 174 | if (it != m_functionTable.end()) 175 | { 176 | return it->second; 177 | } 178 | 179 | std::cerr << "Warning: Function at address 0x" << std::hex << address << std::dec << " not found" << std::endl; 180 | 181 | static RecompiledFunction defaultFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) 182 | { 183 | std::cerr << "Error: Called unimplemented function at address 0x" << std::hex << ctx->pc << std::dec << std::endl; 184 | }; 185 | 186 | return defaultFunction; 187 | } 188 | 189 | void PS2Runtime::SignalException(R5900Context *ctx, PS2Exception exception) 190 | { 191 | if (exception == EXCEPTION_INTEGER_OVERFLOW) 192 | { 193 | // PS2 behavior: jump to exception handler 194 | HandleIntegerOverflow(ctx); 195 | } 196 | } 197 | 198 | void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address) 199 | { 200 | std::cout << "VU0 microprogram call to address 0x" << std::hex << address 201 | << " - not implemented" << std::dec << std::endl; 202 | 203 | // mayve implement like this or a vu0_interpreter 204 | // Placeholder for VU0 microprogram execution 205 | // auto microprog = findCompiledMicroprogram(address); 206 | // if (microprog) microprog(rdram, ctx); 207 | } 208 | 209 | void PS2Runtime::vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address) 210 | { 211 | std::cout << "VU0 microprogram call to address 0x" << std::hex << address 212 | << " - not implemented" << std::dec << std::endl; 213 | } 214 | 215 | void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx) 216 | { 217 | std::cout << "Syscall encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 218 | } 219 | 220 | void PS2Runtime::handleBreak(uint8_t *rdram, R5900Context *ctx) 221 | { 222 | std::cout << "Break encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 223 | } 224 | 225 | void PS2Runtime::handleTrap(uint8_t *rdram, R5900Context *ctx) 226 | { 227 | std::cout << "Trap encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 228 | } 229 | 230 | void PS2Runtime::handleTLBR(uint8_t *rdram, R5900Context *ctx) 231 | { 232 | std::cout << "TLBR (TLB Read) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 233 | } 234 | 235 | void PS2Runtime::handleTLBWI(uint8_t *rdram, R5900Context *ctx) 236 | { 237 | std::cout << "TLBWI (TLB Write Indexed) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 238 | } 239 | 240 | void PS2Runtime::handleTLBWR(uint8_t *rdram, R5900Context *ctx) 241 | { 242 | std::cout << "TLBWR (TLB Write Random) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 243 | } 244 | 245 | void PS2Runtime::handleTLBP(uint8_t *rdram, R5900Context *ctx) 246 | { 247 | std::cout << "TLBP (TLB Probe) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 248 | } 249 | 250 | void PS2Runtime::clearLLBit(R5900Context *ctx) 251 | { 252 | ctx->cop0_status &= ~0x00000002; // LL bit is bit 1 in the status register 253 | std::cout << "LL bit cleared at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 254 | } 255 | 256 | void PS2Runtime::HandleIntegerOverflow(R5900Context *ctx) 257 | { 258 | std::cerr << "Integer overflow exception at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; 259 | 260 | // Set the EPC (Exception Program Counter) to the current PC 261 | m_cpuContext.cop0_epc = ctx->pc; 262 | 263 | // Set the cause register to indicate an integer overflow 264 | m_cpuContext.cop0_cause |= (EXCEPTION_INTEGER_OVERFLOW << 2); 265 | 266 | // Jump to the exception handler (usually at 0x80000000) 267 | m_cpuContext.pc = 0x80000000; // Default PS2 exception handler address 268 | } 269 | 270 | void PS2Runtime::run() 271 | { 272 | RecompiledFunction entryPoint = lookupFunction(m_cpuContext.pc); 273 | 274 | m_cpuContext.r[4] = _mm_set1_epi32(0); // A0 = 0 (argc) 275 | m_cpuContext.r[5] = _mm_set1_epi32(0); // A1 = 0 (argv) 276 | m_cpuContext.r[29] = _mm_set1_epi32(0x02000000); // SP = top of RAM 277 | 278 | std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; 279 | 280 | try 281 | { 282 | // Call the entry point function 283 | entryPoint(m_memory.getRDRAM(), &m_cpuContext, this); 284 | 285 | std::cout << "Program execution completed successfully" << std::endl; 286 | 287 | // A blank image to use as a framebuffer 288 | Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK); 289 | Texture2D frameTex = LoadTextureFromImage(blank); 290 | UnloadImage(blank); 291 | 292 | while (!WindowShouldClose()) 293 | { 294 | // TODO step only a small slice each host 295 | auto self = this; 296 | lookupFunction(memory().read32(0))( 297 | memory().getRDRAM(), 298 | &cpu(), 299 | self); 300 | 301 | UploadFrame(frameTex, self); 302 | 303 | BeginDrawing(); 304 | ClearBackground(BLACK); 305 | DrawTexture(frameTex, 0, 0, WHITE); 306 | EndDrawing(); 307 | } 308 | 309 | CloseWindow(); 310 | } 311 | catch (const std::exception &e) 312 | { 313 | std::cerr << "Error during program execution: " << e.what() << std::endl; 314 | } 315 | } -------------------------------------------------------------------------------- /ps2xRuntime/src/ps2_syscalls.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "ps2_syscalls.h" 3 | #include "ps2_runtime.h" 4 | #include "ps2_stubs.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | std::unordered_map g_fileDescriptors; 15 | int g_nextFd = 3; // Start after stdin, stdout, stderr 16 | 17 | int allocatePs2Fd(FILE *file) 18 | { 19 | if (!file) 20 | return -1; 21 | int fd = g_nextFd++; 22 | g_fileDescriptors[fd] = file; 23 | return fd; 24 | } 25 | 26 | FILE *getHostFile(int ps2Fd) 27 | { 28 | auto it = g_fileDescriptors.find(ps2Fd); 29 | if (it != g_fileDescriptors.end()) 30 | { 31 | return it->second; 32 | } 33 | return nullptr; 34 | } 35 | 36 | void releasePs2Fd(int ps2Fd) 37 | { 38 | g_fileDescriptors.erase(ps2Fd); 39 | } 40 | 41 | const char *translateFioMode(int ps2Flags) 42 | { 43 | bool read = (ps2Flags & PS2_FIO_O_RDONLY) || (ps2Flags & PS2_FIO_O_RDWR); 44 | bool write = (ps2Flags & PS2_FIO_O_WRONLY) || (ps2Flags & PS2_FIO_O_RDWR); 45 | bool append = (ps2Flags & PS2_FIO_O_APPEND); 46 | bool create = (ps2Flags & PS2_FIO_O_CREAT); 47 | bool truncate = (ps2Flags & PS2_FIO_O_TRUNC); 48 | 49 | if (read && write) 50 | { 51 | if (create && truncate) 52 | return "w+b"; 53 | if (create) 54 | return "a+b"; 55 | return "r+b"; 56 | } 57 | else if (write) 58 | { 59 | if (append) 60 | return "ab"; 61 | if (create && truncate) 62 | return "wb"; 63 | if (create) 64 | return "wx"; 65 | return "r+b"; 66 | } 67 | else if (read) 68 | { 69 | return "rb"; 70 | } 71 | return "rb"; 72 | } 73 | 74 | std::string translatePs2Path(const char *ps2Path) 75 | { 76 | std::string pathStr(ps2Path); 77 | if (pathStr.rfind("host0:", 0) == 0) 78 | { 79 | // Map host0: to ./host_fs/ relative to executable 80 | std::filesystem::path hostBasePath = std::filesystem::current_path() / "host_fs"; 81 | std::filesystem::create_directories(hostBasePath); // Ensure it exists 82 | return (hostBasePath / pathStr.substr(6)).string(); 83 | } 84 | else if (pathStr.rfind("cdrom0:", 0) == 0) 85 | { 86 | // Map cdrom0: to ./cd_fs/ relative to executable (for example) 87 | std::filesystem::path cdBasePath = std::filesystem::current_path() / "cd_fs"; 88 | std::filesystem::create_directories(cdBasePath); // Ensure it exists 89 | return (cdBasePath / pathStr.substr(7)).string(); 90 | } 91 | std::cerr << "Warning: Unsupported PS2 path prefix: " << pathStr << std::endl; 92 | return ""; 93 | } 94 | 95 | #include "ps2_syscalls.h" 96 | 97 | namespace ps2_syscalls 98 | { 99 | void FlushCache(uint8_t *rdram, R5900Context *ctx) 100 | { 101 | std::cout << "Syscall: FlushCache (No-op)" << std::endl; 102 | // No-op for now 103 | setReturnS32(ctx, 0); 104 | } 105 | 106 | void ResetEE(uint8_t *rdram, R5900Context *ctx) 107 | { 108 | std::cerr << "Syscall: ResetEE - Halting Execution (Not fully implemented)" << std::endl; 109 | exit(0); // Should we exit or just halt the execution? 110 | } 111 | 112 | void SetMemoryMode(uint8_t *rdram, R5900Context *ctx) 113 | { 114 | // Affects memory mapping / TLB behavior. 115 | // std::cout << "Syscall: SetMemoryMode (No-op)" << std::endl; 116 | setReturnS32(ctx, 0); // Success 117 | } 118 | 119 | void CreateThread(uint8_t *rdram, R5900Context *ctx) 120 | { 121 | // TODO 122 | } 123 | 124 | void DeleteThread(uint8_t *rdram, R5900Context *ctx) 125 | { 126 | // TODO 127 | } 128 | 129 | void StartThread(uint8_t *rdram, R5900Context *ctx) 130 | { 131 | // TODO 132 | } 133 | 134 | void ExitThread(uint8_t *rdram, R5900Context *ctx) 135 | { 136 | // TODO 137 | std::cout << "PS2 ExitThread: Thread is exiting (PC=0x" << std::hex << ctx->pc << std::dec << ")" << std::endl; 138 | } 139 | 140 | void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx) 141 | { 142 | // TODO 143 | } 144 | 145 | void TerminateThread(uint8_t *rdram, R5900Context *ctx) 146 | { 147 | // TODO 148 | } 149 | 150 | void SuspendThread(uint8_t *rdram, R5900Context *ctx) 151 | { 152 | // TODO 153 | } 154 | 155 | void ResumeThread(uint8_t *rdram, R5900Context *ctx) 156 | { 157 | // TODO 158 | } 159 | 160 | void GetThreadId(uint8_t *rdram, R5900Context *ctx) 161 | { 162 | // TODO 163 | } 164 | 165 | void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx) 166 | { 167 | // TODO 168 | } 169 | 170 | void SleepThread(uint8_t *rdram, R5900Context *ctx) 171 | { 172 | // TODO 173 | } 174 | 175 | void WakeupThread(uint8_t *rdram, R5900Context *ctx) 176 | { 177 | // TODO 178 | } 179 | 180 | void iWakeupThread(uint8_t *rdram, R5900Context *ctx) 181 | { 182 | // TODO 183 | } 184 | 185 | void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx) 186 | { 187 | // TODO 188 | } 189 | 190 | void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx) 191 | { 192 | // TODO 193 | } 194 | 195 | void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx) 196 | { 197 | // TODO 198 | } 199 | 200 | void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx) 201 | { 202 | // TODO 203 | } 204 | 205 | void CreateSema(uint8_t *rdram, R5900Context *ctx) 206 | { 207 | // TODO 208 | } 209 | 210 | void DeleteSema(uint8_t *rdram, R5900Context *ctx) 211 | { 212 | // TODO 213 | } 214 | 215 | void SignalSema(uint8_t *rdram, R5900Context *ctx) 216 | { 217 | // TODO 218 | } 219 | 220 | void iSignalSema(uint8_t *rdram, R5900Context *ctx) 221 | { 222 | // TODO 223 | } 224 | 225 | void WaitSema(uint8_t *rdram, R5900Context *ctx) 226 | { 227 | // TODO 228 | } 229 | 230 | void PollSema(uint8_t *rdram, R5900Context *ctx) 231 | { 232 | // TODO 233 | } 234 | 235 | void iPollSema(uint8_t *rdram, R5900Context *ctx) 236 | { 237 | // TODO 238 | } 239 | 240 | void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx) 241 | { 242 | // TODO 243 | } 244 | 245 | void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx) 246 | { 247 | // TODO 248 | } 249 | 250 | void CreateEventFlag(uint8_t *rdram, R5900Context *ctx) 251 | { 252 | // TODO 253 | } 254 | 255 | void DeleteEventFlag(uint8_t *rdram, R5900Context *ctx) 256 | { 257 | // TODO 258 | } 259 | 260 | void SetEventFlag(uint8_t *rdram, R5900Context *ctx) 261 | { 262 | // TODO 263 | } 264 | 265 | void iSetEventFlag(uint8_t *rdram, R5900Context *ctx) 266 | { 267 | // TODO 268 | } 269 | 270 | void ClearEventFlag(uint8_t *rdram, R5900Context *ctx) 271 | { 272 | // TODO 273 | } 274 | 275 | void iClearEventFlag(uint8_t *rdram, R5900Context *ctx) 276 | { 277 | // TODO 278 | } 279 | 280 | void WaitEventFlag(uint8_t *rdram, R5900Context *ctx) 281 | { 282 | // TODO 283 | } 284 | 285 | void PollEventFlag(uint8_t *rdram, R5900Context *ctx) 286 | { 287 | // TODO 288 | } 289 | 290 | void iPollEventFlag(uint8_t *rdram, R5900Context *ctx) 291 | { 292 | // TODO 293 | } 294 | 295 | void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx) 296 | { 297 | // TODO 298 | } 299 | 300 | void iReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx) 301 | { 302 | // TODO 303 | } 304 | 305 | void SetAlarm(uint8_t *rdram, R5900Context *ctx) 306 | { 307 | // TODO 308 | } 309 | 310 | void iSetAlarm(uint8_t *rdram, R5900Context *ctx) 311 | { 312 | // TODO 313 | } 314 | 315 | void CancelAlarm(uint8_t *rdram, R5900Context *ctx) 316 | { 317 | // TODO 318 | } 319 | 320 | void iCancelAlarm(uint8_t *rdram, R5900Context *ctx) 321 | { 322 | // TODO 323 | } 324 | 325 | void EnableIntc(uint8_t *rdram, R5900Context *ctx) 326 | { 327 | // TODO 328 | } 329 | 330 | void DisableIntc(uint8_t *rdram, R5900Context *ctx) 331 | { 332 | // TODO 333 | } 334 | 335 | void EnableDmac(uint8_t *rdram, R5900Context *ctx) 336 | { 337 | // TODO 338 | } 339 | 340 | void DisableDmac(uint8_t *rdram, R5900Context *ctx) 341 | { 342 | // TODO 343 | } 344 | 345 | void SifStopModule(uint8_t *rdram, R5900Context *ctx) 346 | { 347 | // TODO 348 | } 349 | 350 | void SifLoadModule(uint8_t *rdram, R5900Context *ctx) 351 | { 352 | // TODO 353 | } 354 | 355 | void SifInitRpc(uint8_t *rdram, R5900Context *ctx) 356 | { 357 | // TODO 358 | } 359 | 360 | void SifBindRpc(uint8_t *rdram, R5900Context *ctx) 361 | { 362 | // TODO 363 | } 364 | 365 | void SifCallRpc(uint8_t *rdram, R5900Context *ctx) 366 | { 367 | // TODO 368 | } 369 | 370 | void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx) 371 | { 372 | // TODO 373 | } 374 | 375 | void SifCheckStatRpc(uint8_t *rdram, R5900Context *ctx) 376 | { 377 | // TODO 378 | } 379 | 380 | void SifSetRpcQueue(uint8_t *rdram, R5900Context *ctx) 381 | { 382 | // TODO 383 | } 384 | 385 | void SifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx) 386 | { 387 | // TODO 388 | } 389 | 390 | void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx) 391 | { 392 | // TODO 393 | } 394 | 395 | void fioOpen(uint8_t *rdram, R5900Context *ctx) 396 | { 397 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 398 | int flags = (int)getRegU32(ctx, 5); // $a1 (PS2 FIO flags) 399 | 400 | const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 401 | if (!ps2Path) 402 | { 403 | std::cerr << "fioOpen error: Invalid path address" << std::endl; 404 | setReturnS32(ctx, -1); 405 | return; 406 | } 407 | 408 | std::string hostPath = translatePs2Path(ps2Path); 409 | if (hostPath.empty()) 410 | { 411 | std::cerr << "fioOpen error: Failed to translate path '" << ps2Path << "'" << std::endl; 412 | setReturnS32(ctx, -1); 413 | return; 414 | } 415 | 416 | const char *mode = translateFioMode(flags); 417 | std::cout << "fioOpen: '" << hostPath << "' flags=0x" << std::hex << flags << std::dec << " mode='" << mode << "'" << std::endl; 418 | 419 | FILE *fp = ::fopen(hostPath.c_str(), mode); 420 | if (!fp) 421 | { 422 | std::cerr << "fioOpen error: fopen failed for '" << hostPath << "': " << strerror(errno) << std::endl; 423 | setReturnS32(ctx, -1); // e.g., -ENOENT, -EACCES 424 | return; 425 | } 426 | 427 | int ps2Fd = allocatePs2Fd(fp); 428 | if (ps2Fd < 0) 429 | { 430 | std::cerr << "fioOpen error: Failed to allocate PS2 file descriptor" << std::endl; 431 | ::fclose(fp); 432 | setReturnS32(ctx, -1); // e.g., -EMFILE 433 | return; 434 | } 435 | 436 | // returns the PS2 file descriptor 437 | setReturnS32(ctx, ps2Fd); 438 | } 439 | 440 | void fioClose(uint8_t *rdram, R5900Context *ctx) 441 | { 442 | int ps2Fd = (int)getRegU32(ctx, 4); // $a0 443 | std::cout << "fioClose: fd=" << ps2Fd << std::endl; 444 | 445 | FILE *fp = getHostFile(ps2Fd); 446 | if (!fp) 447 | { 448 | std::cerr << "fioClose warning: Invalid PS2 file descriptor " << ps2Fd << std::endl; 449 | setReturnS32(ctx, -1); // e.g., -EBADF 450 | return; 451 | } 452 | 453 | int ret = ::fclose(fp); 454 | releasePs2Fd(ps2Fd); 455 | 456 | // returns 0 on success, -1 on error 457 | setReturnS32(ctx, ret == 0 ? 0 : -1); 458 | } 459 | 460 | void fioRead(uint8_t *rdram, R5900Context *ctx) 461 | { 462 | int ps2Fd = (int)getRegU32(ctx, 4); // $a0 463 | uint32_t bufAddr = getRegU32(ctx, 5); // $a1 464 | size_t size = getRegU32(ctx, 6); // $a2 465 | 466 | uint8_t *hostBuf = getMemPtr(rdram, bufAddr); 467 | FILE *fp = getHostFile(ps2Fd); 468 | 469 | if (!hostBuf) 470 | { 471 | std::cerr << "fioRead error: Invalid buffer address for fd " << ps2Fd << std::endl; 472 | setReturnS32(ctx, -1); // -EFAULT 473 | return; 474 | } 475 | if (!fp) 476 | { 477 | std::cerr << "fioRead error: Invalid file descriptor " << ps2Fd << std::endl; 478 | setReturnS32(ctx, -1); // -EBADF 479 | return; 480 | } 481 | if (size == 0) 482 | { 483 | setReturnS32(ctx, 0); // Read 0 bytes 484 | return; 485 | } 486 | 487 | size_t bytesRead = 0; 488 | { 489 | std::lock_guard lock(g_sys_fd_mutex); 490 | bytesRead = fread(hostBuf, 1, size, fp); 491 | } 492 | 493 | if (bytesRead < size && ferror(fp)) 494 | { 495 | std::cerr << "fioRead error: fread failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; 496 | clearerr(fp); 497 | setReturnS32(ctx, -1); // -EIO or other appropriate error 498 | return; 499 | } 500 | 501 | // returns number of bytes read (can be 0 for EOF) 502 | setReturnS32(ctx, (int32_t)bytesRead); 503 | } 504 | 505 | void fioWrite(uint8_t *rdram, R5900Context *ctx) 506 | { 507 | int ps2Fd = (int)getRegU32(ctx, 4); // $a0 508 | uint32_t bufAddr = getRegU32(ctx, 5); // $a1 509 | size_t size = getRegU32(ctx, 6); // $a2 510 | 511 | const uint8_t *hostBuf = getConstMemPtr(rdram, bufAddr); 512 | FILE *fp = getHostFile(ps2Fd); 513 | 514 | if (!hostBuf) 515 | { 516 | std::cerr << "fioWrite error: Invalid buffer address for fd " << ps2Fd << std::endl; 517 | setReturnS32(ctx, -1); // -EFAULT 518 | return; 519 | } 520 | if (!fp) 521 | { 522 | std::cerr << "fioWrite error: Invalid file descriptor " << ps2Fd << std::endl; 523 | setReturnS32(ctx, -1); // -EBADF 524 | return; 525 | } 526 | if (size == 0) 527 | { 528 | setReturnS32(ctx, 0); // Wrote 0 bytes 529 | return; 530 | } 531 | 532 | size_t bytesWritten = ::fwrite(hostBuf, 1, size, fp); 533 | 534 | if (bytesWritten < size) 535 | { 536 | if (ferror(fp)) 537 | { 538 | std::cerr << "fioWrite error: fwrite failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; 539 | clearerr(fp); 540 | setReturnS32(ctx, -1); // -EIO, -ENOSPC etc. 541 | } 542 | else 543 | { 544 | // Partial write without error? Possible but idk. 545 | setReturnS32(ctx, (int32_t)bytesWritten); 546 | } 547 | return; 548 | } 549 | 550 | // returns number of bytes written 551 | setReturnS32(ctx, (int32_t)bytesWritten); 552 | } 553 | 554 | void fioLseek(uint8_t *rdram, R5900Context *ctx) 555 | { 556 | int ps2Fd = (int)getRegU32(ctx, 4); // $a0 557 | int32_t offset = getRegU32(ctx, 5); // $a1 (PS2 seems to use 32-bit offset here commonly) 558 | int whence = (int)getRegU32(ctx, 6); // $a2 (PS2 FIO_SEEK constants) 559 | 560 | FILE *fp = getHostFile(ps2Fd); 561 | if (!fp) 562 | { 563 | std::cerr << "fioLseek error: Invalid file descriptor " << ps2Fd << std::endl; 564 | setReturnS32(ctx, -1); // -EBADF 565 | return; 566 | } 567 | 568 | int hostWhence; 569 | switch (whence) 570 | { 571 | case PS2_FIO_SEEK_SET: 572 | hostWhence = SEEK_SET; 573 | break; 574 | case PS2_FIO_SEEK_CUR: 575 | hostWhence = SEEK_CUR; 576 | break; 577 | case PS2_FIO_SEEK_END: 578 | hostWhence = SEEK_END; 579 | break; 580 | default: 581 | std::cerr << "fioLseek error: Invalid whence value " << whence << " for fd " << ps2Fd << std::endl; 582 | setReturnS32(ctx, -1); // -EINVAL 583 | return; 584 | } 585 | 586 | if (::fseek(fp, static_cast(offset), hostWhence) != 0) 587 | { 588 | std::cerr << "fioLseek error: fseek failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; 589 | setReturnS32(ctx, -1); // Return error code 590 | return; 591 | } 592 | 593 | long newPos = ::ftell(fp); 594 | if (newPos < 0) 595 | { 596 | std::cerr << "fioLseek error: ftell failed after fseek for fd " << ps2Fd << ": " << strerror(errno) << std::endl; 597 | setReturnS32(ctx, -1); 598 | } 599 | else 600 | { 601 | // maybe we dont need this check. if position fits in 32 bits 602 | if (newPos > 0xFFFFFFFFL) 603 | { 604 | std::cerr << "fioLseek warning: New position exceeds 32-bit for fd " << ps2Fd << std::endl; 605 | setReturnS32(ctx, -1); 606 | } 607 | else 608 | { 609 | setReturnS32(ctx, (int32_t)newPos); 610 | } 611 | } 612 | } 613 | 614 | void fioMkdir(uint8_t *rdram, R5900Context *ctx) 615 | { 616 | // TODO maybe we dont need this. 617 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 618 | // int mode = (int)getRegU32(ctx, 5); 619 | 620 | const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 621 | if (!ps2Path) 622 | { 623 | std::cerr << "fioMkdir error: Invalid path address" << std::endl; 624 | setReturnS32(ctx, -1); // -EFAULT 625 | return; 626 | } 627 | std::string hostPath = translatePs2Path(ps2Path); 628 | if (hostPath.empty()) 629 | { 630 | std::cerr << "fioMkdir error: Failed to translate path '" << ps2Path << "'" << std::endl; 631 | setReturnS32(ctx, -1); 632 | return; 633 | } 634 | 635 | #ifdef _WIN32 636 | int ret = -1; 637 | #else 638 | int ret = ::mkdir(hostPath.c_str(), 0775); 639 | #endif 640 | 641 | if (ret != 0) 642 | { 643 | std::cerr << "fioMkdir error: mkdir failed for '" << hostPath << "': " << strerror(errno) << std::endl; 644 | setReturnS32(ctx, -1); // errno 645 | } 646 | else 647 | { 648 | setReturnS32(ctx, 0); // Success 649 | } 650 | } 651 | 652 | void fioChdir(uint8_t *rdram, R5900Context *ctx) 653 | { 654 | // TODO maybe we dont need this as well. 655 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 656 | const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 657 | if (!ps2Path) 658 | { 659 | std::cerr << "fioChdir error: Invalid path address" << std::endl; 660 | setReturnS32(ctx, -1); 661 | return; 662 | } 663 | 664 | std::string hostPath = translatePs2Path(ps2Path); 665 | if (hostPath.empty()) 666 | { 667 | std::cerr << "fioChdir error: Failed to translate path '" << ps2Path << "'" << std::endl; 668 | setReturnS32(ctx, -1); 669 | return; 670 | } 671 | 672 | std::cerr << "fioChdir: Attempting host chdir to '" << hostPath << "' (Stub - Check side effects)" << std::endl; 673 | 674 | #ifdef _WIN32 675 | int ret = -1; 676 | #else 677 | int ret = ::chdir(hostPath.c_str()); 678 | #endif 679 | 680 | if (ret != 0) 681 | { 682 | std::cerr << "fioChdir error: chdir failed for '" << hostPath << "': " << strerror(errno) << std::endl; 683 | setReturnS32(ctx, -1); 684 | } 685 | else 686 | { 687 | setReturnS32(ctx, 0); // Success 688 | } 689 | } 690 | 691 | void fioRmdir(uint8_t *rdram, R5900Context *ctx) 692 | { 693 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 694 | const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 695 | if (!ps2Path) 696 | { 697 | std::cerr << "fioRmdir error: Invalid path address" << std::endl; 698 | setReturnS32(ctx, -1); 699 | return; 700 | } 701 | std::string hostPath = translatePs2Path(ps2Path); 702 | if (hostPath.empty()) 703 | { 704 | std::cerr << "fioRmdir error: Failed to translate path '" << ps2Path << "'" << std::endl; 705 | setReturnS32(ctx, -1); 706 | return; 707 | } 708 | 709 | #ifdef _WIN32 710 | int ret = -1; 711 | #else 712 | int ret = ::rmdir(hostPath.c_str()); 713 | #endif 714 | 715 | if (ret != 0) 716 | { 717 | std::cerr << "fioRmdir error: rmdir failed for '" << hostPath << "': " << strerror(errno) << std::endl; 718 | setReturnS32(ctx, -1); 719 | } 720 | else 721 | { 722 | setReturnS32(ctx, 0); // Success 723 | } 724 | } 725 | 726 | void fioGetstat(uint8_t *rdram, R5900Context *ctx) 727 | { 728 | // we wont implement this for now. 729 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 730 | uint32_t statBufAddr = getRegU32(ctx, 5); // $a1 731 | 732 | const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 733 | uint8_t *ps2StatBuf = getMemPtr(rdram, statBufAddr); 734 | 735 | if (!ps2Path) 736 | { 737 | std::cerr << "fioGetstat error: Invalid path addr" << std::endl; 738 | setReturnS32(ctx, -1); 739 | return; 740 | } 741 | if (!ps2StatBuf) 742 | { 743 | std::cerr << "fioGetstat error: Invalid buffer addr" << std::endl; 744 | setReturnS32(ctx, -1); 745 | return; 746 | } 747 | 748 | std::string hostPath = translatePs2Path(ps2Path); 749 | if (hostPath.empty()) 750 | { 751 | std::cerr << "fioGetstat error: Bad path translate" << std::endl; 752 | setReturnS32(ctx, -1); 753 | return; 754 | } 755 | 756 | setReturnS32(ctx, -1); 757 | } 758 | 759 | void fioRemove(uint8_t *rdram, R5900Context *ctx) 760 | { 761 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 762 | const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 763 | if (!ps2Path) 764 | { 765 | std::cerr << "fioRemove error: Invalid path" << std::endl; 766 | setReturnS32(ctx, -1); 767 | return; 768 | } 769 | 770 | std::string hostPath = translatePs2Path(ps2Path); 771 | if (hostPath.empty()) 772 | { 773 | std::cerr << "fioRemove error: Path translate fail" << std::endl; 774 | setReturnS32(ctx, -1); 775 | return; 776 | } 777 | 778 | #ifdef _WIN32 779 | int ret = -1; 780 | #else 781 | int ret = ::unlink(hostPath.c_str()); 782 | #endif 783 | 784 | if (ret != 0) 785 | { 786 | std::cerr << "fioRemove error: unlink failed for '" << hostPath << "': " << strerror(errno) << std::endl; 787 | setReturnS32(ctx, -1); 788 | } 789 | else 790 | { 791 | setReturnS32(ctx, 0); // Success 792 | } 793 | } 794 | 795 | void GsSetCrt(uint8_t *rdram, R5900Context *ctx) 796 | { 797 | int interlaced = getRegU32(ctx, 4); // $a0 - 0=non-interlaced, 1=interlaced 798 | int videoMode = getRegU32(ctx, 5); // $a1 - 0=NTSC, 1=PAL, 2=VESA, 3=HiVision 799 | int frameMode = getRegU32(ctx, 6); // $a2 - 0=field, 1=frame 800 | 801 | std::cout << "PS2 GsSetCrt: interlaced=" << interlaced 802 | << ", videoMode=" << videoMode 803 | << ", frameMode=" << frameMode << std::endl; 804 | } 805 | 806 | void GsGetIMR(uint8_t *rdram, R5900Context *ctx) 807 | { 808 | // TODO return IMR value from the Gs hardware this is just a stub. 809 | // The IMR (Interrupt Mask Register) is a 64-bit register that controls which interrupts are enabled. 810 | uint64_t imr = 0x0000000000000000ULL; 811 | 812 | std::cout << "PS2 GsGetIMR: Returning IMR=0x" << std::hex << imr << std::dec << std::endl; 813 | 814 | setReturnS32(ctx, (int32_t)(imr & 0xFFFFFFFF)); // Return lower 32 bits 815 | setReturnS32(ctx, (int32_t)(imr >> 32)); // Return upper 32 bits 816 | } 817 | 818 | void GsPutIMR(uint8_t *rdram, R5900Context *ctx) 819 | { 820 | uint64_t imr = getRegU32(ctx, 4) | ((uint64_t)getRegU32(ctx, 5) << 32); // $a0 = lower 32 bits, $a1 = upper 32 bits 821 | std::cout << "PS2 GsPutIMR: Setting IMR=0x" << std::hex << imr << std::dec << std::endl; 822 | // Do nothing for now. 823 | } 824 | 825 | void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx) 826 | { 827 | int mode = getRegU32(ctx, 4); // $a0 - video mode (various flags) 828 | 829 | std::cout << "PS2 GsSetVideoMode: mode=0x" << std::hex << mode << std::dec << std::endl; 830 | 831 | // Do nothing for now. 832 | } 833 | 834 | void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx) 835 | { 836 | uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure 837 | 838 | if (!getMemPtr(rdram, paramAddr)) 839 | { 840 | std::cerr << "PS2 GetOsdConfigParam error: Invalid parameter address: 0x" 841 | << std::hex << paramAddr << std::dec << std::endl; 842 | setReturnS32(ctx, -1); 843 | return; 844 | } 845 | 846 | uint32_t *param = reinterpret_cast(getMemPtr(rdram, paramAddr)); 847 | 848 | // Default to English language, USA region 849 | *param = 0x00000000; 850 | 851 | std::cout << "PS2 GetOsdConfigParam: Retrieved OSD parameters" << std::endl; 852 | 853 | setReturnS32(ctx, 0); 854 | } 855 | 856 | void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx) 857 | { 858 | uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure 859 | 860 | if (!getConstMemPtr(rdram, paramAddr)) 861 | { 862 | std::cerr << "PS2 SetOsdConfigParam error: Invalid parameter address: 0x" 863 | << std::hex << paramAddr << std::dec << std::endl; 864 | setReturnS32(ctx, -1); 865 | return; 866 | } 867 | 868 | // TODO save user preferences 869 | std::cout << "PS2 SetOsdConfigParam: Set OSD parameters" << std::endl; 870 | 871 | setReturnS32(ctx, 0); 872 | } 873 | 874 | void GetRomName(uint8_t *rdram, R5900Context *ctx) 875 | { 876 | uint32_t bufAddr = getRegU32(ctx, 4); // $a0 877 | size_t bufSize = getRegU32(ctx, 5); // $a1 878 | char *hostBuf = reinterpret_cast(getMemPtr(rdram, bufAddr)); 879 | const char *romName = "ROMVER 0100"; 880 | 881 | if (!hostBuf) 882 | { 883 | std::cerr << "GetRomName error: Invalid buffer address" << std::endl; 884 | setReturnS32(ctx, -1); // Error 885 | return; 886 | } 887 | if (bufSize == 0) 888 | { 889 | setReturnS32(ctx, 0); 890 | return; 891 | } 892 | 893 | strncpy(hostBuf, romName, bufSize - 1); 894 | hostBuf[bufSize - 1] = '\0'; 895 | 896 | // returns the length of the string (excluding null?) or error 897 | setReturnS32(ctx, (int32_t)strlen(hostBuf)); 898 | } 899 | 900 | void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx) 901 | { 902 | uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - pointer to ELF path 903 | 904 | const char *elfPath = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); 905 | 906 | std::cout << "PS2 SifLoadElfPart: Would load ELF from " << elfPath << std::endl; 907 | setReturnS32(ctx, 1); // dummy return value for success 908 | } 909 | 910 | void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx) 911 | { 912 | uint32_t moduePath = getRegU32(ctx, 4); // $a0 - pointer to module path 913 | 914 | // Extract path 915 | const char *modulePath = reinterpret_cast(getConstMemPtr(rdram, moduePath)); 916 | 917 | std::cout << "PS2 SifLoadModule: Would load module from " << moduePath << std::endl; 918 | 919 | setReturnS32(ctx, 1); 920 | } 921 | 922 | void TODO(uint8_t *rdram, R5900Context *ctx) 923 | { 924 | uint32_t syscall_num = getRegU32(ctx, 3); // Syscall number usually in $v1 ($r3) for SYSCALL instr 925 | uint32_t caller_ra = getRegU32(ctx, 31); // $ra 926 | 927 | std::cerr << "Warning: Unimplemented PS2 syscall called. PC=0x" << std::hex << ctx->pc 928 | << ", RA=0x" << caller_ra 929 | << ", Syscall # (from $v1)=0x" << syscall_num << std::dec << std::endl; 930 | 931 | std::cerr << " Args: $a0=0x" << std::hex << getRegU32(ctx, 4) 932 | << ", $a1=0x" << getRegU32(ctx, 5) 933 | << ", $a2=0x" << getRegU32(ctx, 6) 934 | << ", $a3=0x" << getRegU32(ctx, 7) << std::dec << std::endl; 935 | 936 | // Common syscalls: 937 | // 0x04: Exit 938 | // 0x06: LoadExecPS2 939 | // 0x07: ExecPS2 940 | if (syscall_num == 0x04) 941 | { 942 | std::cerr << " -> Syscall is Exit(), calling ExitThread stub." << std::endl; 943 | ExitThread(rdram, ctx); 944 | return; 945 | } 946 | 947 | // Return generic error for unimplemented ones 948 | setReturnS32(ctx, -1); // Return -ENOSYS or similar? Use -1 for simplicity. 949 | } 950 | } -------------------------------------------------------------------------------- /ps2xRuntime/src/register_functions.cpp: -------------------------------------------------------------------------------- 1 | #include "register_functions.h" 2 | 3 | // Replace this file with the actual implementation of the functions that was generated by the compiler 4 | void registerAllFunctions(PS2Runtime &runtime) 5 | { 6 | } --------------------------------------------------------------------------------