From 8f747334d61a70118090988da3274931f26d9724 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Fri, 13 Feb 2026 15:02:34 -0300 Subject: [PATCH] feat: refactor analyzer to not realy only on debug symbols (#53) fix: patching NOP things that code gen already know how to handle feat: added a bug on JAL/J/JAR code gen feat: enhanced tom file fix: fix memory layout for ps2 macros feat: added a lot of not working garbabe to runtime (fix later) feat: some code organization feat: added new tests feat: update readme --- README.md | 116 +- ps2xAnalyzer/CMakeLists.txt | 35 +- ps2xAnalyzer/include/ps2recomp/elf_analyzer.h | 70 +- ps2xAnalyzer/src/elf_analyzer.cpp | 1289 +++-- ps2xRecomp/include/ps2recomp/code_generator.h | 1 - ps2xRecomp/include/ps2recomp/elf_parser.h | 1 + ps2xRecomp/include/ps2recomp/ps2_recompiler.h | 25 +- ps2xRecomp/include/ps2recomp/types.h | 22 +- ps2xRecomp/src/lib/code_generator.cpp | 774 +-- ps2xRecomp/src/lib/config_manager.cpp | 39 + ps2xRecomp/src/lib/elf_parser.cpp | 52 + ps2xRecomp/src/lib/ps2_recompiler.cpp | 425 +- ps2xRecomp/src/lib/r5900_decoder.cpp | 4 +- ps2xRuntime/include/ps2_call_list.h | 3 + ps2xRuntime/include/ps2_runtime.h | 532 +- ps2xRuntime/include/ps2_runtime_macros.h | 412 +- ps2xRuntime/include/ps2_stubs.h | 3 + ps2xRuntime/include/ps2_syscalls.h | 24 +- ps2xRuntime/src/lib/ps2_memory.cpp | 607 ++- ps2xRuntime/src/lib/ps2_runtime.cpp | 1006 +++- ps2xRuntime/src/lib/ps2_stubs.cpp | 2900 +++++++++-- ps2xRuntime/src/lib/ps2_syscalls.cpp | 4282 ++++++++++++++++- ps2xTest/CMakeLists.txt | 8 + ps2xTest/src/code_generator_tests.cpp | 231 +- ps2xTest/src/elf_analyzer_tests.cpp | 235 + ps2xTest/src/main.cpp | 2 + 26 files changed, 10987 insertions(+), 2111 deletions(-) create mode 100644 ps2xTest/src/elf_analyzer_tests.cpp diff --git a/README.md b/README.md index 79a18b0..db0cb4a 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,108 @@ -## PS2Recomp: PlayStation 2 Static Recompiler (Not ready) +## PS2Recomp: PlayStation 2 Static Recompiler (Experimental) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white)](https://discord.gg/JQ8mawxUEf) -* Note this is an experiment and doesn't work as it should, feel free to open a PR to help the project. +Also check our [WIKI](https://github.com/ran-j/PS2Recomp/wiki) -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. + +This project statically recompiles PS2 ELF binaries into C++ and provides a runtime to execute the generated code. + +### Modules + +* `ps2xAnalyzer`: scans ELF/functions and writes TOML config (`stubs`, `skip`, instruction patches). +* `ps2xRecomp`: reads TOML + ELF, decodes R5900 instructions, and generates C++ output. +* `ps2xRuntime`: hosts memory, function registration, syscall dispatch, and hardware stubs. ### Features * Translates MIPS R5900 instructions to C++ code -* Supports PS2-specific 128-bit MMI instructions -* Handles VU0 in macro mode -* Supports relocations and overlays -* Configurable via TOML files -* Single-file or multi-file output options -* Function stubbing and skipping +* PS2-specific MMI and VU0 macro support. +* Single-file or multi-file output. +* Configurable stubs, skips, and instruction patches. +* Instruction-driven syscall handling. ### How It Works PS2Recomp works by: -Parsing a PS2 ELF file to extract functions, symbols, and relocations -Decoding the MIPS R5900 instructions in each function -Translating those instructions to equivalent C++ code -Generating a runtime that can execute the recompiled code +* Parsing a PS2 ELF file to extract functions, symbols, and relocations +* Decoding the MIPS R5900 instructions in each function +* Translating those instructions to equivalent C++ code +* Generating a runtime that can execute the recompiled code The translated code is very literal, with each MIPS instruction mapping to a C++ operation. For example, `addiu $r4, $r4, 0x20` becomes `ctx->r4 = ADD32(ctx->r4, 0X20);`. +### Current Behavior + +* `stubs` entries generate wrappers that call known runtime syscall/stub handlers by name. +* `skip` entries are not recompiled and generate explicit `ps2_stubs::TODO_NAMED(...)` wrappers. +* Recompiled `SYSCALL` now calls `runtime->handleSyscall(...)` with the encoded syscall immediate. +* Runtime syscall dispatch tries encoded syscall ID first, then falls back to `$v1`. + ### Requirements -* CMake 3.20 or higher -* C++20 compatible compiler (I only test with MSVC) -* SSE4/AVX support for 128-bit operations +* CMake 3.20+ +* C++20 compiler (currently tested mainly with MSVC) +* SSE4/AVX host support for some vector paths + +### Build -#### Building ```bash git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git cd PS2Recomp -# Create build directory -mkdir build -cd build - -cmake .. -cmake --build . +cmake -S . -B out/build +cmake --build out/build --config Debug ``` + ### Usage -1. **Analyze the ELF**: Use the `ps2_analyzer` tool to generate an initial configuration. +1. Analyze ELF and generate config: + ```bash ./ps2_analyzer your_game.elf config.toml ``` *For better results on retail games, see the [Ghidra Workflow](ps2xAnalyzer/Readme.md#3-ghidra-integration-recommended-for-complex-games).* -2. **Recompile**: Run the recompiler using the generated configuration. +2. Recompile using generated TOML: + ```bash -./ps2recomp config.toml +./ps2_recomp config.toml ``` -3. **Compile Output**: -* Compile the generated C++ code in the `output/` directory. -* Link with the `ps2xRuntime` implementation. +3. Build generated output and link with `ps2xRuntime`. ### Configuration -PS2Recomp uses TOML configuration files to specify: -* Input ELF file -* Output directory -* Functions to stub or skip -* Instruction patches +Main fields in `config.toml`: + +* `general.input`: source ELF path. +* `general.ghidra_output`: optional function map CSV. +* `general.output`: generated C++ output folder. +* `general.single_file_output`: one combined cpp or one file per function. +* `general.patch_syscalls`: apply configured patches to `SYSCALL` instructions (`false` recommended). +* `general.patch_cop0`: apply configured patches to COP0 instructions. +* `general.patch_cache`: apply configured patches to CACHE instructions. +* `general.stubs`: names to force as stubs. +* `general.skip`: names to force as skipped wrappers. +* `patches.instructions`: raw instruction replacements by address. + +Example: -#### Example configuration: ```toml [general] input = "path/to/game.elf" +ghidra_output = "" output = "output/" -single_file_output = false -# Functions to stub +single_file_output = true +patch_syscalls = false +patch_cop0 = true +patch_cache = true + stubs = ["printf", "malloc", "free"] -# Functions to skip skip = ["abort", "exit"] -# Patches [patches] instructions = [ { address = "0x100004", value = "0x00000000" } @@ -90,23 +110,25 @@ instructions = [ ``` ### Runtime -To execute the recompiled code, you'll need to implement or use a runtime that provides: -* Memory management -* System call handling -* PS2-specific hardware simulation +To execute the recompiled code. -A basic runtime lib is provided in `ps2xRuntime` folder. +`ps2xRuntime` currently provides: + +* Guest memory model and function dispatch table. +* Some syscall dispatcher with common kernel IDs. +* Basic GS/VU/file/system stubs. +* Foundation to expand and port your game. ### Limitations -* VU1 microcode support is limited * Graphics Synthesizer and other hardware components need external implementation -* Some PS2-specific features may not be fully supported yet +* VU1 microcode is not complete. +* Hardware emulation is partial and many paths are stubbed. ### Acknowledgments * Inspired by N64Recomp * Uses ELFIO for ELF parsing * Uses toml11 for TOML parsing -* Uses fmt for string formatting +* Uses fmt for string formatting \ No newline at end of file diff --git a/ps2xAnalyzer/CMakeLists.txt b/ps2xAnalyzer/CMakeLists.txt index b076eeb..b374c2a 100644 --- a/ps2xAnalyzer/CMakeLists.txt +++ b/ps2xAnalyzer/CMakeLists.txt @@ -3,23 +3,32 @@ project(PS2Analyzer VERSION 0.1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) - -file(GLOB_RECURSE PS2ANALYZER_SOURCES - "src/*.cpp" + +set(PS2ANALYZER_LIB_SOURCES + src/elf_analyzer.cpp ) - -add_executable(ps2_analyzer ${PS2ANALYZER_SOURCES}) - -target_include_directories(ps2_analyzer PRIVATE + +add_library(ps2_analyzer_lib STATIC ${PS2ANALYZER_LIB_SOURCES}) + +target_include_directories(ps2_analyzer_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/ps2xRecomp/include ) - -target_link_libraries(ps2_analyzer PRIVATE - fmt::fmt + +target_link_libraries(ps2_analyzer_lib PUBLIC ps2_recomp_lib ) - -install(TARGETS ps2_analyzer + +add_executable(ps2_analyzer + src/analyzer_main.cpp +) + +target_link_libraries(ps2_analyzer PRIVATE + ps2_analyzer_lib +) + +install(TARGETS ps2_analyzer ps2_analyzer_lib RUNTIME DESTINATION bin -) \ No newline at end of file + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h b/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h index dba6896..25175c3 100644 --- a/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h +++ b/ps2xAnalyzer/include/ps2recomp/elf_analyzer.h @@ -8,30 +8,43 @@ #include #include #include +#include namespace ps2recomp { - struct CFGNode; - struct Instruction; - struct FunctionCall; - struct JumpTable; - struct Relocation; - struct Section; - struct Symbol; - struct Function; - class R5900Decoder; - class ElfParser; + struct CFGNode; + struct Instruction; + struct FunctionCall; + struct JumpTable; + struct Relocation; + struct Section; + struct Symbol; + struct Function; + class R5900Decoder; + class ElfParser; - using CFG = std::unordered_map; + using CFG = std::unordered_map; - class ElfAnalyzer + class ElfAnalyzer { public: - explicit ElfAnalyzer(const std::string &elfPath); + explicit ElfAnalyzer(const std::string &elfPath); ~ElfAnalyzer(); bool analyze(); bool generateToml(const std::string &outputPath); + bool isLibrarySymbolNameForHeuristics(const std::string &name) const; + static bool isReliableSymbolNameForHeuristics(const std::string &name); + static bool isSystemSymbolNameForHeuristics(const std::string &name); + static bool shouldAutoSkipNameForHeuristics(const std::string &name); + static int findEntryFunctionIndexForHeuristics(const std::vector &functions, uint32_t entryAddress); + static int findFallbackEntryFunctionIndexForHeuristics(const std::vector &functions); + static bool hasHardwareIOSignalForHeuristics(const std::vector &instructions); + static bool hasLargeComplexMMISignalForHeuristics(const std::vector &instructions, size_t largeInstructionThreshold = 500); + static bool hasSelfModifyingSignalForHeuristics(const std::vector &instructions, const std::vector
§ions); + static bool shouldSkipForPatchDensityForHeuristics(const std::string &functionName, uint32_t functionSizeBytes, size_t patchCount, bool isLibraryFunction); + static std::vector detectJumpTablesForHeuristics(const std::vector &instructions, const std::vector
§ions, const std::function &readWord); + static std::unordered_set findRecursiveFunctionsForHeuristics(const std::unordered_map> &callGraph); private: std::string m_elfPath; @@ -42,24 +55,42 @@ namespace ps2recomp std::vector m_symbols; std::vector
m_sections; std::vector m_relocations; - + std::unordered_set m_libFunctions; std::unordered_set m_skipFunctions; + std::unordered_set m_forceRecompileStarts; std::unordered_set m_knownLibNames; std::unordered_map> m_functionDataUsage; std::unordered_map m_commonDataAccess; - + std::map m_patches; std::map m_patchReasons; std::unordered_map m_functionCFGs; std::vector m_jumpTables; std::unordered_map> m_functionCalls; - + + std::unordered_map m_mmioByInstructionAddress; + void initializeLibraryFunctions(); void analyzeEntryPoint(); void analyzeLibraryFunctions(); void analyzeDataUsage(); + void identifyPotentialPatches(); + bool tryPatchSelfModifyingStore(const Function &func, + const std::vector &instructions, + size_t index); + bool tryResolveBasePlusOffset(const std::vector &instructions, + size_t index, + uint32_t reg, + int16_t offset, + uint32_t &baseAddr) const; + bool tryResolveLuiBase(const std::vector &instructions, + size_t index, + uint32_t reg, + uint32_t &baseAddr) const; + bool isCodeAddress(uint32_t addr) const; + void analyzeControlFlow(); void detectJumpTables(); void analyzePerformanceCriticalPaths() const; @@ -67,12 +98,12 @@ namespace ps2recomp void analyzeRegisterUsage() const; void analyzeFunctionSignatures() const; void optimizePatches(); - + bool identifyMemcpyPattern(const Function &func) const; bool identifyMemsetPattern(const Function &func) const; bool identifyStringOperationPattern(const Function &func) const; bool identifyMathPattern(const Function &func) const; - + bool isSystemFunction(const std::string &name) const; bool isLibraryFunction(const std::string &name) const; std::vector decodeFunction(const Function &function) const; @@ -81,6 +112,7 @@ namespace ps2recomp std::string escapeBackslashes(const std::string &path); bool hasMMIInstructions(const Function &function) const; bool hasVUInstructions(const Function &function) const; + bool shouldAutoSkipByHeuristic(const Function &function) const; bool identifyFunctionType(const Function &function); void categorizeFunction(Function &function); uint32_t getSuccessor(const Instruction &inst, uint32_t currentAddr); @@ -89,4 +121,4 @@ namespace ps2recomp }; } -#endif // PS2RECOMP_ELF_ANALYZER_H \ No newline at end of file +#endif // PS2RECOMP_ELF_ANALYZER_H diff --git a/ps2xAnalyzer/src/elf_analyzer.cpp b/ps2xAnalyzer/src/elf_analyzer.cpp index 4f9683d..95bcb8e 100644 --- a/ps2xAnalyzer/src/elf_analyzer.cpp +++ b/ps2xAnalyzer/src/elf_analyzer.cpp @@ -13,12 +13,14 @@ #include #include #include +#include namespace fs = std::filesystem; namespace ps2recomp { static bool hasPs2ApiPrefix(const std::string &name); + static bool hasReliableSymbolName(const std::string &name); static bool isDoNotSkipOrStub(const std::string &name); static uint32_t decodeAbsoluteJumpTarget(uint32_t instructionAddress, uint32_t targetField); static bool tryReadWord(const ElfParser *parser, uint32_t address, uint32_t &outWord); @@ -55,7 +57,6 @@ namespace ps2recomp std::cout << "Extracted " << m_relocations.size() << " relocations" << std::endl; analyzeEntryPoint(); - analyzeLibraryFunctions(); analyzeDataUsage(); identifyPotentialPatches(); analyzeControlFlow(); @@ -64,14 +65,16 @@ namespace ps2recomp identifyRecursiveFunctions(); analyzeRegisterUsage(); analyzeFunctionSignatures(); + analyzeLibraryFunctions(); optimizePatches(); for (auto &func : m_functions) { + categorizeFunction(func); + if (!m_skipFunctions.contains(func.name) && !m_libFunctions.contains(func.name)) { - categorizeFunction(func); func.instructions = decodeFunction(func); } } @@ -125,12 +128,93 @@ namespace ps2recomp file << "# Path to output directory\n"; file << "output = \"" << escapeBackslashes(outputDirStr) << "\"\n\n"; - file << "# Single file output mode (false for one file per function)\n"; - file << "single_file_output = true\n\n"; + file << "# Single file output mode (recommended for large games)\n"; + file << "single_file_output = false\n\n"; + + file << "# Patch policy (instruction-driven handling is preferred for syscalls)\n"; + file << "patch_syscalls = false\n"; + file << "patch_cop0 = true\n"; + file << "patch_cache = true\n\n"; + + std::unordered_map functionNameCounts; + functionNameCounts.reserve(m_functions.size()); + for (const auto &func : m_functions) + { + if (!func.name.empty()) + { + functionNameCounts[func.name]++; + } + } + + auto makeSelector = [&](const std::string &name, uint32_t start) -> std::string + { + auto it = functionNameCounts.find(name); + if (it == functionNameCounts.end() || it->second <= 1) + { + return name; + } + + std::stringstream selector; + selector << name << "@0x" + << std::hex << std::uppercase << std::setw(8) << std::setfill('0') + << start; + return selector.str(); + }; + + auto collectFunctionSelectors = + [&](const std::unordered_set &nameSet) -> std::vector + { + std::vector orderedFunctions; + orderedFunctions.reserve(m_functions.size()); + for (const auto &func : m_functions) + { + orderedFunctions.push_back(&func); + } + + std::sort(orderedFunctions.begin(), orderedFunctions.end(), + [](const Function *a, const Function *b) + { return a->start < b->start; }); + + std::vector entries; + std::unordered_set seenEntries; + std::unordered_set coveredNames; + + for (const Function *func : orderedFunctions) + { + if (!nameSet.contains(func->name)) + { + continue; + } + + coveredNames.insert(func->name); + const std::string entry = makeSelector(func->name, func->start); + if (seenEntries.insert(entry).second) + { + entries.push_back(entry); + } + } + + std::vector leftovers; + leftovers.reserve(nameSet.size()); + for (const auto &name : nameSet) + { + if (!coveredNames.contains(name) && seenEntries.insert(name).second) + { + leftovers.push_back(name); + } + } + std::sort(leftovers.begin(), leftovers.end()); + entries.insert(entries.end(), leftovers.begin(), leftovers.end()); + + return entries; + }; + + const std::vector stubEntries = collectFunctionSelectors(m_libFunctions); + const std::vector skipEntries = collectFunctionSelectors(m_skipFunctions); file << "# Functions to stub (these will generate empty implementations)\n"; file << "stubs = [\n"; - for (const auto &func : m_libFunctions) + for (const auto &func : stubEntries) { file << " \"" << func << "\",\n"; } @@ -138,12 +222,24 @@ namespace ps2recomp file << "# Functions to skip (these will not be recompiled)\n"; file << "skip = [\n"; - for (const auto &func : m_skipFunctions) + for (const auto &func : skipEntries) { file << " \"" << func << "\",\n"; } file << "]\n\n"; + if (!m_mmioByInstructionAddress.empty()) + { + file << "# Detected MMIO accesses\n"; + file << "[mmio]\n"; + for (const auto &[instAddr, mmioAddr] : m_mmioByInstructionAddress) + { + file << "\"0x" << std::hex << instAddr << "\" = \"0x" << mmioAddr << "\"\n" + << std::dec; + } + file << "\n"; + } + if (!m_jumpTables.empty()) { file << "# Jump tables detected in the program\n"; @@ -252,29 +348,55 @@ namespace ps2recomp m_knownLibNames.insert(stdLibFuncs.begin(), stdLibFuncs.end()); } + int ElfAnalyzer::findEntryFunctionIndexForHeuristics(const std::vector &functions, uint32_t entryAddress) + { + auto it = std::find_if(functions.begin(), functions.end(), + [entryAddress](const Function &f) + { return f.start == entryAddress; }); + if (it != functions.end()) + { + return static_cast(std::distance(functions.begin(), it)); + } + + it = std::find_if(functions.begin(), functions.end(), + [entryAddress](const Function &f) + { return f.start <= entryAddress && entryAddress < f.end; }); + if (it != functions.end()) + { + return static_cast(std::distance(functions.begin(), it)); + } + + return -1; + } + + int ElfAnalyzer::findFallbackEntryFunctionIndexForHeuristics(const std::vector &functions) + { + auto it = std::find_if(functions.begin(), functions.end(), + [](const Function &f) + { return f.start == 0x100000 || f.start == 0x80100000; }); + if (it == functions.end()) + { + return -1; + } + + return static_cast(std::distance(functions.begin(), it)); + } + void ElfAnalyzer::analyzeEntryPoint() { const uint32_t entryAddress = m_elfParser->getEntryPoint(); - auto it = std::find_if(m_functions.begin(), m_functions.end(), - [entryAddress](const Function &f) - { return f.start == entryAddress; }); - - if (it == m_functions.end()) - { - it = std::find_if(m_functions.begin(), m_functions.end(), - [entryAddress](const Function &f) - { return f.start <= entryAddress && entryAddress < f.end; }); - } - - if (it != m_functions.end()) + const int entryIndex = findEntryFunctionIndexForHeuristics(m_functions, entryAddress); + if (entryIndex >= 0) { + const Function &entryFunction = m_functions[static_cast(entryIndex)]; std::cout << "Found entry point from ELF header: 0x" << std::hex << entryAddress - << " in function " << it->name << " (starts at 0x" << it->start << ")" + << " in function " << entryFunction.name << " (starts at 0x" << entryFunction.start << ")" << std::dec << std::endl; - m_skipFunctions.insert(it->name); + m_forceRecompileStarts.insert(entryFunction.start); + m_skipFunctions.erase(entryFunction.name); - std::vector instructions = decodeFunction(*it); + std::vector instructions = decodeFunction(entryFunction); for (const auto &inst : instructions) { @@ -286,10 +408,12 @@ namespace ps2recomp { if (func.start == target) { - std::cout << "Found initialization call to: " << func.name << " at 0x" + std::cout << "Found entry call to: " << func.name << " at 0x" << std::hex << inst.address << std::dec << std::endl; - if (!isDoNotSkipOrStub(func.name) && (func.name.find("init") != std::string::npos || func.name.find("Init") != std::string::npos)) + if (hasReliableSymbolName(func.name) && + !isDoNotSkipOrStub(func.name) && + isSystemFunction(func.name)) { m_skipFunctions.insert(func.name); } @@ -304,15 +428,14 @@ namespace ps2recomp std::cout << "Entry point 0x" << std::hex << entryAddress << " not mapped to an extracted function" << std::dec << std::endl; - for (const auto &func : m_functions) + const int fallbackIndex = findFallbackEntryFunctionIndexForHeuristics(m_functions); + if (fallbackIndex >= 0) { - if (func.start == 0x100000 || func.start == 0x80100000) - { - std::cout << "Found potential entry point by address: " << func.name - << " at 0x" << std::hex << func.start << std::dec << std::endl; - m_skipFunctions.insert(func.name); - break; - } + const Function &fallbackEntry = m_functions[static_cast(fallbackIndex)]; + std::cout << "Found potential entry point by address: " << fallbackEntry.name + << " at 0x" << std::hex << fallbackEntry.start << std::dec << std::endl; + m_forceRecompileStarts.insert(fallbackEntry.start); + m_skipFunctions.erase(fallbackEntry.name); } } } @@ -323,6 +446,11 @@ namespace ps2recomp { if (symbol.isFunction) { + if (!hasReliableSymbolName(symbol.name)) + { + continue; + } + if (isLibraryFunction(symbol.name)) { m_libFunctions.insert(symbol.name); @@ -336,6 +464,11 @@ namespace ps2recomp for (const auto &func : m_functions) { + if (!hasReliableSymbolName(func.name)) + { + continue; + } + if (isLibraryFunction(func.name)) { m_libFunctions.insert(func.name); @@ -450,6 +583,15 @@ namespace ps2recomp { uint32_t targetAddr = baseAddr + static_cast(inst.immediate); + // Detect MMIO accesses + if ((targetAddr >= 0x10000000 && targetAddr < 0x14000000) || // I/O + (targetAddr >= 0x70000000 && targetAddr < 0x70004000)) // Scratchpad + { + m_mmioByInstructionAddress[inst.address] = targetAddr; + std::cout << "Detected MMIO access at " << std::hex << inst.address + << " -> " << targetAddr << std::dec << std::endl; + } + for (const auto §ion : m_sections) { if (targetAddr >= section.address && targetAddr < section.address + section.size) @@ -545,131 +687,141 @@ namespace ps2recomp continue; } - std::vector instructions = decodeFunction(func); + const std::vector instructions = decodeFunction(func); - for (size_t i = 0; i < instructions.size(); i++) + for (size_t index = 0; index + 1 < instructions.size(); ++index) { - const auto &inst = instructions[i]; + tryPatchSelfModifyingStore(func, instructions, index); + } + } + } - if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYSCALL) + bool ElfAnalyzer::tryPatchSelfModifyingStore( + const Function &func, + const std::vector &instructions, + size_t index) + { + const Instruction &storeInst = instructions[index]; + if (storeInst.opcode != OPCODE_SW) + { + return false; + } + + const Instruction &nextInst = instructions[index + 1]; + if (nextInst.opcode != OPCODE_J && nextInst.opcode != OPCODE_JAL) + { + return false; + } + + uint32_t targetAddr = 0; + if (!tryResolveBasePlusOffset(instructions, index, storeInst.rs, static_cast(storeInst.immediate), targetAddr)) + { + return false; + } + + if (!isCodeAddress(targetAddr)) + { + return false; + } + + const uint32_t jumpTarget = decodeAbsoluteJumpTarget(nextInst.address, nextInst.target); + if (!isCodeAddress(jumpTarget)) + { + return false; + } + + std::cout << "Potential self-modifying code at " << formatAddress(storeInst.address) + << " writing to " << formatAddress(targetAddr) + << " then jumping to " << formatAddress(jumpTarget) + << " in function " << func.name << std::endl; + + m_patches[storeInst.address] = 0x00000000; + m_patchReasons[storeInst.address] = "Potential self-modifying code"; + return true; + } + + bool ElfAnalyzer::tryResolveBasePlusOffset( + const std::vector &instructions, + size_t index, + uint32_t baseReg, + int16_t offset, + uint32_t &outAddr) const + { + uint32_t baseAddr = 0; + if (!tryResolveLuiBase(instructions, index, baseReg, baseAddr)) + { + return false; + } + + outAddr = baseAddr + static_cast(offset); + return true; + } + + bool ElfAnalyzer::tryResolveLuiBase( + const std::vector &instructions, + size_t index, + uint32_t reg, + uint32_t &baseAddr) const + { + baseAddr = 0; + + const size_t start = (index > 8) ? (index - 8) : 0; + for (size_t pos = index; pos-- > start;) + { + const Instruction &prev = instructions[pos]; + + if ((prev.opcode == OPCODE_ADDIU || prev.opcode == OPCODE_ORI) && prev.rt == reg) + { + uint32_t hiBase = 0; + if (!tryResolveLuiBase(instructions, pos, prev.rs, hiBase)) { - std::cout << "Found syscall at " << formatAddress(inst.address) << " in function " << func.name << std::endl; - m_patches[inst.address] = 0x00000000; // NOP - m_patchReasons[inst.address] = "Syscall requires special handling"; + return false; } - if (inst.opcode == OPCODE_COP0) + if (prev.opcode == OPCODE_ADDIU) { - std::cout << "Found COP0 instruction at " << formatAddress(inst.address) << " in function " << func.name << std::endl; - m_patches[inst.address] = 0x00000000; // NOP - m_patchReasons[inst.address] = "Privileged COP0 instruction"; + baseAddr = hiBase + static_cast(static_cast(static_cast(prev.immediate))); + } + else + { + baseAddr = hiBase | static_cast(prev.immediate); } - if (inst.opcode == OPCODE_CACHE) - { - std::cout << "Found CACHE instruction at " << formatAddress(inst.address) << " in function " << func.name << std::endl; - m_patches[inst.address] = 0x00000000; // NOP - m_patchReasons[inst.address] = "Cache manipulation not supported"; - } + return true; + } - // Detect potential self-modifying code - if (inst.opcode == OPCODE_SW && i + 1 < instructions.size()) - { - const auto &nextInst = instructions[i + 1]; + if (prev.opcode == OPCODE_LUI && prev.rt == reg) + { + baseAddr = prev.immediate << 16; + return true; + } - if (nextInst.opcode == OPCODE_J || nextInst.opcode == OPCODE_JAL) - { - uint32_t jumpTarget = decodeAbsoluteJumpTarget(nextInst.address, nextInst.target); - - for (const auto §ion : m_sections) - { - if (section.isCode && jumpTarget >= section.address && jumpTarget < section.address + section.size) - { - std::cout << "Potential self-modifying code at " << formatAddress(inst.address) << " in function " << func.name << std::endl; - m_patches[inst.address] = 0x00000000; // NOP the store - m_patchReasons[inst.address] = "Potential self-modifying code"; - } - } - } - } - - // Detect stores to regions mapped to hardware registers - if ((inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SH || inst.opcode == OPCODE_SB) && - inst.rs != 28) // Not GP-relative - { - uint32_t baseAddr = 0; - for (int j = 1; j <= 5 && static_cast(inst.address) - j * 4 >= static_cast(func.start); j++) - { - uint32_t prevAddr = inst.address - j * 4; - uint32_t prevInst = 0; - if (!tryReadWord(m_elfParser.get(), prevAddr, prevInst)) - { - continue; - } - - if (OPCODE(prevInst) == OPCODE_LUI && RT(prevInst) == inst.rs) - { - baseAddr = IMMEDIATE(prevInst) << 16; - break; - } - } - - if (baseAddr != 0) - { - uint32_t targetAddr = baseAddr + static_cast(inst.immediate); - - if ((targetAddr >= 0x10000000 && targetAddr < 0x10010000) || // Timer registers - (targetAddr >= 0x10020000 && targetAddr < 0x10030000) || // DMAC registers - (targetAddr >= 0x12000000 && targetAddr < 0x12010000)) // GS registers - { - std::cout << "Hardware register access at " << formatAddress(inst.address) - << " to address 0x" << std::hex << targetAddr << std::dec - << " in function " << func.name << std::endl; - - // We might need to replace this with a special function call but lets just patch it for now - m_patchReasons[inst.address] = "Hardware register access to " + formatAddress(targetAddr); - } - } - } - - if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYNC) - { - std::cout << "SYNC instruction (memory barrier) at " << formatAddress(inst.address) - << " in function " << func.name << std::endl; - // We might need to add memory barriers in the recompiled code - } - - // Detect instructions that use special PS2 features like quad load/store - if (inst.opcode == OPCODE_LQ || inst.opcode == OPCODE_SQ) - { - std::cout << "Quad word " << (inst.opcode == OPCODE_LQ ? "load" : "store") - << " at " << formatAddress(inst.address) << " in function " << func.name << std::endl; - // These will require special handling with SIMD instructions - } + if (prev.rt == reg || prev.rd == reg) + { + break; } } - for (const auto &func : m_functions) + return false; + } + + bool ElfAnalyzer::isCodeAddress(uint32_t addr) const + { + for (const auto §ion : m_sections) { - if (m_skipFunctions.contains(func.name)) + if (!section.isCode) { continue; } - std::vector instructions = decodeFunction(func); - - for (const auto &inst : instructions) + const uint32_t sectionEnd = section.address + section.size; + if (addr >= section.address && addr < sectionEnd) { - if (inst.isMMI || inst.isVU) - { - std::cout << "Found PS2 multimedia instruction at " << formatAddress(inst.address) - << " in function " << func.name << std::endl; - - // These might need special handling, but we won't patch them with NOPs - m_patchReasons[inst.address] = "PS2 multimedia instruction"; - } + return true; } } + + return false; } void ElfAnalyzer::analyzeControlFlow() @@ -727,6 +879,257 @@ namespace ps2recomp } } + std::vector ElfAnalyzer::detectJumpTablesForHeuristics( + const std::vector &instructions, + const std::vector
§ions, + const std::function &readWord) + { + std::vector jumpTables; + + auto addSignedImm16 = [](uint32_t hiPart, uint16_t imm16) -> uint32_t + { + return hiPart + static_cast(static_cast(static_cast(imm16))); + }; + + auto orUnsignedImm16 = [](uint32_t hiPart, uint16_t imm16) -> uint32_t + { + return hiPart | static_cast(imm16); + }; + + auto looksLikeCodeTarget = [sections](uint32_t addr) -> bool + { + if (addr == 0) + { + return false; + } + + if (sections.empty()) + { + return true; + } + + for (const auto §ion : sections) + { + if (!section.isCode) + { + continue; + } + + const uint32_t sectionEnd = section.address + section.size; + if (addr >= section.address && addr < sectionEnd) + { + return true; + } + } + + return false; + }; + + auto readJumpEntryCandidate = [&](uint32_t entryAddr, bool isLoadDouble, uint32_t &outTarget) -> bool + { + outTarget = 0; + + uint32_t w0 = 0; + if (!readWord(entryAddr, w0)) + { + return false; + } + + if (!isLoadDouble) + { + outTarget = w0; + return true; + } + + uint32_t w1 = 0; + if (!readWord(entryAddr + 4u, w1)) + { + outTarget = w0; // still keep something + return true; + } + + const bool w0Looks = looksLikeCodeTarget(w0); + const bool w1Looks = looksLikeCodeTarget(w1); + + if (w0Looks && !w1Looks) + { + outTarget = w0; + return true; + } + if (w1Looks && !w0Looks) + { + outTarget = w1; + return true; + } + outTarget = w0; + return true; + }; + + auto tryBuildTable = [&](uint32_t baseAddr, uint32_t baseReg, uint32_t numEntries, uint32_t strideBytes, bool isLoadDouble) -> std::optional + { + JumpTable jumpTable; + jumpTable.address = baseAddr; + jumpTable.baseRegister = baseReg; + + uint32_t validCodeTargets = 0; + uint32_t totalRead = 0; + + for (uint32_t e = 0; e < numEntries; e++) + { + const uint32_t entryAddr = baseAddr + (e * strideBytes); + + uint32_t targetAddr = 0; + if (!readJumpEntryCandidate(entryAddr, isLoadDouble, targetAddr)) + { + continue; + } + + totalRead++; + + if (looksLikeCodeTarget(targetAddr)) + { + validCodeTargets++; + } + + JumpTableEntry entry; + entry.index = e; + entry.target = targetAddr; + jumpTable.entries.push_back(entry); + } + + if (jumpTable.entries.empty()) + { + return std::nullopt; + } + + bool ok = false; + if (sections.empty()) + { + ok = (totalRead >= 2); + } + else + { + ok = (validCodeTargets >= 2) && + (totalRead >= 2) && + (validCodeTargets * 2 >= totalRead); + } + + if (!ok) + { + return std::nullopt; + } + + return jumpTable; + }; + + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + + if (inst.opcode != OPCODE_SLTIU || i + 2 >= instructions.size()) + { + continue; + } + + const auto &nextInst = instructions[i + 1]; + if (nextInst.opcode != OPCODE_BNE && nextInst.opcode != OPCODE_BEQ) + { + continue; + } + + // scan for LW/LD + JR pair + for (size_t j = i + 2; j < std::min(i + 10, instructions.size()); j++) + { + const auto &loadInst = instructions[j]; + const bool isLoadWord = (loadInst.opcode == OPCODE_LW); + const bool isLoadDouble = (loadInst.opcode == OPCODE_LD); + + if ((!isLoadWord && !isLoadDouble) || j + 1 >= instructions.size()) + { + continue; + } + + const auto &jumpInst = instructions[j + 1]; + if (jumpInst.opcode != OPCODE_SPECIAL || + jumpInst.function != SPECIAL_JR || + jumpInst.rs != loadInst.rt) + { + continue; + } + + const uint32_t numEntries = inst.immediate; + if (numEntries == 0 || numEntries >= 1000) + { + break; + } + + uint32_t baseAddr = 0; + + // A) LUI tmp ; ADDIU/ORI base, tmp, lo + // B) LUI base ; ... ; LW/LD rt, lo(base) + for (int k = static_cast(j) - 1; k >= static_cast(i); k--) + { + const auto &addrInst = instructions[static_cast(k)]; + if (addrInst.opcode != OPCODE_LUI) + { + continue; + } + + const uint32_t hiPart = (addrInst.immediate << 16); + + if (static_cast(k + 1) < instructions.size()) + { + const auto &offsetInst = instructions[static_cast(k + 1)]; + const bool isAddiuOrOri = (offsetInst.opcode == OPCODE_ADDIU || offsetInst.opcode == OPCODE_ORI); + + if (isAddiuOrOri && + offsetInst.rs == addrInst.rt && + offsetInst.rt == loadInst.rs) + { + if (offsetInst.opcode == OPCODE_ADDIU) + { + baseAddr = addSignedImm16(hiPart, offsetInst.immediate); + } + else + { + baseAddr = orUnsignedImm16(hiPart, offsetInst.immediate); + } + break; + } + } + + if (addrInst.rt == loadInst.rs) + { + baseAddr = addSignedImm16(hiPart, loadInst.immediate); + break; + } + } + + if (baseAddr == 0) + { + break; + } + + const uint32_t preferredStride = isLoadDouble ? 8u : 4u; + + std::optional table = tryBuildTable(baseAddr, loadInst.rs, numEntries, preferredStride, isLoadDouble); + if (!table && isLoadDouble) + { + table = tryBuildTable(baseAddr, loadInst.rs, numEntries, 4u, isLoadDouble); + } + + if (table) + { + jumpTables.push_back(std::move(*table)); + } + + break; + } + } + + return jumpTables; + } + void ElfAnalyzer::detectJumpTables() { std::cout << "Detecting jump tables..." << std::endl; @@ -740,86 +1143,25 @@ namespace ps2recomp } std::vector instructions = decodeFunction(func); - - for (size_t i = 0; i < instructions.size(); i++) - { - const auto &inst = instructions[i]; - - if (inst.opcode == OPCODE_SLTIU && i + 2 < instructions.size()) + std::vector detectedTables = detectJumpTablesForHeuristics( + instructions, + m_sections, + [this](uint32_t address, uint32_t &outWord) -> bool { - const auto &nextInst = instructions[i + 1]; - if (nextInst.opcode == OPCODE_BNE || nextInst.opcode == OPCODE_BEQ) - { - for (size_t j = i + 2; j < std::min(i + 10, instructions.size()); j++) - { - const auto &loadInst = instructions[j]; + return tryReadWord(m_elfParser.get(), address, outWord); + }); - if (loadInst.opcode == OPCODE_LW && j + 1 < instructions.size()) - { - const auto &jumpInst = instructions[j + 1]; - - if (jumpInst.opcode == OPCODE_SPECIAL && jumpInst.function == SPECIAL_JR && - jumpInst.rs == loadInst.rt) - { - std::cout << "Detected jump table in function " << func.name - << " at " << formatAddress(loadInst.address) << std::endl; - - uint32_t baseAddr = 0; - uint32_t numEntries = inst.immediate; // From the bounds check - - for (int k = j - 1; k >= static_cast(i); k--) - { - const auto &addrInst = instructions[k]; - - if (addrInst.opcode == OPCODE_LUI && k + 1 < instructions.size()) - { - const auto &offsetInst = instructions[k + 1]; - - if ((offsetInst.opcode == OPCODE_ADDIU || offsetInst.opcode == OPCODE_ORI) && - offsetInst.rs == addrInst.rt && offsetInst.rt == loadInst.rs) - { - - baseAddr = (addrInst.immediate << 16) | (offsetInst.immediate & 0xFFFF); - break; - } - } - } - - if (baseAddr != 0 && numEntries > 0 && numEntries < 1000) - { - JumpTable jumpTable; - jumpTable.address = baseAddr; - jumpTable.baseRegister = loadInst.rs; - - for (uint32_t e = 0; e < numEntries; e++) - { - uint32_t entryAddr = baseAddr + (e * 4); - - uint32_t targetAddr = 0; - if (tryReadWord(m_elfParser.get(), entryAddr, targetAddr)) - { - JumpTableEntry entry; - entry.index = e; - entry.target = targetAddr; - jumpTable.entries.push_back(entry); - - std::cout << " - Jump table entry " << e << ": 0x" - << std::hex << targetAddr << std::dec << std::endl; - } - } - - if (!jumpTable.entries.empty()) - { - m_jumpTables.push_back(jumpTable); - } - } - - break; - } - } - } - } + for (const auto &jumpTable : detectedTables) + { + std::cout << "Detected jump table in function " << func.name + << " at " << formatAddress(jumpTable.address) << std::endl; + for (const auto &[index, target] : jumpTable.entries) + { + std::cout << " - Jump table entry " << index << ": 0x" + << std::hex << target << std::dec << std::endl; } + + m_jumpTables.push_back(jumpTable); } } } @@ -880,6 +1222,112 @@ namespace ps2recomp } } + std::unordered_set ElfAnalyzer::findRecursiveFunctionsForHeuristics( + const std::unordered_map> &callGraph) + { + std::unordered_set nodes; + for (const auto &[caller, callees] : callGraph) + { + nodes.insert(caller); + for (const auto &callee : callees) + { + nodes.insert(callee); + } + } + + std::unordered_map index; + std::unordered_map lowlink; + std::unordered_set onStack; + std::vector stack; + + index.reserve(nodes.size()); + lowlink.reserve(nodes.size()); + onStack.reserve(nodes.size()); + stack.reserve(nodes.size()); + + int currentIndex = 0; + std::vector> sccs; + sccs.reserve(nodes.size()); + + std::function strongconnect; + strongconnect = [&](const std::string &v) + { + index[v] = currentIndex; + lowlink[v] = currentIndex; + currentIndex++; + + stack.push_back(v); + onStack.insert(v); + + auto it = callGraph.find(v); + if (it != callGraph.end()) + { + for (const auto &w : it->second) + { + if (!index.contains(w)) + { + strongconnect(w); + lowlink[v] = std::min(lowlink[v], lowlink[w]); + } + else if (onStack.contains(w)) + { + lowlink[v] = std::min(lowlink[v], index[w]); + } + } + } + + if (lowlink[v] == index[v]) + { + std::vector scc; + while (!stack.empty()) + { + std::string w = stack.back(); + stack.pop_back(); + onStack.erase(w); + scc.push_back(w); + if (w == v) + { + break; + } + } + + sccs.push_back(std::move(scc)); + } + }; + + for (const auto &name : nodes) + { + if (!index.contains(name)) + { + strongconnect(name); + } + } + + std::unordered_set recursive; + for (const auto &scc : sccs) + { + if (scc.size() > 1) + { + recursive.insert(scc.begin(), scc.end()); + continue; + } + + const std::string &name = scc[0]; + auto it = callGraph.find(name); + if (it == callGraph.end()) + { + continue; + } + + if (std::find(it->second.begin(), it->second.end(), name) != it->second.end()) + { + recursive.insert(name); + } + } + + return recursive; + } + void ElfAnalyzer::identifyRecursiveFunctions() { std::cout << "Identifying recursive functions..." << std::endl; @@ -930,103 +1378,18 @@ namespace ps2recomp } } - std::unordered_map index; - std::unordered_map lowlink; - std::unordered_set onStack; - std::vector stack; - - index.reserve(eligible.size()); - lowlink.reserve(eligible.size()); - onStack.reserve(eligible.size()); - stack.reserve(eligible.size()); - - int currentIndex = 0; - - std::vector> sccs; - sccs.reserve(256); - - std::function strongconnect; - strongconnect = [&](const std::string &v) + std::unordered_set recursive = findRecursiveFunctionsForHeuristics(callGraph); + for (const auto &name : recursive) { - index[v] = currentIndex; - lowlink[v] = currentIndex; - currentIndex++; - - stack.push_back(v); - onStack.insert(v); - - auto it = callGraph.find(v); - if (it != callGraph.end()) - { - for (const auto &w : it->second) - { - if (!index.contains(w)) - { - strongconnect(w); - lowlink[v] = std::min(lowlink[v], lowlink[w]); - } - else if (onStack.contains(w)) - { - lowlink[v] = std::min(lowlink[v], index[w]); - } - } - } - - if (lowlink[v] == index[v]) - { - std::vector scc; - while (!stack.empty()) - { - std::string w = stack.back(); - stack.pop_back(); - onStack.erase(w); - - scc.push_back(w); - if (w == v) - { - break; - } - } - - sccs.push_back(std::move(scc)); - } - }; - - for (const auto &name : eligible) - { - if (!index.contains(name)) - { - strongconnect(name); - } - } - - // SCC size > 1 -> mutual recursion - // SCC size == 1 -> direct recursion if it calls itself - for (const auto &scc : sccs) - { - if (scc.size() > 1) - { - for (const auto &name : scc) - { - std::cout << "Function " << name << " is part of a mutually recursive cycle" << std::endl; - } - continue; - } - - const std::string &name = scc[0]; auto it = callGraph.find(name); - if (it == callGraph.end()) + if (it != callGraph.end() && + std::find(it->second.begin(), it->second.end(), name) != it->second.end()) { - continue; + std::cout << "Function " << name << " is directly recursive" << std::endl; } - - for (const auto &callee : it->second) + else { - if (callee == name) - { - std::cout << "Function " << name << " is directly recursive" << std::endl; - break; - } + std::cout << "Function " << name << " is part of a mutually recursive cycle" << std::endl; } } } @@ -1248,10 +1611,10 @@ namespace ps2recomp << " patches. Consider skipping or stubing instead." << std::endl; // If too many patches in one function, maybe better to skip it - if (patchAddrs.size() > 5 && - static_cast(patchAddrs.size()) / ((func.end - func.start) / 4) > 0.2 && - !isLibraryFunction(func.name) && - !isDoNotSkipOrStub(func.name)) + if (shouldSkipForPatchDensityForHeuristics(func.name, + func.end - func.start, + patchAddrs.size(), + isLibraryFunction(func.name))) { std::cout << " - Adding " << func.name << " to skip list due to high patch density" << std::endl; m_skipFunctions.insert(func.name); @@ -1676,8 +2039,64 @@ namespace ps2recomp return kDoNotSkipOrStub.contains(name); } - bool ElfAnalyzer::isSystemFunction(const std::string &name) const + static bool hasReliableSymbolName(const std::string &name) { + if (name.empty()) + { + return false; + } + + auto startsWith = [&](const char *prefix) -> bool + { + return name.rfind(prefix, 0) == 0; + }; + + if (startsWith("sub_") || startsWith("FUN_") || startsWith("func_") || + startsWith("entry_") || startsWith("function_") || startsWith("LAB_")) + { + return false; + } + + bool hasAlpha = false; + bool allHexOrPrefix = true; + for (char c : name) + { + if (std::isalpha(static_cast(c))) + { + hasAlpha = true; + } + + if (!(std::isxdigit(static_cast(c)) || c == 'x' || c == 'X' || c == '_')) + { + allHexOrPrefix = false; + } + } + + if (!hasAlpha) + { + return false; + } + + if ((startsWith("0x") || startsWith("0X")) && allHexOrPrefix) + { + return false; + } + + return true; + } + + bool ElfAnalyzer::isReliableSymbolNameForHeuristics(const std::string &name) + { + return hasReliableSymbolName(name); + } + + bool ElfAnalyzer::isSystemSymbolNameForHeuristics(const std::string &name) + { + if (!hasReliableSymbolName(name)) + { + return false; + } + static const std::unordered_set systemFuncs = { "entry", "_start", "_init", "_fini", "abort", "exit", "_exit", @@ -1694,11 +2113,35 @@ namespace ps2recomp name.find(".") == 0; // .text.* or .plt.* symbols } + bool ElfAnalyzer::shouldAutoSkipNameForHeuristics(const std::string &name) + { + if (isDoNotSkipOrStub(name)) + { + return false; + } + + // For named game logic, prefer recompiling and only skip explicit/system cases. + if (!hasReliableSymbolName(name)) + { + return true; + } + + return isSystemSymbolNameForHeuristics(name); + } + + bool ElfAnalyzer::isSystemFunction(const std::string &name) const + { + return isSystemSymbolNameForHeuristics(name); + } + bool ElfAnalyzer::isLibraryFunction(const std::string &name) const { if (name.empty()) return false; + if (!hasReliableSymbolName(name)) + return false; + if (m_knownLibNames.find(name) != m_knownLibNames.end()) return true; @@ -1715,6 +2158,124 @@ namespace ps2recomp return false; } + bool ElfAnalyzer::isLibrarySymbolNameForHeuristics(const std::string &name) const + { + return isLibraryFunction(name); + } + + bool ElfAnalyzer::hasHardwareIOSignalForHeuristics(const std::vector &instructions) + { + for (const auto &inst : instructions) + { + if (inst.opcode == OPCODE_LUI) + { + const uint32_t upperAddr = inst.immediate << 16; + if ((upperAddr >= 0x10000000 && upperAddr < 0x14000000) || // I/O area + (upperAddr >= 0x1F800000 && upperAddr < 0x1F900000)) // Scratchpad RAM + { + return true; + } + } + } + + return false; + } + + bool ElfAnalyzer::hasLargeComplexMMISignalForHeuristics(const std::vector &instructions, + size_t largeInstructionThreshold) + { + if (instructions.size() <= largeInstructionThreshold) + { + return false; + } + + for (const auto &inst : instructions) + { + if (inst.isMMI && + inst.opcode == OPCODE_MMI && + (inst.function == MMI_MMI0 || inst.function == MMI_MMI1 || + inst.function == MMI_MMI2 || inst.function == MMI_MMI3)) + { + return true; + } + } + + return false; + } + + bool ElfAnalyzer::hasSelfModifyingSignalForHeuristics(const std::vector &instructions, + const std::vector
§ions) + { + for (size_t i = 0; i < instructions.size(); i++) + { + const auto &inst = instructions[i]; + if (!(inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SH || + inst.opcode == OPCODE_SB || inst.opcode == OPCODE_SQ)) + { + continue; + } + + uint32_t baseAddr = 0; + for (int j = static_cast(i) - 1; j >= 0 && j >= static_cast(i) - 5; j--) + { + const auto &prevInst = instructions[static_cast(j)]; + if (prevInst.opcode == OPCODE_LUI && prevInst.rt == inst.rs) + { + baseAddr = prevInst.immediate << 16; + break; + } + } + + if (baseAddr == 0) + { + continue; + } + + const uint32_t targetAddr = baseAddr + static_cast(inst.immediate); + for (const auto §ion : sections) + { + if (section.isCode && + targetAddr >= section.address && + targetAddr < section.address + section.size) + { + return true; + } + } + } + + return false; + } + + bool ElfAnalyzer::shouldSkipForPatchDensityForHeuristics(const std::string &functionName, + uint32_t functionSizeBytes, + size_t patchCount, + bool isLibraryFunction) + { + if (patchCount <= 5 || functionSizeBytes < 4) + { + return false; + } + + const double instructionCount = static_cast(functionSizeBytes) / 4.0; + if (instructionCount <= 0.0) + { + return false; + } + + const double density = static_cast(patchCount) / instructionCount; + if (density <= 0.2) + { + return false; + } + + if (isLibraryFunction || isDoNotSkipOrStub(functionName)) + { + return false; + } + + return shouldAutoSkipNameForHeuristics(functionName); + } + std::vector ElfAnalyzer::decodeFunction(const Function &function) const { std::vector instructions; @@ -1779,13 +2340,17 @@ namespace ps2recomp return false; } - bool ElfAnalyzer::identifyFunctionType(const Function &function) + bool ElfAnalyzer::shouldAutoSkipByHeuristic(const Function &function) const { - if (m_libFunctions.contains(function.name) || - m_skipFunctions.contains(function.name)) + if (m_forceRecompileStarts.contains(function.start)) { return false; } + return shouldAutoSkipNameForHeuristics(function.name); + } + + bool ElfAnalyzer::identifyFunctionType(const Function &function) + { if (isDoNotSkipOrStub(function.name)) { return false; @@ -1797,47 +2362,33 @@ namespace ps2recomp std::vector instructions = decodeFunction(function); - bool hasHardwareIO = false; - bool hasComplexMMI = false; - bool isVeryLarge = instructions.size() > 500; // Arbitrary large function threshold - - for (const auto &inst : instructions) - { - // Check for LUI+SW combinations to hardware registers - if (inst.opcode == OPCODE_LUI) - { - uint32_t upperAddr = inst.immediate << 16; - - // Check if upper address is in hardware region - if ((upperAddr >= 0x10000000 && upperAddr < 0x14000000) || // I/O area - (upperAddr >= 0x1F800000 && upperAddr < 0x1F900000)) // Scratchpad RAM - { - hasHardwareIO = true; - } - } - - // Check for complex MMI operations - if (inst.isMMI && - (inst.opcode == OPCODE_MMI && - (inst.function == MMI_MMI0 || inst.function == MMI_MMI1 || - inst.function == MMI_MMI2 || inst.function == MMI_MMI3))) - { - hasComplexMMI = true; - } - } + const bool hasHardwareIO = hasHardwareIOSignalForHeuristics(instructions); + const bool hasLargeComplexMMI = hasLargeComplexMMISignalForHeuristics(instructions); if (hasHardwareIO) { - m_skipFunctions.insert(function.name); - std::cout << "Skipping function " << function.name << " due to hardware I/O" << std::endl; - return true; + if (shouldAutoSkipByHeuristic(function)) + { + m_skipFunctions.insert(function.name); + std::cout << "Skipping function " << function.name << " due to hardware I/O" << std::endl; + return true; + } + + std::cout << "Keeping function " << function.name + << " despite hardware I/O (reliable game symbol)" << std::endl; } - if (hasComplexMMI && isVeryLarge) + if (hasLargeComplexMMI) { - m_skipFunctions.insert(function.name); - std::cout << "Skipping large function " << function.name << " with complex MMI" << std::endl; - return true; + if (shouldAutoSkipByHeuristic(function)) + { + m_skipFunctions.insert(function.name); + std::cout << "Skipping large function " << function.name << " with complex MMI" << std::endl; + return true; + } + + std::cout << "Keeping large function " << function.name + << " with complex MMI (reliable game symbol)" << std::endl; } return false; @@ -1850,7 +2401,9 @@ namespace ps2recomp if (isSelfModifyingCode(function)) { std::cout << "Function " << function.name << " contains self-modifying code" << std::endl; - if (!isLibraryFunction(function.name) && !isDoNotSkipOrStub(function.name)) + if (!isLibraryFunction(function.name) && + !isDoNotSkipOrStub(function.name) && + shouldAutoSkipByHeuristic(function)) { m_skipFunctions.insert(function.name); } @@ -1865,47 +2418,7 @@ namespace ps2recomp bool ElfAnalyzer::isSelfModifyingCode(const Function &function) const { std::vector instructions = decodeFunction(function); - - for (size_t i = 0; i < instructions.size(); i++) - { - const auto &inst = instructions[i]; - - if ((inst.opcode == OPCODE_SW || inst.opcode == OPCODE_SH || - inst.opcode == OPCODE_SB || inst.opcode == OPCODE_SQ)) - { - - uint32_t baseAddr = 0; - - // Look for preceding LUI instruction - for (int j = i - 1; j >= 0 && j >= static_cast(i) - 5; j--) - { - const auto &prevInst = instructions[j]; - - if (prevInst.opcode == OPCODE_LUI && prevInst.rt == inst.rs) - { - baseAddr = prevInst.immediate << 16; - break; - } - } - - if (baseAddr != 0) - { - uint32_t targetAddr = baseAddr + static_cast(inst.immediate); - - // Check if target address is within a code section - for (const auto §ion : m_sections) - { - if (section.isCode && targetAddr >= section.address && - targetAddr < section.address + section.size) - { - return true; - } - } - } - } - } - - return false; + return hasSelfModifyingSignalForHeuristics(instructions, m_sections); } bool ElfAnalyzer::isLoopHeavyFunction(const Function &function) const diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 540f551..ddab83b 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -120,7 +120,6 @@ namespace ps2recomp // Jump Table Generation std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress, const std::vector &entries); - std::string generateBootstrapFunction() const; const Symbol *findSymbolByAddress(uint32_t address) const; std::string getFunctionName(uint32_t address) const; diff --git a/ps2xRecomp/include/ps2recomp/elf_parser.h b/ps2xRecomp/include/ps2recomp/elf_parser.h index e1abdfa..bb756d7 100644 --- a/ps2xRecomp/include/ps2recomp/elf_parser.h +++ b/ps2xRecomp/include/ps2recomp/elf_parser.h @@ -35,6 +35,7 @@ namespace ps2recomp uint32_t getSectionAddress(const std::string §ionName) const; uint32_t getSectionSize(const std::string §ionName) const; uint32_t getEntryPoint() const; + void debugAddress(uint32_t address) const; private: std::string m_filePath; diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h index 74e28d9..13673e3 100644 --- a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -11,19 +11,28 @@ namespace ps2recomp { - class R5900Decoder; - class ElfParser; + class R5900Decoder; + class ElfParser; - class PS2Recompiler + enum class StubTarget + { + Unknown, + Syscall, + Stub + }; + + class PS2Recompiler { public: - explicit PS2Recompiler(const std::string &configPath); + explicit PS2Recompiler(const std::string &configPath); ~PS2Recompiler(); bool initialize(); bool recompile(); void generateOutput(); + static StubTarget resolveStubTarget(const std::string& name); + private: ConfigManager m_configManager; std::unique_ptr m_elfParser; @@ -38,20 +47,22 @@ namespace ps2recomp std::unordered_map> m_decodedFunctions; std::unordered_map m_skipFunctions; + std::unordered_set m_skipFunctionStarts; std::unordered_set m_stubFunctions; + std::unordered_set m_stubFunctionStarts; std::map m_generatedStubs; std::unordered_map m_functionRenames; CodeGenerator::BootstrapInfo m_bootstrapInfo; bool decodeFunction(Function &function); void discoverAdditionalEntryPoints(); - bool shouldSkipFunction(const std::string &name) const; - bool isStubFunction(const std::string &name) const; + bool shouldSkipFunction(const Function &function) const; + bool isStubFunction(const Function &function) const; bool generateFunctionHeader(); bool generateStubHeader(); bool writeToFile(const std::string &path, const std::string &content); std::filesystem::path getOutputPath(const Function &function) const; - std::string sanitizeFunctionName(const std::string &name) const; + std::string sanitizeFunctionName(const std::string &name) const; }; } diff --git a/ps2xRecomp/include/ps2recomp/types.h b/ps2xRecomp/include/ps2recomp/types.h index 0ecd239..d3c9ca2 100644 --- a/ps2xRecomp/include/ps2recomp/types.h +++ b/ps2xRecomp/include/ps2recomp/types.h @@ -43,6 +43,9 @@ namespace ps2recomp uint8_t pmfhlVariation; // For PMFHL instructions uint8_t vuFunction; // For VU instructions + bool isMmio = false; + uint32_t mmioAddress = 0; + struct { bool isVector; // Uses vector operations @@ -59,8 +62,8 @@ namespace ps2recomp bool modifiesGPR; // Modifies general purpose register bool modifiesFPR; // Modifies floating point register bool modifiesVFR; // Modifies vector float register - bool modifiesVIR; // Modifies vector integer register - bool modifiesVIC; // Modifies vector integer control register + bool modifiesVIR; // Modifies vector integer register + bool modifiesVIC; // Modifies vector integer control register bool modifiesMemory; // Modifies memory bool modifiesControl; // Modifies control register } modificationInfo; @@ -69,7 +72,7 @@ namespace ps2recomp immediate(0), simmediate(0), target(0), raw(0), isMMI(false), isVU(false), isBranch(false), isJump(false), isCall(false), isReturn(false), hasDelaySlot(false), isMultimedia(false), isStore(false), isLoad(false), - mmiType(0), mmiFunction(0), pmfhlVariation(0), vuFunction(0) + mmiType(0), mmiFunction(0), pmfhlVariation(0), vuFunction(0), isMmio(false), mmioAddress(0) { vectorInfo = {}; modificationInfo = {}; @@ -85,8 +88,9 @@ namespace ps2recomp std::vector instructions; std::vector callers; std::vector callees; - bool isRecompiled; - bool isStub; + bool isRecompiled = false; + bool isStub = false; + bool isSkipped = false; }; // Symbol information @@ -166,12 +170,16 @@ namespace ps2recomp std::string inputPath; std::string outputPath; std::string ghidraMapPath; - bool singleFileOutput; + bool singleFileOutput = false; + bool patchSyscalls = false; + bool patchCop0 = true; + bool patchCache = true; std::vector skipFunctions; std::unordered_map patches; std::vector stubImplementations; + std::unordered_map mmioByInstructionAddress; }; } // namespace ps2recomp -#endif // PS2RECOMP_TYPES_H \ No newline at end of file +#endif // PS2RECOMP_TYPES_H diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 1e740d0..5963684 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -1,5 +1,6 @@ #include "ps2recomp/code_generator.h" #include "ps2recomp/instructions.h" +#include "ps2recomp/ps2_recompiler.h" #include "ps2recomp/types.h" #include #include @@ -23,7 +24,7 @@ namespace ps2recomp "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", - "wchar_t", "while", "xor", "xor_eq", "std"}; + "wchar_t", "while", "xor", "xor_eq"}; } namespace ps2recomp @@ -127,67 +128,187 @@ namespace ps2recomp if (isReservedCxxKeyword(sanitized)) return "ps2_" + sanitized; + if (sanitized[0] == '_') + return "ps2" + sanitized; + if (!isReservedCxxIdentifier(sanitized)) return sanitized; return "ps2_" + sanitized; } - std::string CodeGenerator::handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot, - const Function &function, const std::unordered_set &internalTargets) + std::string CodeGenerator::handleBranchDelaySlots( + const Instruction &branchInst, + const Instruction &delaySlot, + const Function &function, + const std::unordered_set &internalTargets) { std::stringstream ss; - bool hasValidDelaySlot = (delaySlot.raw != 0); - std::string delaySlotCode = hasValidDelaySlot ? translateInstruction(delaySlot) : ""; - uint8_t rs_reg = branchInst.rs; - uint8_t rt_reg = branchInst.rt; - uint8_t rd_reg = branchInst.rd; + const bool hasValidDelaySlot = !(delaySlot.opcode == OPCODE_SPECIAL && + delaySlot.function == SPECIAL_SLL && + delaySlot.rd == 0 && + delaySlot.rt == 0 && + delaySlot.sa == 0); + + const std::string delaySlotCode = hasValidDelaySlot ? translateInstruction(delaySlot) : ""; + + const uint8_t rs_reg = branchInst.rs; + const uint8_t rt_reg = branchInst.rt; + const uint8_t rd_reg = branchInst.rd; + + const uint32_t branchPc = branchInst.address; + const uint32_t delayPc = branchInst.address + 4u; + const uint32_t fallthroughPc = branchInst.address + 8u; + + std::vector sortedInternalTargets; + if (branchInst.opcode == OPCODE_SPECIAL && + branchInst.function == SPECIAL_JR && + rs_reg == 31 && + !internalTargets.empty()) + { + sortedInternalTargets.reserve(internalTargets.size()); + for (uint32_t t : internalTargets) + { + sortedInternalTargets.push_back(t); + } + std::sort(sortedInternalTargets.begin(), sortedInternalTargets.end()); + } + + if (internalTargets.contains(delayPc)) + { + ss << fmt::format(" if (ctx->pc == 0x{:X}u) {{\n", delayPc); + + if (hasValidDelaySlot) + { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); + ss << " " << delaySlotCode << "\n"; + } + + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc); + + if (internalTargets.contains(fallthroughPc)) + { + ss << fmt::format(" goto label_{:x};\n", fallthroughPc); // label uses lowercase usually, but let's keep consistency. Labels are case insensitive in C but check expectation. + } + else + { + ss << fmt::format(" goto label_fallthrough_0x{:x};\n", branchPc); + } + + ss << " }\n"; + } + + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", branchPc); + + // ------------------------- + // J / JAL (static jump) + // ------------------------- if (branchInst.opcode == OPCODE_J || branchInst.opcode == OPCODE_JAL) { if (branchInst.opcode == OPCODE_JAL) { - ss << " SET_GPR_U32(ctx, 31, 0x" << std::hex << (branchInst.address + 8) << ");\n" - << std::dec; + ss << fmt::format(" SET_GPR_U32(ctx, 31, 0x{:X}u);\n", fallthroughPc); } + if (hasValidDelaySlot) { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); ss << " " << delaySlotCode << "\n"; } - uint32_t target = buildAbsoluteJumpTarget(branchInst.address, branchInst.target); - std::string funcName = getFunctionName(target); - if (!funcName.empty()) + + const uint32_t target = buildAbsoluteJumpTarget(branchInst.address, branchInst.target); + + if (internalTargets.contains(target)) { - if (branchInst.opcode == OPCODE_J) - { - ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; - } - else - { - ss << " " << funcName << "(rdram, ctx, runtime);\n"; - } + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); + ss << fmt::format(" goto label_{:x};\n", target); } else { - ss << " ctx->pc = 0x" << std::hex << target << "; return;\n" - << std::dec; + std::string funcName = getFunctionName(target); + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); + + if (!funcName.empty()) + { + if (branchInst.opcode == OPCODE_J) + { + ss << " " << funcName << "(rdram, ctx, runtime); return;\n"; + } + else + { + ss << " " << funcName << "(rdram, ctx, runtime);\n"; + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + } + } + else + { + ss << " {\n"; + ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target); + ss << " targetFn(rdram, ctx, runtime);\n"; + if (branchInst.opcode == OPCODE_J) + { + ss << " return;\n"; + } + else + { + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + } + ss << " }\n"; + } } } + // ------------------------- + // JR / JALR (register jump) + // ------------------------- else if (branchInst.opcode == OPCODE_SPECIAL && (branchInst.function == SPECIAL_JR || branchInst.function == SPECIAL_JALR)) { - uint8_t link_reg = (branchInst.function == SPECIAL_JALR) ? ((rd_reg == 0) ? 31 : rd_reg) : 0; - if (link_reg != 0) + ss << " {\n"; + ss << " uint32_t jumpTarget = GPR_U32(ctx, " << static_cast(rs_reg) << ");\n"; + + if (branchInst.function == SPECIAL_JALR && rd_reg != 0) { - ss << " SET_GPR_U32(ctx, " << static_cast(link_reg) << ", 0x" << std::hex << (branchInst.address + 8) << ");\n" - << std::dec; + ss << fmt::format(" SET_GPR_U32(ctx, {}, 0x{:X}u);\n", rd_reg, fallthroughPc); } + if (hasValidDelaySlot) { - ss << " " << delaySlotCode << "\n"; + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); + ss << " " << delaySlotCode << "\n"; } - ss << " ctx->pc = GPR_U32(ctx, " << static_cast(rs_reg) << "); return;\n"; + + ss << " ctx->pc = jumpTarget;\n"; + + if (branchInst.function == SPECIAL_JR && rs_reg == 31 && !sortedInternalTargets.empty()) + { + ss << " switch (jumpTarget) {\n"; + for (uint32_t t : sortedInternalTargets) + { + ss << fmt::format(" case 0x{:X}u: goto label_{:x};\n", t, t); + } + ss << " default: break;\n"; + ss << " }\n"; + } + + if (branchInst.function == SPECIAL_JR) + { + ss << " return;\n"; + } + else + { + ss << " {\n"; + ss << " auto targetFn = runtime->lookupFunction(jumpTarget);\n"; + ss << " targetFn(rdram, ctx, runtime);\n"; + ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc); + ss << " }\n"; + } + + ss << " }\n"; } + // ------------------------- + // Conditional Branches + // ------------------------- else if (branchInst.isBranch) { std::string conditionStr = "false"; @@ -236,112 +357,137 @@ namespace ps2recomp break; case REGIMM_BLTZAL: conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X});", branchInst.address + 8); + linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; case REGIMM_BGEZAL: conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X});", branchInst.address + 8); + linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; case REGIMM_BLTZALL: conditionStr = fmt::format("GPR_S32(ctx, {}) < 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X});", branchInst.address + 8); + linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); break; case REGIMM_BGEZALL: conditionStr = fmt::format("GPR_S32(ctx, {}) >= 0", rs_reg); - linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X});", branchInst.address + 8); + linkCode = fmt::format("SET_GPR_U32(ctx, 31, 0x{:X}u);", fallthroughPc); + break; + default: break; } break; case OPCODE_COP1: if (branchInst.rs == COP1_BC) { - uint8_t bc_cond = branchInst.rt; - if (bc_cond == COP1_BC_BCF || bc_cond == COP1_BC_BCFL) - { - conditionStr = "!(ctx->fcr31 & 0x800000)"; - } - else - { - conditionStr = "(ctx->fcr31 & 0x800000)"; - } + const uint8_t bc_cond = branchInst.rt; + conditionStr = (bc_cond == COP1_BC_BCF || bc_cond == COP1_BC_BCFL) + ? "!(ctx->fcr31 & 0x800000)" + : "(ctx->fcr31 & 0x800000)"; } break; case OPCODE_COP2: if (branchInst.rs == COP2_BC) { - uint8_t bc_cond = branchInst.rt; - if (bc_cond == COP2_BC_BCF || bc_cond == COP2_BC_BCFL) - { - conditionStr = "!(ctx->vu0_status & 0x1)"; - } - else - { - conditionStr = "(ctx->vu0_status & 0x1)"; - } + const uint8_t bc_cond = branchInst.rt; + conditionStr = (bc_cond == COP2_BC_BCF || bc_cond == COP2_BC_BCFL) + ? "!(ctx->vu0_status & 0x1)" + : "(ctx->vu0_status & 0x1)"; } break; + default: + break; } - int32_t offset = branchInst.simmediate << 2; - uint32_t target = branchInst.address + 4 + offset; + const int32_t offsetBytes = (static_cast(static_cast(branchInst.simmediate)) << 2); + const uint32_t target = static_cast( + static_cast(branchInst.address + 4u) + static_cast(offsetBytes)); - std::string targetAction; - std::string funcName = getFunctionName(target); - bool isInternalTarget = internalTargets.contains(target); + const bool isLikely = + (branchInst.opcode == OPCODE_BEQL || branchInst.opcode == OPCODE_BNEL || + branchInst.opcode == OPCODE_BLEZL || branchInst.opcode == OPCODE_BGTZL || + (branchInst.opcode == OPCODE_REGIMM && + (branchInst.rt == REGIMM_BLTZL || branchInst.rt == REGIMM_BGEZL || + branchInst.rt == REGIMM_BLTZALL || branchInst.rt == REGIMM_BGEZALL)) || + (branchInst.opcode == OPCODE_COP1 && branchInst.rs == COP1_BC && + (branchInst.rt == COP1_BC_BCFL || branchInst.rt == COP1_BC_BCTL)) || + (branchInst.opcode == OPCODE_COP2 && branchInst.rs == COP2_BC && + (branchInst.rt == COP2_BC_BCFL || branchInst.rt == COP2_BC_BCTL))); - if (isInternalTarget) - { - targetAction = fmt::format("goto label_{:x};", target); - } - else if (!funcName.empty()) - { - targetAction = fmt::format("{}(rdram, ctx, runtime); return;", funcName); - } - else - { - targetAction = fmt::format("ctx->pc = 0x{:X}; return;", target); - } - - bool isLikely = (branchInst.opcode == OPCODE_BEQL || branchInst.opcode == OPCODE_BNEL || - branchInst.opcode == OPCODE_BLEZL || branchInst.opcode == OPCODE_BGTZL || - (branchInst.opcode == OPCODE_REGIMM && (branchInst.rt == REGIMM_BLTZL || branchInst.rt == REGIMM_BGEZL || branchInst.rt == REGIMM_BLTZALL || branchInst.rt == REGIMM_BGEZALL)) || - (branchInst.opcode == OPCODE_COP1 && branchInst.rs == COP1_BC && (branchInst.rt == COP1_BC_BCFL || branchInst.rt == COP1_BC_BCTL)) || - (branchInst.opcode == OPCODE_COP2 && branchInst.rs == COP2_BC && (branchInst.rt == COP2_BC_BCFL || branchInst.rt == COP2_BC_BCTL))); - - if (!linkCode.empty()) - { - ss << " " << linkCode << "\n"; - } + const std::string branchTakenVar = fmt::format("branch_taken_0x{:x}", branchInst.address); + ss << " {\n"; + ss << " const bool " << branchTakenVar << " = (" << conditionStr << ");\n"; if (isLikely) { - ss << " if (" << conditionStr << ") {\n"; + ss << " if (" << branchTakenVar << ") {\n"; + if (!linkCode.empty()) + { + ss << " " << linkCode << "\n"; + } if (hasValidDelaySlot) { - ss << " " << delaySlotCode << "\n"; + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); + ss << " " << delaySlotCode << "\n"; } - ss << " " << targetAction << "\n"; - ss << " }\n"; + + if (internalTargets.contains(target)) + { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); + ss << fmt::format(" goto label_{:x};\n", target); + } + else + { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); + ss << " return;\n"; + } + + ss << " }\n"; } else { + if (!linkCode.empty()) + { + ss << " if (" << branchTakenVar << ") { " << linkCode << " }\n"; + } + if (hasValidDelaySlot) { - ss << " " << delaySlotCode << "\n"; + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); + ss << " " << delaySlotCode << "\n"; } - ss << " if (" << conditionStr << ") {\n"; - ss << " " << targetAction << "\n"; - ss << " }\n"; + + ss << " if (" << branchTakenVar << ") {\n"; + if (internalTargets.contains(target)) + { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); + ss << fmt::format(" goto label_{:x};\n", target); + } + else + { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); + ss << " return;\n"; + } + ss << " }\n"; } + + ss << " }\n"; } else { ss << " " << translateInstruction(branchInst) << "\n"; if (hasValidDelaySlot) { + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", delayPc); ss << " " << delaySlotCode << "\n"; } } + + if (internalTargets.contains(delayPc) && !internalTargets.contains(fallthroughPc)) + { + ss << fmt::format("label_fallthrough_0x{:x}:\n", branchPc); + } + + ss << fmt::format(" ctx->pc = 0x{:X}u;\n", fallthroughPc); + return ss.str(); } @@ -357,8 +503,9 @@ namespace ps2recomp bool isStaticJump = (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL); if (inst.isBranch && inst.opcode != OPCODE_J && inst.opcode != OPCODE_JAL) { - int32_t offset = inst.simmediate << 2; - uint32_t target = inst.address + 4 + offset; + const int32_t offsetBytes = (static_cast(static_cast(inst.simmediate)) << 2); + const uint32_t target = static_cast( + static_cast(inst.address + 4u) + static_cast(offsetBytes)); if (target >= function.start && target < function.end) { @@ -368,9 +515,18 @@ namespace ps2recomp else if (isStaticJump) { uint32_t target = buildAbsoluteJumpTarget(inst.address, inst.target); - if (target >= function.start && target < function.end) + if (target > function.start && target < function.end) { targets.insert(target); + + if (inst.opcode == OPCODE_JAL) + { + uint32_t returnAddr = inst.address + 8; + if (returnAddr >= function.start && returnAddr < function.end) + { + targets.insert(returnAddr); + } + } } } } @@ -378,40 +534,13 @@ namespace ps2recomp return targets; } - std::string CodeGenerator::generateFunction(const Function &function, const std::vector &instructions, const bool &useHeaders) + std::string ps2recomp::CodeGenerator::generateFunction( + const Function &function, + const std::vector &instructions, + const bool &useHeaders) { std::stringstream ss; - static const std::unordered_set systemCallNames = { - "FlushCache", "ResetEE", "SetMemoryMode", - "CreateThread", "DeleteThread", "StartThread", "ExitThread", "ExitDeleteThread", - "TerminateThread", "SuspendThread", "ResumeThread", "GetThreadId", "ReferThreadStatus", - "SleepThread", "WakeupThread", "iWakeupThread", "ChangeThreadPriority", - "RotateThreadReadyQueue", "ReleaseWaitThread", "iReleaseWaitThread", - "CreateSema", "DeleteSema", "SignalSema", "iSignalSema", "WaitSema", "PollSema", - "iPollSema", "ReferSemaStatus", "iReferSemaStatus", "CreateEventFlag", - "DeleteEventFlag", "SetEventFlag", "iSetEventFlag", "ClearEventFlag", - "iClearEventFlag", "WaitEventFlag", "PollEventFlag", "iPollEventFlag", - "ReferEventFlagStatus", "iReferEventFlagStatus", "SetAlarm", "iSetAlarm", - "CancelAlarm", "iCancelAlarm", "EnableIntc", "DisableIntc", "EnableDmac", - "DisableDmac", "SifStopModule", "SifLoadModule", "SifInitRpc", "SifBindRpc", - "SifCallRpc", "SifRegisterRpc", "SifCheckStatRpc", "SifSetRpcQueue", - "SifRemoveRpcQueue", "SifRemoveRpc", "fioOpen", "fioClose", "fioRead", "fioWrite", - "fioLseek", "fioMkdir", "fioChdir", "fioRmdir", "fioGetstat", "fioRemove", - "GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetVideoMode", "GetOsdConfigParam", - "SetOsdConfigParam", "GetRomName", "sceSifLoadModule", - "SifSetDChain"}; - - if (systemCallNames.contains(function.name)) - { - std::string sanitizedName = sanitizeFunctionName(function.name); - ss << "// System call wrapper for " << function.name << "\n"; - ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n"; - ss << " ps2_syscalls::" << function.name << "(rdram, ctx, runtime);\n"; - ss << "}\n"; - return ss.str(); - } - if (useHeaders) { ss << "#include \"ps2_runtime_macros.h\"\n"; @@ -424,14 +553,19 @@ namespace ps2recomp ss << "// Function: " << function.name << "\n"; ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; + std::string sanitizedName = getFunctionName(function.start); if (sanitizedName.empty()) { std::stringstream nameBuilder; - nameBuilder << "Errorfunc_" << std::hex << function.start; // this should never happen but lets put here just to track + nameBuilder << "Errorfunc_" << std::hex << function.start; sanitizedName = nameBuilder.str(); } + ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n\n"; + ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n" + << std::dec; + ss << "\n"; for (size_t i = 0; i < instructions.size(); ++i) { @@ -457,12 +591,19 @@ namespace ps2recomp ss << handleBranchDelaySlots(inst, delaySlot, function, internalTargets); - // Skip the delay slot instruction as we've already handled it - ++i; + ++i; // Skip delay slot instruction (handled inside branch logic) } else { - ss << " " << translateInstruction(inst) << "\n"; + ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n" + << std::dec; + + ss << " " << translateInstruction(inst); + if (inst.isMmio) + { + ss << " // MMIO: 0x" << std::hex << inst.mmioAddress << std::dec; + } + ss << "\n"; } } catch (const std::exception &e) @@ -479,7 +620,6 @@ namespace ps2recomp } ss << "}\n"; - return ss.str(); } @@ -490,6 +630,24 @@ namespace ps2recomp return translateMMIInstruction(inst); } + auto genRead = [&](int width, const std::string &addr) + { + if (inst.isMmio) + { + return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr); + } + return fmt::format("READ{}({})", width, addr); + }; + + auto genWrite = [&](int width, const std::string &addr, const std::string &val) + { + if (inst.isMmio) + { + return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, val); + } + return fmt::format("WRITE{}({}, {})", width, addr, val); + }; + switch (inst.opcode) { case OPCODE_SPECIAL: @@ -515,8 +673,6 @@ namespace ps2recomp case OPCODE_ADDIU: if (inst.rt == 0) return "// NOP (addiu $zero, ...)"; - return fmt::format("SET_GPR_S32(ctx, {}, ADD32(GPR_U32(ctx, {}), {}));", - inst.rt, inst.rs, inst.simmediate); return fmt::format("SET_GPR_S32(ctx, {}, ADD32(GPR_U32(ctx, {}), {}));", inst.rt, inst.rs, inst.simmediate); case OPCODE_SLTI: return fmt::format("SET_GPR_U32(ctx, {}, SLT32(GPR_S32(ctx, {}), {}));", inst.rt, inst.rs, inst.simmediate); @@ -531,39 +687,42 @@ namespace ps2recomp case OPCODE_LUI: return fmt::format("SET_GPR_U32(ctx, {}, ((uint32_t){} << 16));", inst.rt, inst.immediate); case OPCODE_LB: - return fmt::format("SET_GPR_S32(ctx, {}, (int8_t)READ8(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_S32(ctx, {}, (int8_t){});", inst.rt, genRead(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LH: - return fmt::format("SET_GPR_S32(ctx, {}, (int16_t)READ16(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_S32(ctx, {}, (int16_t){});", inst.rt, genRead(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LW: - return fmt::format("SET_GPR_U32(ctx, {}, READ32(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_U32(ctx, {}, {});", inst.rt, genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LBU: - return fmt::format("SET_GPR_U32(ctx, {}, (uint8_t)READ8(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_U32(ctx, {}, (uint8_t){});", inst.rt, genRead(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LHU: - return fmt::format("SET_GPR_U32(ctx, {}, (uint16_t)READ16(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_U32(ctx, {}, (uint16_t){});", inst.rt, genRead(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_LWU: - return fmt::format("SET_GPR_U32(ctx, {}, READ32(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_U32(ctx, {}, {});", inst.rt, genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_SB: - return fmt::format("WRITE8(ADD32(GPR_U32(ctx, {}), {}), (uint8_t)GPR_U32(ctx, {}));", inst.rs, inst.simmediate, inst.rt); + return genWrite(8, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("(uint8_t)GPR_U32(ctx, {})", inst.rt)) + ";"; case OPCODE_SH: - return fmt::format("WRITE16(ADD32(GPR_U32(ctx, {}), {}), (uint16_t)GPR_U32(ctx, {}));", inst.rs, inst.simmediate, inst.rt); + return genWrite(16, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("(uint16_t)GPR_U32(ctx, {})", inst.rt)) + ";"; case OPCODE_SW: - return fmt::format("WRITE32(ADD32(GPR_U32(ctx, {}), {}), GPR_U32(ctx, {}));", inst.rs, inst.simmediate, inst.rt); + return genWrite(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("GPR_U32(ctx, {})", inst.rt)) + ";"; case OPCODE_LQ: - return fmt::format("SET_GPR_VEC(ctx, {}, READ128(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_VEC(ctx, {}, {});", inst.rt, genRead(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_SQ: - return fmt::format("WRITE128(ADD32(GPR_U32(ctx, {}), {}), GPR_VEC(ctx, {}));", inst.rs, inst.simmediate, inst.rt); + return genWrite(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("GPR_VEC(ctx, {})", inst.rt)) + ";"; case OPCODE_LD: - return fmt::format("SET_GPR_U64(ctx, {}, READ64(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("SET_GPR_U64(ctx, {}, {});", inst.rt, genRead(64, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_SD: - return fmt::format("WRITE64(ADD32(GPR_U32(ctx, {}), {}), GPR_U64(ctx, {}));", inst.rs, inst.simmediate, inst.rt); + return genWrite(64, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("GPR_U64(ctx, {})", inst.rt)) + ";"; case OPCODE_LWC1: - return fmt::format("{{ uint32_t val = READ32(ADD32(GPR_U32(ctx, {}), {})); ctx->f[{}] = *(float*)&val; }}", inst.rs, inst.simmediate, inst.rt); + return fmt::format("{{ uint32_t bits = {}; float f; std::memcpy(&f, &bits, sizeof(f)); ctx->f[{}] = f; }}", genRead(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate)), inst.rt); case OPCODE_SWC1: - return fmt::format("{{ float val = ctx->f[{}]; WRITE32(ADD32(GPR_U32(ctx, {}), {}), *(uint32_t*)&val); }}", inst.rt, inst.rs, inst.simmediate); + return fmt::format( + "{{ float f = ctx->f[{}]; uint32_t bits; std::memcpy(&bits, &f, sizeof(bits)); {}; }}", + inst.rt, + genWrite(32, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), "bits")); case OPCODE_LDC2: // was OPCODE_LQC2 need to check - return fmt::format("ctx->vu0_vf[{}] = _mm_castsi128_ps(READ128(ADD32(GPR_U32(ctx, {}), {})));", inst.rt, inst.rs, inst.simmediate); + return fmt::format("ctx->vu0_vf[{}] = _mm_castsi128_ps({});", inst.rt, genRead(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate))); case OPCODE_SDC2: // was OPCODE_SQC2 need to check - return fmt::format("WRITE128(ADD32(GPR_U32(ctx, {}), {}), _mm_castps_si128(ctx->vu0_vf[{}]));", inst.rs, inst.simmediate, inst.rt); + return genWrite(128, fmt::format("ADD32(GPR_U32(ctx, {}), {})", inst.rs, inst.simmediate), fmt::format("_mm_castps_si128(ctx->vu0_vf[{}])", inst.rt)) + ";"; case OPCODE_DADDI: return fmt::format( "{{ int64_t src = (int64_t)GPR_S64(ctx, {}); " @@ -593,75 +752,75 @@ namespace ps2recomp case OPCODE_LDL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (addr & 7) << 3; " + "uint32_t shift = (7 - (addr & 7)) << 3; " "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL << shift; " - "uint64_t aligned_data = READ64(addr & ~7ULL); " - "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & ~mask) | (aligned_data & mask)); }}", - inst.rs, inst.simmediate, inst.rt, inst.rt); + "uint64_t aligned_data = {}; " + "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & ~mask) | ((aligned_data << shift) & mask)); }}", + inst.rs, inst.simmediate, genRead(64, "addr & ~7ULL"), inst.rt, inst.rt); case OPCODE_LDR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = ((~addr) & 7) << 3; " + "uint32_t shift = (addr & 7) << 3; " "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL >> shift; " - "uint64_t aligned_data = READ64(addr & ~7ULL); " - "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & ~mask) | (aligned_data & mask)); }}", - inst.rs, inst.simmediate, inst.rt, inst.rt); + "uint64_t aligned_data = {}; " + "SET_GPR_U64(ctx, {}, (GPR_U64(ctx, {}) & ~mask) | ((aligned_data >> shift) & mask)); }}", + inst.rs, inst.simmediate, genRead(64, "addr & ~7ULL"), inst.rt, inst.rt); case OPCODE_LWL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = ((~addr) & 3) << 3; /* big-endian */ " - "uint32_t mask = 0xFFFFFFFF >> shift; " - "uint32_t word = READ32(addr & ~3); " - "SET_GPR_U32(ctx, {}, (GPR_U32(ctx,{}) & ~mask) | ((word >> shift) & mask)); }}", - inst.rs, inst.simmediate, inst.rt, inst.rt); + "uint32_t shift = (3 - (addr & 3)) << 3; " + "uint32_t mask = 0xFFFFFFFF << shift; " + "uint32_t aligned_word = {}; " + "SET_GPR_U32(ctx, {}, (GPR_U32(ctx, {}) & ~mask) | ((aligned_word << shift) & mask)); }}", + inst.rs, inst.simmediate, genRead(32, "addr & ~3"), inst.rt, inst.rt); case OPCODE_LWR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " "uint32_t shift = (addr & 3) << 3; " - "uint32_t mask = 0xFFFFFFFF << shift; " - "uint32_t word = READ32(addr & ~3); " - "SET_GPR_U32(ctx, {}, (GPR_U32(ctx,{}) & ~mask) | (word << shift)); }}", - inst.rs, inst.simmediate, inst.rt, inst.rt); + "uint32_t mask = 0xFFFFFFFF >> shift; " + "uint32_t aligned_word = {}; " + "SET_GPR_U32(ctx, {}, (GPR_U32(ctx, {}) & ~mask) | ((aligned_word >> shift) & mask)); }}", + inst.rs, inst.simmediate, genRead(32, "addr & ~3"), inst.rt, inst.rt); case OPCODE_SWL: + return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " + "uint32_t shift = (3 - (addr & 3)) << 3; " + "uint32_t mask = 0xFFFFFFFF >> shift; " + "uint32_t aligned_addr = addr & ~3; " + "uint32_t old_data = {}; " + "uint32_t new_data = (old_data & ~mask) | ((GPR_U32(ctx, {}) >> shift) & mask); " + "{}; }}", + inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, genWrite(32, "aligned_addr", "new_data")); + + case OPCODE_SWR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " "uint32_t shift = (addr & 3) << 3; " "uint32_t mask = 0xFFFFFFFF << shift; " "uint32_t aligned_addr = addr & ~3; " - "uint32_t old_data = READ32(aligned_addr); " - "uint32_t new_data = (old_data & ~mask) | (GPR_U32(ctx, {}) & mask); " - "WRITE32(aligned_addr, new_data); }}", - inst.rs, inst.simmediate, inst.rt); - - case OPCODE_SWR: - return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = ((~addr) & 3) << 3; " - "uint32_t mask = 0xFFFFFFFF >> shift; " - "uint32_t aligned_addr = addr & ~3; " - "uint32_t old_data = READ32(aligned_addr); " - "uint32_t new_data = (old_data & ~mask) | (GPR_U32(ctx, {}) & mask); " - "WRITE32(aligned_addr, new_data); }}", - inst.rs, inst.simmediate, inst.rt); + "uint32_t old_data = {}; " + "uint32_t new_data = (old_data & ~mask) | ((GPR_U32(ctx, {}) << shift) & mask); " + "{}; }}", + inst.rs, inst.simmediate, genRead(32, "aligned_addr"), inst.rt, genWrite(32, "aligned_addr", "new_data")); case OPCODE_SDL: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = (addr & 7) << 3; " - "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL << shift; " + "uint32_t shift = (7 - (addr & 7)) << 3; " + "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL >> shift; " "uint64_t aligned_addr = addr & ~7ULL; " - "uint64_t old_data = READ64(aligned_addr); " - "uint64_t new_data = (old_data & ~mask) | (GPR_U64(ctx, {}) & mask); " - "WRITE64(aligned_addr, new_data); }}", - inst.rs, inst.simmediate, inst.rt); + "uint64_t old_data = {}; " + "uint64_t new_data = (old_data & ~mask) | ((GPR_U64(ctx, {}) >> shift) & mask); " + "{}; }}", + inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, genWrite(64, "aligned_addr", "new_data")); case OPCODE_SDR: return fmt::format("{{ uint32_t addr = ADD32(GPR_U32(ctx, {}), {}); " - "uint32_t shift = ((~addr) & 7) << 3; " - "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL >> shift; " + "uint32_t shift = ((addr & 7)) << 3; " + "uint64_t mask = 0xFFFFFFFFFFFFFFFFULL << shift; " "uint64_t aligned_addr = addr & ~7ULL; " - "uint64_t old_data = READ64(aligned_addr); " - "uint64_t new_data = (old_data & ~mask) | (GPR_U64(ctx, {}) & mask); " - "WRITE64(aligned_addr, new_data); }}", - inst.rs, inst.simmediate, inst.rt); + "uint64_t old_data = {}; " + "uint64_t new_data = (old_data & ~mask) | ((GPR_U64(ctx, {}) << shift) & mask); " + "{}; }}", + inst.rs, inst.simmediate, genRead(64, "aligned_addr"), inst.rt, genWrite(64, "aligned_addr", "new_data")); case OPCODE_CACHE: return "// CACHE instruction (ignored)"; case OPCODE_PREF: @@ -696,7 +855,7 @@ namespace ps2recomp case SPECIAL_JALR: return fmt::format("// JALR ${}, ${} - Handled by branch logic", inst.rd, inst.rs); case SPECIAL_SYSCALL: - return fmt::format("runtime->handleSyscall(rdram, ctx);"); + return fmt::format("runtime->handleSyscall(rdram, ctx, 0x{:X}u);", (inst.raw >> 6) & 0xFFFFFu); case SPECIAL_BREAK: return fmt::format("runtime->handleBreak(rdram, ctx);"); case SPECIAL_SYNC: @@ -714,12 +873,24 @@ namespace ps2recomp case SPECIAL_MULTU: return fmt::format("{{ uint64_t result = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); ctx->lo = (uint32_t)result; ctx->hi = (uint32_t)(result >> 32); }}", inst.rs, inst.rt); case SPECIAL_DIV: - return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); if (divisor != 0) {{ ctx->lo = (uint32_t)(GPR_S32(ctx, {}) / divisor); ctx->hi = (uint32_t)(GPR_S32(ctx, {}) % divisor); }} else {{ ctx->lo = (GPR_S32(ctx,{}) < 0) ? 1 : -1; ctx->hi = GPR_S32(ctx,{}); }} }}", inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); + return fmt::format("{{ int32_t divisor = GPR_S32(ctx, {}); " + " int32_t dividend = GPR_S32(ctx, {}); " + " if (divisor != 0) {{ " + " if (divisor == -1 && dividend == INT32_MIN) {{ " + " ctx->lo = INT32_MIN; ctx->hi = 0; " + " }} else {{ " + " ctx->lo = (uint32_t)(dividend / divisor); " + " ctx->hi = (uint32_t)(dividend % divisor); " + " }} " + " }} else {{ " + " ctx->lo = (dividend < 0) ? 1 : -1; ctx->hi = dividend; " + " }} }}", + inst.rt, inst.rs); case SPECIAL_DIVU: return fmt::format("{{ uint32_t divisor = GPR_U32(ctx, {}); if (divisor != 0) {{ ctx->lo = GPR_U32(ctx, {}) / divisor; ctx->hi = GPR_U32(ctx, {}) % divisor; }} else {{ ctx->lo = 0xFFFFFFFF; ctx->hi = GPR_U32(ctx,{}); }} }}", inst.rt, inst.rs, inst.rt, inst.rs, inst.rt); case SPECIAL_ADD: return fmt::format( - "if (runtime->check_overflow) {{ " + "{{ " " int32_t rs_val = GPR_S32(ctx, {}); " " int32_t rt_val = GPR_S32(ctx, {}); " " int64_t result = (int64_t)rs_val + (int64_t)rt_val; " @@ -728,10 +899,8 @@ namespace ps2recomp " }} else {{ " " SET_GPR_S32(ctx, {}, (int32_t)result); " " }} " - "}} else {{ " - " SET_GPR_S32(ctx, {}, ADD32(GPR_S32(ctx, {}), GPR_S32(ctx, {}))); " "}}", - inst.rs, inst.rt, inst.rd, inst.rd, inst.rs, inst.rt); + inst.rs, inst.rt, inst.rd); case SPECIAL_ADDU: return fmt::format("SET_GPR_U32(ctx, {}, ADD32(GPR_U32(ctx, {}), GPR_U32(ctx, {})));", inst.rd, inst.rs, inst.rt); case SPECIAL_SUB: @@ -764,8 +933,17 @@ namespace ps2recomp case SPECIAL_MTSA: return fmt::format("ctx->sa = GPR_U32(ctx, {}) & 0x1F;", inst.rs); case SPECIAL_DADD: + return fmt::format( + "{{ int64_t a = (int64_t)GPR_S64(ctx, {}); " + "int64_t b = (int64_t)GPR_S64(ctx, {}); " + "int64_t r = a + b; " + "if (((a ^ b) >= 0) && ((a ^ r) < 0)) runtime->SignalException(ctx, EXCEPTION_INTEGER_OVERFLOW); " + "else SET_GPR_S64(ctx, {}, r); }}", + inst.rs, inst.rt, inst.rd); case SPECIAL_DADDU: - return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) + GPR_U64(ctx, {}));", inst.rd, inst.rs, inst.rt); + return fmt::format( + "SET_GPR_U64(ctx, {}, (uint64_t)GPR_U64(ctx, {}) + (uint64_t)GPR_U64(ctx, {}));", + inst.rd, inst.rs, inst.rt); case SPECIAL_DSUB: case SPECIAL_DSUBU: return fmt::format("SET_GPR_U64(ctx, {}, GPR_U64(ctx, {}) - GPR_U64(ctx, {}));", inst.rd, inst.rs, inst.rt); @@ -817,7 +995,8 @@ namespace ps2recomp case REGIMM_BLTZALL: case REGIMM_BGEZALL: { - uint32_t target = inst.address + 4 + (inst.simmediate << 2); + const int32_t offsetBytes = (static_cast(static_cast(inst.simmediate)) << 2); + const uint32_t target = static_cast(static_cast(inst.address + 4u) + static_cast(offsetBytes)); return fmt::format("// REGIMM branch instruction to 0x{:X} - Handled by branch logic", target); } case REGIMM_MTSAB: @@ -966,15 +1145,15 @@ namespace ps2recomp return fmt::format("runtime->handleTLBP(rdram, ctx);"); case COP0_CO_ERET: return fmt::format( - "if (ctx->cop0_status & 0x4) {{ \\\n" // Check ERL bit (bit 2) - " ctx->pc = ctx->cop0_errorepc; \\\n" - " ctx->cop0_status &= ~0x4; \\\n" // Clear ERL bit - "}} else {{ \\\n" // If ERL is not set, use EPC and clear EXL (bit 1) - " ctx->pc = ctx->cop0_epc; \\\n" // Note: If neither ERL/EXL set, behavior is undefined; using EPC is common. - " ctx->cop0_status &= ~0x2; \\\n" // Clear EXL bit - "}} \\\n" - "runtime->clearLLBit(ctx); \\\n" // Essential: Clear Load-Linked bit - "return;" // Stop execution in this recompiled block + "if (ctx->cop0_status & 0x4) {{ \n" // Check ERL bit (bit 2) + " ctx->pc = ctx->cop0_errorepc; \n" + " ctx->cop0_status &= ~0x4; \n" // Clear ERL bit + "}} else {{ \n" // If ERL is not set, use EPC and clear EXL (bit 1) + " ctx->pc = ctx->cop0_epc; \n" // Note: If neither ERL/EXL set, behavior is undefined; using EPC is common. + " ctx->cop0_status &= ~0x2; \n" // Clear EXL bit + "}} \n" + "runtime->clearLLBit(ctx); \n" // Essential: Clear Load-Linked bit + "return;" // Stop execution in this recompiled block ); case COP0_CO_EI: return fmt::format("ctx->cop0_status |= 0x1; // Enable interrupts"); @@ -1155,7 +1334,15 @@ namespace ps2recomp case MMI_MADDU1: return fmt::format("{{ uint64_t acc = ((uint64_t)ctx->hi1 << 32) | ctx->lo1; uint64_t prod = (uint64_t)GPR_U32(ctx, {}) * (uint64_t)GPR_U32(ctx, {}); uint64_t result = acc + prod; ctx->lo1 = (uint32_t)result; ctx->hi1 = (uint32_t)(result >> 32); }}", rs, rt); case MMI_PLZCW: - return fmt::format("{{ uint32_t val = GPR_U32(ctx, {}); SET_GPR_U32(ctx, {}, ps2_clz32(val)); }}", rs, rd); + return fmt::format( + "{{ " + "uint64_t v = GPR_U64(ctx, {}); " + "uint32_t lo = (uint32_t)(v & 0xFFFFFFFFu); " + "uint32_t hi = (uint32_t)(v >> 32); " + "uint64_t out = ((uint64_t)ps2_clz32(hi) << 32) | (uint64_t)ps2_clz32(lo); " + "SET_GPR_U64(ctx, {}, out); " + "}}", + rs, rd); case MMI_PSLLH: return fmt::format("SET_GPR_VEC(ctx, {}, _mm_slli_epi16(GPR_VEC(ctx, {}), {}));", rd, rt, sa); case MMI_PSRLH: @@ -1707,37 +1894,37 @@ namespace ps2recomp std::string CodeGenerator::translateVU_VADD_Field(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VSUB_Field(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VMUL_Field(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VADD(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + return fmt::format("{{ __m128 res = PS2_VADD(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VSUB(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + return fmt::format("{{ __m128 res = PS2_VSUB(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translateVU_VMUL(const Instruction &inst) { uint8_t dest_mask = inst.vectorInfo.vectorField; - return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); + return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = PS2_VBLEND(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, inst.rt, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rd, inst.rd); } std::string CodeGenerator::translatePMADDW(const Instruction &inst) @@ -1815,8 +2002,8 @@ namespace ps2recomp std::string CodeGenerator::translatePREVH(const Instruction &inst) { // Reverses the order of the 8 halfwords - return fmt::format("{{ __m128i mask = _mm_set_epi8(0,1, 2,3, 4,5, 6,7, 8,9, 10,11, 12,13, 14,15); " - "SET_GPR_VEC(ctx, {}, _mm_shuffle_epi8(GPR_VEC(ctx, {}), mask)); }}", + return fmt::format("{{ __m128i mask = _mm_setr_epi8(14,15, 12,13, 10,11, 8,9, 6,7, 4,5, 2,3, 0,1); " + "SET_GPR_VEC(ctx, {}, PS2_SHUFFLE_EPI8(GPR_VEC(ctx, {}), mask)); }}", inst.rd, inst.rs); } @@ -1836,18 +2023,28 @@ namespace ps2recomp std::string CodeGenerator::translatePDIVBW(const Instruction &inst) { - // Divide each element of rs by the first element of rt - return fmt::format("{{ int32_t div = GPR_S32(ctx, {}); \n" - " int32_t r0 = GPR_S32(ctx, {}); int32_t r1 = GPR_S32(ctx, {}); \n" - " int32_t r2 = GPR_S32(ctx, {}); int32_t r3 = GPR_S32(ctx, {}); \n" - " int32_t q0=0, q1=0, q2=0, q3=0; \n" - " if (div != 0) {{ \n" - " q0 = r0 / div; ctx->lo = q0; ctx->hi = r0 % div; \n" // HI/LO only from first element - " q1 = r1 / div; q2 = r2 / div; q3 = r3 / div; \n" - " }} else {{ ctx->lo = (r0 < 0) ? 1 : -1; ctx->hi = r0; }} \n" - " SET_GPR_VEC(ctx, {}, _mm_set_epi32(q3, q2, q1, q0)); }}", - inst.rt, inst.rs + 0, inst.rs + 1, inst.rs + 2, inst.rs + 3, // TODO check if GPR_S32 allows offset indexing - inst.rd); + return fmt::format( + "{{\n" + " __m128i rsVec = GPR_VEC(ctx, {});\n" + " __m128i rtVec = GPR_VEC(ctx, {});\n" + " alignas(16) int32_t rsWords[4];\n" + " alignas(16) int32_t rtWords[4];\n" + " _mm_store_si128((__m128i*)rsWords, rsVec);\n" + " _mm_store_si128((__m128i*)rtWords, rtVec);\n" + " int32_t div = rtWords[0];\n" + " int32_t q0 = 0, q1 = 0, q2 = 0, q3 = 0;\n" + " if (div != 0) {{\n" + " q0 = rsWords[0] / div; ctx->lo = (uint32_t)q0; ctx->hi = (uint32_t)(rsWords[0] % div);\n" + " q1 = rsWords[1] / div;\n" + " q2 = rsWords[2] / div;\n" + " q3 = rsWords[3] / div;\n" + " }} else {{\n" + " ctx->lo = (rsWords[0] < 0) ? 1 : -1;\n" + " ctx->hi = (uint32_t)rsWords[0];\n" + " }}\n" + " SET_GPR_VEC(ctx, {}, _mm_set_epi32(q3, q2, q1, q0));\n" + "}}", + inst.rs, inst.rt, inst.rd); } std::string CodeGenerator::translatePEXEW(const Instruction &inst) @@ -1951,13 +2148,20 @@ namespace ps2recomp std::string CodeGenerator::translateVU_VMTIR(const Instruction &inst) { - return fmt::format("ctx->vu0_i = (float)ctx->vi[{}];", inst.rt); // rt = IT + return fmt::format("{{ uint32_t tmp = ctx->vi[{}]; ctx->vu0_i = *(float*)&tmp; }}", inst.rt); } std::string CodeGenerator::translateVU_VMFIR(const Instruction &inst) { - uint8_t dest_mask = inst.vectorInfo.vectorField; // Use parsed field - return fmt::format("{{ float val = (float)ctx->vi[{}]; __m128 res = _mm_set1_ps(val); __m128i mask = _mm_set_epi32({}, {}, {}, {}); ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", inst.rs, (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, inst.rt, inst.rt); // rs=IS, rt=FT + uint8_t dest_mask = inst.vectorInfo.vectorField; + return fmt::format("{{ uint32_t tmp = ctx->vi[{}]; float val = *(float*)&tmp; " + "__m128 res = _mm_set1_ps(val); " + "__m128i mask = _mm_set_epi32({}, {}, {}, {}); " + "ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}", + inst.rs, + (dest_mask & 0x8) ? -1 : 0, (dest_mask & 0x4) ? -1 : 0, + (dest_mask & 0x2) ? -1 : 0, (dest_mask & 0x1) ? -1 : 0, + inst.rt, inst.rt); } std::string CodeGenerator::translateVU_VILWR(const Instruction &inst) @@ -2032,7 +2236,7 @@ namespace ps2recomp return fmt::format( "{{ " " uint32_t r_vals[4]; " - " _mm_storeu_si128((__m128i*)r_vals, (__m128i)ctx->vu0_r); " + " _mm_storeu_si128((__m128i*)r_vals, _mm_castps_si128(ctx->vu0_r)); " " " " // Simple LFSR-based random number generation (PS2-like behavior) " " uint32_t feedback = r_vals[0] ^ (r_vals[0] << 13) ^ (r_vals[1] >> 19) ^ (r_vals[2] << 7); " @@ -2041,7 +2245,7 @@ namespace ps2recomp " r_vals[2] = r_vals[3]; " " r_vals[3] = feedback; " " " - " ctx->vu0_r = _mm_loadu_si128((__m128i*)r_vals); " + " ctx->vu0_r = _mm_castsi128_ps(_mm_loadu_si128((__m128i*)r_vals)); \n" "}}"); } @@ -2155,7 +2359,7 @@ namespace ps2recomp " uint32_t r2 = r1 * 0x41C64E6D + 0x3039; " " uint32_t r3 = r2 * 0x41C64E6D + 0x3039; " " " - " ctx->vu0_r = _mm_set_epi32(r3, r2, r1, r0); " + " ctx->vu0_r = _mm_castsi128_ps(_mm_set_epi32(r3, r2, r1, r0)); \n " "}}", fs_reg); } @@ -2166,8 +2370,8 @@ namespace ps2recomp return fmt::format( "{{ " - " __m128i r_current = (__m128i)ctx->vu0_r; " - " __m128i fs_data = (__m128i)ctx->vu0_vf[{}]; " + " __m128i r_current = _mm_castps_si128(ctx->vu0_r); " + " __m128i fs_data = _mm_castps_si128(ctx->vu0_vf[{}]); " " " " // XOR the current random value with the data from the VU vector register " " __m128i xored = _mm_xor_si128(r_current, fs_data); " @@ -2230,6 +2434,16 @@ namespace ps2recomp std::stringstream ss; std::unordered_set registeredAddresses; + auto emitRegistration = [&](uint32_t address, const std::string &name) + { + if (!registeredAddresses.insert(address).second) + { + return; + } + + ss << " runtime.registerFunction(0x" << std::hex << address << std::dec + << ", " << name << ");\n"; + }; // Begin function ss << "#include \"ps2_runtime.h\"\n"; @@ -2251,14 +2465,26 @@ namespace ps2recomp for (const auto &function : functions) { - if (!function.isRecompiled && !function.isStub) + if (!function.isRecompiled && !function.isStub && !function.isSkipped) continue; std::string generatedName = getFunctionName(function.start); - if (function.isStub) + if (function.isSkipped) { - stubFunctions.emplace_back(function.start, generatedName); + libraryFunctions.emplace_back(function.start, generatedName); + } + else if (function.isStub) + { + const auto target = PS2Recompiler::resolveStubTarget(function.name); + if (target == StubTarget::Syscall) + { + systemCallFunctions.emplace_back(function.start, generatedName); + } + else + { + stubFunctions.emplace_back(function.start, generatedName); + } } else { @@ -2268,37 +2494,42 @@ namespace ps2recomp if (m_bootstrapInfo.valid) { - ss << " // Register ELF entry bootstrap\n"; - ss << " runtime.registerFunction(0x" << std::hex << m_bootstrapInfo.entry << std::dec - << ", entry_" << std::hex << m_bootstrapInfo.entry << std::dec << ");\n\n"; + ss << " // Register ELF entry function\n"; + std::string entryTarget = m_bootstrapInfo.entryName; + if (entryTarget.empty()) + { + entryTarget = getFunctionName(m_bootstrapInfo.entry); + } + if (entryTarget.empty()) + { + throw std::runtime_error("No entry function name available for registration."); + } + emitRegistration(m_bootstrapInfo.entry, entryTarget); + ss << "\n"; } ss << " // Register recompiled functions\n"; for (const auto &[first, second] : normalFunctions) { - ss << " runtime.registerFunction(0x" << std::hex << first << std::dec - << ", " << second << ");\n"; + emitRegistration(first, second); } ss << "\n // Register stub functions\n"; for (const auto &[first, second] : stubFunctions) { - ss << " runtime.registerFunction(0x" << std::hex << first << std::dec - << ", " << second << ");\n"; + emitRegistration(first, second); } ss << "\n // Register system call stubs\n"; for (const auto &[first, second] : systemCallFunctions) { - ss << " runtime.registerFunction(0x" << std::hex << first << std::dec - << ", " << second << ");\n"; + emitRegistration(first, second); } ss << "\n // Register library stubs\n"; for (const auto &[first, second] : libraryFunctions) { - ss << " runtime.registerFunction(0x" << std::hex << first << std::dec - << ", " << second << ");\n"; + emitRegistration(first, second); } ss << "}\n"; @@ -2351,49 +2582,4 @@ namespace ps2recomp return nullptr; } - - std::string CodeGenerator::generateBootstrapFunction() const - { - if (!m_bootstrapInfo.valid) - return {}; - - std::stringstream ss; - ss << "// Auto-generated bootstrap for ELF entry point\n"; - ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec - << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n"; - if (m_bootstrapInfo.bssEnd > m_bootstrapInfo.bssStart) - { - ss << " const uint32_t bss_start = 0x" << std::hex << m_bootstrapInfo.bssStart << ";\n"; - ss << " const uint32_t bss_end = 0x" << std::hex << m_bootstrapInfo.bssEnd << ";\n"; - ss << " __m128i zero = _mm_setzero_si128();\n"; - ss << " uint32_t addr = bss_start;\n"; - ss << " for (; (bss_end - addr) >= 16; addr += 16) {\n"; - ss << " WRITE128(addr, zero);\n"; - ss << " }\n"; - ss << " for (; (bss_end - addr) >= 4; addr += 4) {\n"; - ss << " WRITE32(addr, 0);\n"; - ss << " }\n"; - ss << " for (; addr < bss_end; ++addr) {\n"; - ss << " WRITE8(addr, 0);\n"; - ss << " }\n\n"; - } - if (m_bootstrapInfo.gp != 0) - { - ss << " SET_GPR_U32(ctx, 28, 0x" << std::hex << m_bootstrapInfo.gp << ");\n"; - } - if (m_bootstrapInfo.bssEnd > m_bootstrapInfo.bssStart) - { - ss << " SET_GPR_U32(ctx, 29, bss_end);\n"; - } - if (!m_bootstrapInfo.entryName.empty()) - { - ss << " " << m_bootstrapInfo.entryName << "(rdram, ctx, runtime);\n"; - } - else - { - throw std::runtime_error(" No entry function name available for bootstrap."); - } - ss << "}\n"; - return ss.str(); - } -}; +} diff --git a/ps2xRecomp/src/lib/config_manager.cpp b/ps2xRecomp/src/lib/config_manager.cpp index b1a7f2b..7d8c26f 100644 --- a/ps2xRecomp/src/lib/config_manager.cpp +++ b/ps2xRecomp/src/lib/config_manager.cpp @@ -29,6 +29,9 @@ namespace ps2recomp config.ghidraMapPath = toml::find_or(general, "ghidra_output", ""); config.outputPath = toml::find(general, "output"); config.singleFileOutput = toml::find_or(general, "single_file_output", false); + config.patchSyscalls = toml::find_or(general, "patch_syscalls", config.patchSyscalls); + config.patchCop0 = toml::find_or(general, "patch_cop0", config.patchCop0); + config.patchCache = toml::find_or(general, "patch_cache", config.patchCache); if (general.contains("stubs") && general.at("stubs").is_array()) { @@ -90,6 +93,25 @@ namespace ps2recomp } } } + + if (data.contains("mmio") && data.at("mmio").is_table()) + { + const auto &mmioTable = toml::find(data, "mmio").as_table(); + for (const auto &[key, value] : mmioTable) + { + uint32_t instAddr = std::stoul(key, nullptr, 0); + uint32_t mmioAddr = 0; + if (value.is_string()) + { + mmioAddr = std::stoul(value.as_string(), nullptr, 0); + } + else if (value.is_integer()) + { + mmioAddr = static_cast(value.as_integer()); + } + config.mmioByInstructionAddress[instAddr] = mmioAddr; + } + } } catch (const std::exception &e) { @@ -109,10 +131,27 @@ namespace ps2recomp general["ghidra_output"] = config.ghidraMapPath; general["output"] = config.outputPath; general["single_file_output"] = config.singleFileOutput; + general["patch_syscalls"] = config.patchSyscalls; + general["patch_cop0"] = config.patchCop0; + general["patch_cache"] = config.patchCache; general["skip"] = config.skipFunctions; general["stubs"] = config.stubImplementations; data["general"] = general; + if (!config.mmioByInstructionAddress.empty()) + { + toml::table mmioTable; + for (const auto &[instAddr, mmioAddr] : config.mmioByInstructionAddress) + { + std::ostringstream keyStream; + keyStream << "0x" << std::hex << instAddr; + std::ostringstream valStream; + valStream << "0x" << std::hex << mmioAddr; + mmioTable[keyStream.str()] = valStream.str(); + } + data["mmio"] = mmioTable; + } + toml::table patches; toml::array instPatches; for (const auto &[addr, value] : config.patches) diff --git a/ps2xRecomp/src/lib/elf_parser.cpp b/ps2xRecomp/src/lib/elf_parser.cpp index 35cd9b8..462ac56 100644 --- a/ps2xRecomp/src/lib/elf_parser.cpp +++ b/ps2xRecomp/src/lib/elf_parser.cpp @@ -352,6 +352,7 @@ namespace func.end = highPc; func.isRecompiled = false; func.isStub = false; + func.isSkipped = false; if (func.name.empty()) { @@ -458,6 +459,7 @@ namespace func.end = (end > start) ? end : (start + 4); func.isRecompiled = false; func.isStub = false; + func.isSkipped = false; outFunctions.push_back(std::move(func)); } @@ -522,6 +524,7 @@ namespace ps2recomp } existing.isStub = existing.isStub || newFunction.isStub; + existing.isSkipped = existing.isSkipped || newFunction.isSkipped; }; for (const auto &symbol : m_symbols) @@ -536,6 +539,7 @@ namespace ps2recomp func.end = (symbol.size > 0) ? (symbol.address + symbol.size) : 0; func.isRecompiled = false; func.isStub = false; + func.isSkipped = false; addOrMerge(func); } @@ -675,6 +679,53 @@ namespace ps2recomp return 0; } + void ElfParser::debugAddress(uint32_t address) const + { + for (const auto §ion : m_sections) + { + if (address < section.address || address >= (section.address + section.size)) + { + continue; + } + + const uint32_t offset = address - section.address; + + std::printf( + "Address 0x%08X -> section '%s'\n" + " section.address=0x%08X section.size=0x%08X section.offset=0x%08X\n" + " isCode=%d isData=%d isBSS=%d isReadOnly=%d data=%p\n" + " offsetInSection=0x%08X\n", + address, + section.name.c_str(), + section.address, section.size, section.offset, + section.isCode ? 1 : 0, + section.isData ? 1 : 0, + section.isBSS ? 1 : 0, + section.isReadOnly ? 1 : 0, + (void *)section.data, + offset); + + if (!section.data) + { + std::printf(" section.data == nullptr (possible SHT_NOBITS/BSS)\n"); + return; + } + + const uint32_t dumpStart = (offset >= 16) ? (offset - 16) : 0; + const uint32_t dumpEnd = std::min(section.size, offset + 32); + + std::printf(" bytes around address:\n "); + for (uint32_t dumpOffset = dumpStart; dumpOffset < dumpEnd; ++dumpOffset) + { + std::printf("%02X ", section.data[dumpOffset]); + } + std::printf("\n"); + return; + } + + std::printf("Address 0x%08X not covered by any section in m_sections\n", address); + } + uint32_t ElfParser::getEntryPoint() const { return static_cast(m_elf->get_entry()); @@ -728,6 +779,7 @@ namespace ps2recomp func.end = end; func.isRecompiled = false; func.isStub = false; + func.isSkipped = false; m_extraFunctions.push_back(std::move(func)); count++; diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index 0a5a5fb..cb0f3ea 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace fs = std::filesystem; @@ -21,13 +21,6 @@ namespace ps2recomp { namespace { - enum class StubTarget - { - Unknown, - Syscall, - Stub - }; - uint32_t decodeAbsoluteJumpTarget(uint32_t address, uint32_t target) { return ((address + 4) & 0xF0000000u) | (target << 2); @@ -78,17 +71,167 @@ namespace ps2recomp return sanitized; } - StubTarget resolveStubTarget(const std::string &name) + bool shouldGenerateCodeForFunction(const Function &function) { - if (ps2_runtime_calls::isSyscallName(name)) + return function.isRecompiled || function.isStub || function.isSkipped; + } + + enum class PatchClass + { + Generic, + Syscall, + Cop0, + Cache + }; + + PatchClass classifyPatchedInstruction(uint32_t rawInstruction) + { + const uint32_t opcode = OPCODE(rawInstruction); + if (opcode == OPCODE_SPECIAL && FUNCTION(rawInstruction) == SPECIAL_SYSCALL) { - return StubTarget::Syscall; + return PatchClass::Syscall; } - if (ps2_runtime_calls::isStubName(name)) + if (opcode == OPCODE_COP0) { - return StubTarget::Stub; + return PatchClass::Cop0; } - return StubTarget::Unknown; + if (opcode == OPCODE_CACHE) + { + return PatchClass::Cache; + } + return PatchClass::Generic; + } + + bool shouldApplyConfiguredPatch(PatchClass patchClass, const RecompilerConfig &config) + { + switch (patchClass) + { + case PatchClass::Syscall: + return config.patchSyscalls; + case PatchClass::Cop0: + return config.patchCop0; + case PatchClass::Cache: + return config.patchCache; + default: + return true; + } + } + + std::string escapeCStringLiteral(const std::string &value) + { + std::string escaped; + escaped.reserve(value.size()); + for (char c : value) + { + switch (c) + { + case '\\': + escaped += "\\\\"; + break; + case '"': + escaped += "\\\""; + break; + case '\n': + escaped += "\\n"; + break; + case '\r': + escaped += "\\r"; + break; + case '\t': + escaped += "\\t"; + break; + default: + escaped.push_back(c); + break; + } + } + return escaped; + } + + std::string trimAsciiWhitespace(const std::string &value) + { + const auto first = std::find_if_not(value.begin(), value.end(), + [](unsigned char c) + { return std::isspace(c) != 0; }); + if (first == value.end()) + { + return {}; + } + + const auto last = std::find_if_not(value.rbegin(), value.rend(), + [](unsigned char c) + { return std::isspace(c) != 0; }) + .base(); + return std::string(first, last); + } + + bool tryParseU32AddressLiteral(const std::string &literal, uint32_t &outAddress) + { + if (literal.empty()) + { + return false; + } + + try + { + size_t parsedCount = 0; + const unsigned long parsed = std::stoul(literal, &parsedCount, 0); + if (parsedCount != literal.size() || parsed > std::numeric_limits::max()) + { + return false; + } + + outAddress = static_cast(parsed); + return true; + } + catch (...) + { + return false; + } + } + + struct FunctionSelector + { + std::string name; + std::optional start; + }; + + FunctionSelector parseFunctionSelector(const std::string &rawSelector) + { + FunctionSelector selector{}; + const std::string trimmed = trimAsciiWhitespace(rawSelector); + if (trimmed.empty()) + { + return selector; + } + + const std::size_t at = trimmed.rfind('@'); + if (at != std::string::npos) + { + selector.name = trimAsciiWhitespace(trimmed.substr(0, at)); + + uint32_t parsedAddress = 0; + const std::string addressLiteral = trimAsciiWhitespace(trimmed.substr(at + 1)); + if (tryParseU32AddressLiteral(addressLiteral, parsedAddress)) + { + selector.start = parsedAddress; + return selector; + } + + // for now backward compatibility + selector.name = trimmed; + return selector; + } + + uint32_t parsedAddress = 0; + if (tryParseU32AddressLiteral(trimmed, parsedAddress)) + { + selector.start = parsedAddress; + return selector; + } + + selector.name = trimmed; + return selector; } } @@ -107,11 +250,27 @@ namespace ps2recomp for (const auto &name : m_config.skipFunctions) { - m_skipFunctions[name] = true; + const FunctionSelector selector = parseFunctionSelector(name); + if (!selector.name.empty()) + { + m_skipFunctions[selector.name] = true; + } + if (selector.start.has_value()) + { + m_skipFunctionStarts.insert(*selector.start); + } } for (const auto &name : m_config.stubImplementations) { - m_stubFunctions.insert(name); + const FunctionSelector selector = parseFunctionSelector(name); + if (!selector.name.empty()) + { + m_stubFunctions.insert(selector.name); + } + if (selector.start.has_value()) + { + m_stubFunctionStarts.insert(*selector.start); + } } m_elfParser = std::make_unique(m_config.inputPath); @@ -222,16 +381,18 @@ namespace ps2recomp { std::cout << "processing function: " << function.name << std::endl; - if (isStubFunction(function.name)) + if (isStubFunction(function)) { function.isStub = true; + function.isSkipped = false; continue; } - if (shouldSkipFunction(function.name)) + if (shouldSkipFunction(function)) { - std::cout << "Skipping function (stubbed): " << function.name << std::endl; - function.isStub = true; + std::cout << "Skipping function (runtime TODO wrapper): " << function.name << std::endl; + function.isSkipped = true; + function.isStub = false; continue; } @@ -239,6 +400,7 @@ namespace ps2recomp { ++failedCount; std::cerr << "Skipping function due decode failure: " << function.name << std::endl; + function.isSkipped = true; continue; } @@ -280,40 +442,19 @@ namespace ps2recomp std::string sanitized = sanitizeFunctionName(function.name); if (sanitized.empty()) { - std::stringstream ss; - ss << "func_" << std::hex << function.start; - sanitized = ss.str(); + sanitized = "func"; } - return sanitized; + std::stringstream ss; + ss << sanitized << "_0x" << std::hex << function.start; + return ss.str(); }; - std::unordered_map nameCounts; for (const auto &function : m_functions) { - if (!function.isRecompiled && !function.isStub) - continue; - std::string sanitized = makeName(function); - nameCounts[sanitized]++; - } - - for (const auto &function : m_functions) - { - if (!function.isRecompiled && !function.isStub) + if (!shouldGenerateCodeForFunction(function)) continue; - std::string sanitized = makeName(function); - bool isDuplicate = nameCounts[sanitized] > 1; - - std::stringstream ss; - if (isDuplicate) - { - ss << sanitized << "_0x" << std::hex << function.start; - } - else - { - ss << sanitized; - } - m_functionRenames[function.start] = ss.str(); + m_functionRenames[function.start] = makeName(function); } if (m_codeGenerator) @@ -345,24 +486,31 @@ namespace ps2recomp m_generatedStubs.clear(); for (const auto &function : m_functions) { - if (function.isStub) + if (function.isStub || function.isSkipped) { std::string generatedName = m_codeGenerator->getFunctionName(function.start); std::stringstream stub; stub << "void " << generatedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { "; - switch (resolveStubTarget(function.name)) + if (function.isSkipped) { - case StubTarget::Syscall: - stub << "ps2_syscalls::" << function.name << "(rdram, ctx, runtime); "; - break; - case StubTarget::Stub: - stub << "ps2_stubs::" << function.name << "(rdram, ctx, runtime); "; - break; - default: - stub << "ps2_stubs::TODO(rdram, ctx, runtime); "; - break; + stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(function.name) << "\", rdram, ctx, runtime); "; + } + else + { + switch (resolveStubTarget(function.name)) + { + case StubTarget::Syscall: + stub << "ps2_syscalls::" << function.name << "(rdram, ctx, runtime); "; + break; + case StubTarget::Stub: + stub << "ps2_stubs::" << function.name << "(rdram, ctx, runtime); "; + break; + default: + stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(function.name) << "\", rdram, ctx, runtime); "; + break; + } } stub << "}"; @@ -382,22 +530,18 @@ namespace ps2recomp combinedOutput << "#include \"ps2_recompiled_stubs.h\"\n"; combinedOutput << "#include \"ps2_syscalls.h\"\n"; combinedOutput << "#include \"ps2_stubs.h\"\n"; - if (m_bootstrapInfo.valid) - { - combinedOutput << "\n" - << m_codeGenerator->generateBootstrapFunction() << "\n\n"; - } + combinedOutput << "\n"; for (const auto &function : m_functions) { - if (!function.isRecompiled && !function.isStub) + if (!shouldGenerateCodeForFunction(function)) { continue; } try { - if (function.isStub) + if (function.isStub || function.isSkipped) { combinedOutput << m_generatedStubs.at(function.start) << "\n\n"; } @@ -419,25 +563,17 @@ namespace ps2recomp } fs::path outputPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.cpp"; - writeToFile(outputPath.string(), combinedOutput.str()); + if (!writeToFile(outputPath.string(), combinedOutput.str())) + { + throw std::runtime_error("Failed to write combined output: " + outputPath.string()); + } std::cout << "Wrote recompiled to combined output to: " << outputPath << std::endl; } else { - if (m_bootstrapInfo.valid) - { - std::stringstream boot; - boot << "#include \"ps2_recompiled_functions.h\"\n\n"; - boot << "#include \"ps2_runtime_macros.h\"\n"; - boot << "#include \"ps2_runtime.h\"\n\n"; - boot << m_codeGenerator->generateBootstrapFunction() << "\n"; - fs::path bootPath = fs::path(m_config.outputPath) / "ps2_entry_bootstrap.cpp"; - writeToFile(bootPath.string(), boot.str()); - } - for (const auto &function : m_functions) { - if (!function.isRecompiled && !function.isStub) + if (!shouldGenerateCodeForFunction(function)) { continue; } @@ -445,7 +581,7 @@ namespace ps2recomp std::string code; try { - if (function.isStub) + if (function.isStub || function.isSkipped) { std::stringstream stubFile; stubFile << "#include \"ps2_runtime.h\"\n"; @@ -471,7 +607,10 @@ namespace ps2recomp fs::path outputPath = getOutputPath(function); fs::create_directories(outputPath.parent_path()); - writeToFile(outputPath.string(), code); + if (!writeToFile(outputPath.string(), code)) + { + throw std::runtime_error("Failed to write function output: " + outputPath.string()); + } } std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl; @@ -480,7 +619,10 @@ namespace ps2recomp std::string registerFunctions = m_codeGenerator->generateFunctionRegistration(m_functions, m_generatedStubs); fs::path registerPath = fs::path(m_config.outputPath) / "register_functions.cpp"; - writeToFile(registerPath.string(), registerFunctions); + if (!writeToFile(registerPath.string(), registerFunctions)) + { + throw std::runtime_error("Failed to write function registration file: " + registerPath.string()); + } std::cout << "Generated function registration file: " << registerPath << std::endl; generateStubHeader(); @@ -505,12 +647,25 @@ namespace ps2recomp // ss << "namespace stubs {\n\n"; std::unordered_set stubNames; - stubNames.insert(m_config.skipFunctions.begin(), m_config.skipFunctions.end()); - stubNames.insert(m_config.stubImplementations.begin(), m_config.stubImplementations.end()); - - for (const auto &funcName : stubNames) + for (const auto &function : m_functions) { - ss << "void " << sanitizeFunctionName(funcName) << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n"; + if (!function.isStub && !function.isSkipped) + { + continue; + } + + const std::string generatedName = m_codeGenerator->getFunctionName(function.start); + if (generatedName.empty()) + { + continue; + } + + if (!stubNames.insert(generatedName).second) + { + continue; + } + + ss << "void " << generatedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n"; } // ss << "\n} // namespace stubs\n"; @@ -544,7 +699,7 @@ namespace ps2recomp for (const auto &function : m_functions) { - if (!function.isRecompiled && !function.isStub) + if (!shouldGenerateCodeForFunction(function)) { continue; } @@ -554,12 +709,6 @@ namespace ps2recomp ss << "void " << finalName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n"; } - if (m_bootstrapInfo.valid) - { - ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec - << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n"; - } - ss << "\n#endif // PS2_RECOMPILED_FUNCTIONS_H\n"; fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.h"; @@ -583,7 +732,7 @@ namespace ps2recomp existingStarts.insert(function.start); } - auto getStaticBranchTarget = [](const Instruction &inst) -> std::optional + auto getStaticEntryTarget = [](const Instruction &inst) -> std::optional { if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) { @@ -596,12 +745,6 @@ namespace ps2recomp return std::nullopt; } - if (inst.isBranch) - { - int32_t offset = static_cast(inst.simmediate) << 2; - return inst.address + 4 + offset; - } - return std::nullopt; }; @@ -621,7 +764,7 @@ namespace ps2recomp for (const auto &function : m_functions) { - if (!function.isRecompiled || function.isStub) + if (!function.isRecompiled || function.isStub || function.isSkipped) { continue; } @@ -636,7 +779,7 @@ namespace ps2recomp for (const auto &inst : instructions) { - auto targetOpt = getStaticBranchTarget(inst); + auto targetOpt = getStaticEntryTarget(inst); if (!targetOpt.has_value()) { continue; @@ -655,7 +798,13 @@ namespace ps2recomp } const Function *containingFunction = findContainingFunction(target); - if (!containingFunction || containingFunction->isStub || !containingFunction->isRecompiled) + if (!containingFunction || containingFunction->isStub || containingFunction->isSkipped || !containingFunction->isRecompiled) + { + continue; + } + + // Internal branches within the same function are handled as labels/gotos and should not produce separate entry wrappers. + if (containingFunction->start == function.start) { continue; } @@ -687,6 +836,7 @@ namespace ps2recomp entryFunction.end = containingFunction->end; entryFunction.isRecompiled = true; entryFunction.isStub = false; + entryFunction.isSkipped = false; newEntries.push_back(entryFunction); existingStarts.insert(target); @@ -727,25 +877,37 @@ namespace ps2recomp } uint32_t rawInstruction = m_elfParser->readWord(address); + const uint32_t originalInstruction = rawInstruction; auto patchIt = m_config.patches.find(address); if (patchIt != m_config.patches.end()) { - try + const PatchClass patchClass = classifyPatchedInstruction(originalInstruction); + if (shouldApplyConfiguredPatch(patchClass, m_config)) { - rawInstruction = std::stoul(patchIt->second, nullptr, 0); - std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl; - } - catch (const std::exception &e) - { - std::cerr << "Invalid patch value at 0x" << std::hex << address << std::dec - << " (" << patchIt->second << "): " << e.what() - << ". Using original instruction." << std::endl; + try + { + rawInstruction = std::stoul(patchIt->second, nullptr, 0); + std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl; + } + catch (const std::exception &e) + { + std::cerr << "Invalid patch value at 0x" << std::hex << address << std::dec + << " (" << patchIt->second << "): " << e.what() + << ". Using original instruction." << std::endl; + } } } Instruction inst = m_decoder->decodeInstruction(address, rawInstruction); + auto mmioIt = m_config.mmioByInstructionAddress.find(address); + if (mmioIt != m_config.mmioByInstructionAddress.end()) + { + inst.isMmio = true; + inst.mmioAddress = mmioIt->second; + } + instructions.push_back(inst); } catch (const std::exception &e) @@ -775,18 +937,28 @@ namespace ps2recomp return true; } - bool PS2Recompiler::shouldSkipFunction(const std::string &name) const + bool PS2Recompiler::shouldSkipFunction(const Function &function) const { - return m_skipFunctions.contains(name); - } - - bool PS2Recompiler::isStubFunction(const std::string &name) const - { - if (m_stubFunctions.contains(name)) + if (m_skipFunctionStarts.contains(function.start)) { return true; } - return ps2_runtime_calls::isStubName(name); + + return m_skipFunctions.contains(function.name); + } + + bool PS2Recompiler::isStubFunction(const Function &function) const + { + if (m_stubFunctionStarts.contains(function.start)) + { + return true; + } + + if (m_stubFunctions.contains(function.name)) + { + return true; + } + return ps2_runtime_calls::isStubName(function.name); } bool PS2Recompiler::writeToFile(const std::string &path, const std::string &content) @@ -864,4 +1036,17 @@ namespace ps2recomp return sanitized; } + + StubTarget PS2Recompiler::resolveStubTarget(const std::string &name) + { + if (ps2_runtime_calls::isSyscallName(name)) + { + return StubTarget::Syscall; + } + if (ps2_runtime_calls::isStubName(name)) + { + return StubTarget::Stub; + } + return StubTarget::Unknown; + } } diff --git a/ps2xRecomp/src/lib/r5900_decoder.cpp b/ps2xRecomp/src/lib/r5900_decoder.cpp index f68b576..bc6f968 100644 --- a/ps2xRecomp/src/lib/r5900_decoder.cpp +++ b/ps2xRecomp/src/lib/r5900_decoder.cpp @@ -14,7 +14,7 @@ namespace ps2recomp } Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction) const - { + { Instruction inst; inst.address = address; @@ -39,12 +39,14 @@ namespace ps2recomp inst.isMultimedia = false; inst.isLoad = false; inst.isStore = false; + inst.isMmio = false; // Initialize the enhanced fields inst.mmiType = 0; inst.mmiFunction = 0; inst.pmfhlVariation = 0; inst.vuFunction = 0; + inst.mmioAddress = 0; inst.vectorInfo.isVector = false; inst.vectorInfo.usesQReg = false; diff --git a/ps2xRuntime/include/ps2_call_list.h b/ps2xRuntime/include/ps2_call_list.h index d6e71ab..7c17e5e 100644 --- a/ps2xRuntime/include/ps2_call_list.h +++ b/ps2xRuntime/include/ps2_call_list.h @@ -111,6 +111,9 @@ X(_printf) \ X(_printf_r) \ X(abs) \ + X(__ieee754_rem_pio2f) \ + X(__kernel_cosf) \ + X(__kernel_sinf) \ X(atan) \ X(atan2) \ X(calloc) \ diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 4c3d82e..c5eeacc 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -15,53 +15,67 @@ #include // For SSE4.1 instructions #endif #include +#include #include #include +#include -constexpr uint32_t PS2_RAM_SIZE = 32 * 1024 * 1024; // 32MB -constexpr uint32_t PS2_RAM_MASK = 0x1FFFFFF; // Mask for 32MB alignment -constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM +constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB +constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment +constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000; -constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16 * 1024; // 16KB -constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC) -constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB -constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG -constexpr uint32_t PS2_BIOS_SIZE = 4 * 1024 * 1024; // 4MB +constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB +constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC) +constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB +constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG +constexpr uint32_t PS2_BIOS_SIZE = 4u * 1024u * 1024u; // 4MB constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; // Base address as seen from EE constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000; -constexpr uint32_t PS2_VU0_CODE_SIZE = 4 * 1024; // 4KB Micro Memory -constexpr uint32_t PS2_VU0_DATA_SIZE = 4 * 1024; // 4KB Data Memory (VU Mem) +constexpr uint32_t PS2_VU0_CODE_SIZE = 4u * 1024u; // 4KB Micro Memory +constexpr uint32_t PS2_VU0_DATA_SIZE = 4u * 1024u; // 4KB Data Memory (VU Mem) -constexpr uint32_t PS2_VU1_MEM_BASE = 0x11008000; // Base address as seen from EE -constexpr uint32_t PS2_VU1_CODE_SIZE = 16 * 1024; // 16KB Micro Memory -constexpr uint32_t PS2_VU1_DATA_SIZE = 16 * 1024; // 16KB Data Memory (VU Mem) constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000; constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000; +constexpr uint32_t PS2_VU1_MEM_BASE = PS2_VU1_CODE_BASE; // Alias used by older code paths +constexpr uint32_t PS2_VU1_CODE_SIZE = 16u * 1024u; // 16KB Micro Memory +constexpr uint32_t PS2_VU1_DATA_SIZE = 16u * 1024u; // 16KB Data Memory (VU Mem) constexpr uint32_t PS2_GS_BASE = 0x12000000; -constexpr uint32_t PS2_GS_PRIV_REG_BASE = 0x12000000; // GS Privileged Registers +constexpr uint32_t PS2_GS_PRIV_REG_BASE = PS2_GS_BASE; // GS Privileged Registers constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000; -constexpr size_t PS2_GS_VRAM_SIZE = 4 * 1024 * 1024; // 4MB GS VRAM +constexpr size_t PS2_GS_VRAM_SIZE = 4u * 1024u * 1024u; // 4MB GS VRAM -#define PS2_FIO_O_RDONLY 0x0001 -#define PS2_FIO_O_WRONLY 0x0002 -#define PS2_FIO_O_RDWR 0x0003 -#define PS2_FIO_O_APPEND 0x0100 -#define PS2_FIO_O_CREAT 0x0200 -#define PS2_FIO_O_TRUNC 0x0400 -#define PS2_FIO_O_EXCL 0x0800 +inline constexpr uint32_t PS2_FIO_O_RDONLY = 0x0001; +inline constexpr uint32_t PS2_FIO_O_WRONLY = 0x0002; +inline constexpr uint32_t PS2_FIO_O_RDWR = 0x0003; +inline constexpr uint32_t PS2_FIO_O_NBLOCK = 0x0010; +inline constexpr uint32_t PS2_FIO_O_APPEND = 0x0100; +inline constexpr uint32_t PS2_FIO_O_CREAT = 0x0200; +inline constexpr uint32_t PS2_FIO_O_TRUNC = 0x0400; +inline constexpr uint32_t PS2_FIO_O_EXCL = 0x0800; +inline constexpr uint32_t PS2_FIO_O_NOWAIT = 0x8000; -#define PS2_FIO_SEEK_SET 0 -#define PS2_FIO_SEEK_CUR 1 -#define PS2_FIO_SEEK_END 2 +inline constexpr uint32_t PS2_FIO_SEEK_SET = 0; +inline constexpr uint32_t PS2_FIO_SEEK_CUR = 1; +inline constexpr uint32_t PS2_FIO_SEEK_END = 2; -#define PS2_FIO_S_IFDIR 0x1000 -#define PS2_FIO_S_IFREG 0x2000 +inline constexpr uint32_t PS2_FIO_S_IFDIR = 0x1000; +inline constexpr uint32_t PS2_FIO_S_IFREG = 0x2000; + +static_assert((PS2_RAM_SIZE & (PS2_RAM_SIZE - 1u)) == 0u, "PS2_RAM_SIZE must be a power of two"); +static_assert(PS2_RAM_MASK == (PS2_RAM_SIZE - 1u), "PS2_RAM_MASK must match PS2_RAM_SIZE"); enum PS2Exception { + EXCEPTION_TLB_REFILL = 0x02, // TLB refill/load exception + EXCEPTION_ADDRESS_ERROR_LOAD = 0x04, // Address error on load + EXCEPTION_ADDRESS_ERROR_STORE = 0x05, // Address error on store + EXCEPTION_SYSCALL = 0x08, // SYSCALL instruction + EXCEPTION_BREAKPOINT = 0x09, // BREAK instruction + EXCEPTION_RESERVED_INSTRUCTION = 0x0A, EXCEPTION_INTEGER_OVERFLOW = 0x0C, // From MIPS spec + EXCEPTION_TRAP = 0x0D, // Trap instruction condition met }; // PS2 CPU context (R5900) @@ -106,7 +120,7 @@ struct alignas(16) R5900Context uint32_t vu0_itop; uint32_t vu0_info; uint32_t vu0_xitop; // VU0 XITOP - input ITOP for VIF/VU sync - uint32_t vu0_pc; + uint32_t vu0_pc; float vu0_cf[4]; // VU0 FMAC control floating-point registers @@ -134,6 +148,10 @@ struct alignas(16) R5900Context uint32_t cop0_taghi; uint32_t cop0_errorepc; + // LL/SC reservation state (not part of COP0 Status bits). + uint32_t llbit; + uint32_t lladdr; + // COP2 control registers (VU0 integer + control) uint32_t cop2_ccr[32]; @@ -143,81 +161,18 @@ struct alignas(16) R5900Context R5900Context() { - for (int i = 0; i < 32; i++) - { - r[i] = _mm_setzero_si128(); - f[i] = 0.0f; - vu0_vf[i] = _mm_setzero_ps(); - } - - for (int i = 0; i < 4; i++) - { - vu0_cf[i] = 0.0f; - } - - for (int i = 0; i < 16; ++i) - { - vi[i] = 0; - } - - pc = 0; - insn_count = 0; - lo = hi = lo1 = hi1 = 0; - sa = 0; + std::memset(this, 0, sizeof(*this)); // Initialize VU0 registers vu0_q = 1.0f; // Q register usually initialized to 1.0 - vu0_p = 0.0f; - vu0_i = 0.0f; - vu0_r = _mm_setzero_ps(); - vu0_acc = _mm_setzero_ps(); - vu0_status = 0; - vu0_mac_flags = 0; - vu0_clip_flags = 0; - vu0_cmsar0 = 0; - vu0_fbrst = 0; - vu0_fbrst2 = 0; - vu0_fbrst3 = 0; - vu0_fbrst4 = 0; - vu0_xitop = 0; - vu0_pc = 0; - vu0_tpc = 0; - vu0_vpu_stat2 = 0; - vu0_tpc2 = 0; - vu0_cmsar1 = 0; - vu0_vpu_stat3 = 0; - vu0_cmsar2 = 0; - vu0_vpu_stat4 = 0; - vu0_itop = 0; - vu0_info = 0; - // Reset COP0 registers - cop0_index = 0; cop0_random = 47; // Start at maximum value - cop0_entrylo0 = 0; - cop0_entrylo1 = 0; - cop0_context = 0; - cop0_pagemask = 0; - cop0_wired = 0; - cop0_badvaddr = 0; - cop0_count = 0; - cop0_entryhi = 0; - cop0_compare = 0; - cop0_status = 0x400000; // BEV set, ERL clear, kernel mode - cop0_cause = 0; - cop0_epc = 0; + // cop0_status = 0x400000; // BEV set, ERL clear, kernel mode + // 0x00400000 = BEV (Boot Exception Vectors). + // 0x00000000 = Normal mode (after BIOS handoff). + cop0_status = 0x00000000; cop0_prid = 0x00002e20; // CPU ID for R5900 - cop0_config = 0; - cop0_badpaddr = 0; - cop0_debug = 0; - cop0_perf = 0; - cop0_taglo = 0; - cop0_taghi = 0; - cop0_errorepc = 0; - - // Reset COP1 state - fcr31 = 0; } void dump() const @@ -233,9 +188,9 @@ struct alignas(16) R5900Context { std::cout << "R" << std::setw(2) << std::dec << i << ": 0x" << std::hex << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 3)) - << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 2)) << "_" + << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 2)) << "_" << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 1)) - << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 0)) << "\n"; + << std::setw(8) << static_cast(_mm_extract_epi32(r[i], 0)) << "\n"; } std::cout << "Status: 0x" << std::setw(8) << cop0_status << " Cause: 0x" << std::setw(8) << cop0_cause @@ -252,36 +207,276 @@ inline uint32_t getRegU32(const R5900Context *ctx, int reg) // Check if reg is valid (0-31) if (reg < 0 || reg > 31) return 0; + if (reg == 0) + return 0; return static_cast(_mm_extract_epi32(ctx->r[reg], 0)); } inline void setReturnU32(R5900Context *ctx, uint32_t value) { - ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0 + // Keep low 64-bits coherent for helpers that read GPRs as 64-bit. + ctx->r[2] = _mm_set_epi64x(0, static_cast(value)); // $v0 } inline void setReturnS32(R5900Context *ctx, int32_t value) { - ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0 Sign extension handled by cast? TODO Check MIPS ABI. + // Signed 32-bit return should be sign-extended when observed as 64-bit. + ctx->r[2] = _mm_set_epi64x(0, static_cast(value)); // $v0 } inline void setReturnU64(R5900Context *ctx, uint64_t value) { - // 64-bit returns use $v0/$v1 (r2/r3) - ctx->r[2] = _mm_set_epi32(0, 0, 0, static_cast(value)); - ctx->r[3] = _mm_set_epi32(0, 0, 0, static_cast(value >> 32)); + // Keep both conventions: full 64-bit value in $v0 and high 32-bit in $v1. + ctx->r[2] = _mm_set_epi64x(0, static_cast(value)); + ctx->r[3] = _mm_set_epi64x(0, static_cast(static_cast(value >> 32))); } +inline constexpr uint32_t PS2_PATH_WATCH_ADDR = 0x00369F2Fu; +inline constexpr uint32_t PS2_PATH_WATCH_BYTES = 32u; +inline constexpr uint32_t PS2_PATH_WATCH_MAX_LOGS = 512u; +inline std::atomic g_ps2PathWatchLogCount{0}; + +inline uint32_t ps2PathWatchPhysAddr() +{ + return PS2_PATH_WATCH_ADDR & PS2_RAM_MASK; +} + +inline bool ps2PathWatchIntersects(uint32_t writeAddr, uint32_t writeSize) +{ + const uint64_t writeStart = writeAddr; + const uint64_t writeEnd = writeStart + static_cast(writeSize); + const uint64_t watchStart = ps2PathWatchPhysAddr(); + const uint64_t watchEnd = watchStart + static_cast(PS2_PATH_WATCH_BYTES); + return writeEnd > watchStart && writeStart < watchEnd; +} + +inline void ps2PathWatchDumpPrefix(const uint8_t *rdram) +{ + if (!rdram) + { + return; + } + + const uint32_t base = ps2PathWatchPhysAddr(); + auto flags = std::cout.flags(); + std::cout << " buf=" << std::hex; + for (uint32_t i = 0; i < 16u; ++i) + { + const uint32_t addr = (base + i) & PS2_RAM_MASK; + std::cout << static_cast(rdram[addr]); + if (i + 1u < 16u) + { + std::cout << '.'; + } + } + std::cout.flags(flags); +} + +inline uint8_t ps2PathWatchExtractByteFromWrite(uint32_t writeAddr, uint32_t watchAddr, uint64_t valueLo, uint64_t valueHi) +{ + const uint32_t byteIndex = watchAddr - writeAddr; + if (byteIndex < 8u) + { + return static_cast((valueLo >> (byteIndex * 8u)) & 0xFFu); + } + return static_cast((valueHi >> ((byteIndex - 8u) * 8u)) & 0xFFu); +} + +inline void ps2TraceGuestWrite(uint8_t *rdram, + uint32_t guestAddr, + uint32_t size, + uint64_t valueLo, + uint64_t valueHi, + const char *op, + const R5900Context *ctx) +{ + if (!rdram || size == 0u) + { + return; + } + + const uint32_t writeAddr = guestAddr & PS2_RAM_MASK; + if (!ps2PathWatchIntersects(writeAddr, size)) + { + return; + } + + const uint32_t logIndex = g_ps2PathWatchLogCount.fetch_add(1, std::memory_order_relaxed); + if (logIndex >= PS2_PATH_WATCH_MAX_LOGS) + { + return; + } + + const uint32_t watchAddr = ps2PathWatchPhysAddr(); + const bool touchesFirstByte = (watchAddr >= writeAddr) && (watchAddr < writeAddr + size); + const uint8_t oldByte = rdram[watchAddr]; + const uint8_t newByte = touchesFirstByte ? ps2PathWatchExtractByteFromWrite(writeAddr, watchAddr, valueLo, valueHi) : oldByte; + + const uint32_t pc = ctx ? ctx->pc : 0u; + const uint32_t ra = ctx ? static_cast(_mm_extract_epi32(ctx->r[31], 0)) : 0u; + const uint32_t sp = ctx ? static_cast(_mm_extract_epi32(ctx->r[29], 0)) : 0u; + + auto flags = std::cout.flags(); + std::cout << "[watch:path-write] #" << (logIndex + 1u) + << " op=" << op + << " addr=0x" << std::hex << writeAddr + << " size=0x" << size + << " pc=0x" << pc + << " ra=0x" << ra + << " sp=0x" << sp + << " vLo=0x" << valueLo; + if (size > 8u) + { + std::cout << " vHi=0x" << valueHi; + } + if (touchesFirstByte) + { + std::cout << " firstByte:" << static_cast(oldByte) + << "->" << static_cast(newByte); + if (oldByte != 0u && newByte == 0u) + { + std::cout << " (ZEROED)"; + } + } + ps2PathWatchDumpPrefix(rdram); + std::cout.flags(flags); + std::cout << std::endl; +} + +inline void ps2TraceGuestRangeWrite(uint8_t *rdram, + uint32_t guestAddr, + uint32_t size, + const char *op, + const R5900Context *ctx) +{ + if (!rdram || size == 0u) + { + return; + } + + const uint32_t writeAddr = guestAddr & PS2_RAM_MASK; + if (!ps2PathWatchIntersects(writeAddr, size)) + { + return; + } + + const uint32_t logIndex = g_ps2PathWatchLogCount.fetch_add(1, std::memory_order_relaxed); + if (logIndex >= PS2_PATH_WATCH_MAX_LOGS) + { + return; + } + + const uint32_t pc = ctx ? ctx->pc : 0u; + const uint32_t ra = ctx ? static_cast(_mm_extract_epi32(ctx->r[31], 0)) : 0u; + const uint32_t sp = ctx ? static_cast(_mm_extract_epi32(ctx->r[29], 0)) : 0u; + const uint8_t firstByte = rdram[ps2PathWatchPhysAddr()]; + + auto flags = std::cout.flags(); + std::cout << "[watch:path-range] #" << (logIndex + 1u) + << " op=" << op + << " addr=0x" << std::hex << writeAddr + << " size=0x" << size + << " pc=0x" << pc + << " ra=0x" << ra + << " sp=0x" << sp + << " firstByte=" << static_cast(firstByte); + ps2PathWatchDumpPrefix(rdram); + std::cout.flags(flags); + std::cout << std::endl; +} + +inline std::atomic &ps2ScratchpadHostPtrStorage() +{ + static std::atomic ptr{nullptr}; + return ptr; +} + +inline void ps2SetScratchpadHostPtr(uint8_t *ptr) +{ + ps2ScratchpadHostPtrStorage().store(ptr, std::memory_order_relaxed); +} + +inline uint8_t *ps2GetScratchpadHostPtr() +{ + return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed); +} + +inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch) +{ + if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) + { + scratch = true; + offset = addr - PS2_SCRATCHPAD_BASE; + return true; + } + + uint32_t phys = 0; + if (addr < 0x20000000u) + { + phys = addr; + } + else if ((addr >= 0x20000000u && addr < 0x40000000u) || + (addr >= 0x80000000u && addr < 0xC0000000u)) + { + phys = addr & 0x1FFFFFFFu; + } + else + { + // Keep legacy runtime behavior for odd upper-bit aliases used by game code. + phys = addr & PS2_RAM_MASK; + } + + if (phys >= PS2_RAM_SIZE) + { + phys &= PS2_RAM_MASK; + } + + scratch = false; + offset = phys; + return true; +} inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr) { - constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1; - return rdram + (addr & PS2_RAM_MASK); + if (rdram == nullptr) + { + return nullptr; + } + + uint32_t offset = 0; + bool scratch = false; + if (!ps2ResolveGuestPointer(addr, offset, scratch)) + { + return nullptr; + } + + if (scratch) + { + uint8_t *scratchpad = ps2GetScratchpadHostPtr(); + return scratchpad ? (scratchpad + offset) : nullptr; + } + return rdram + offset; } -inline const uint8_t *getConstMemPtr(uint8_t *rdram, uint32_t addr) +inline const uint8_t *getConstMemPtr(const uint8_t *rdram, uint32_t addr) { - constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1; - return rdram + (addr & PS2_RAM_MASK); + if (rdram == nullptr) + { + return nullptr; + } + + uint32_t offset = 0; + bool scratch = false; + if (!ps2ResolveGuestPointer(addr, offset, scratch)) + { + return nullptr; + } + + if (scratch) + { + const uint8_t *scratchpad = ps2GetScratchpadHostPtr(); + return scratchpad ? (scratchpad + offset) : nullptr; + } + return rdram + offset; } // PS2 GS (Graphics Synthesizer) registers @@ -307,6 +502,8 @@ struct GSRegisters uint64_t busdir; // Bus direction uint64_t siglblid; // Signal label ID }; +static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly"); +static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit"); // PS2 VIF (VPU Interface) registers struct VIFRegisters @@ -329,6 +526,7 @@ struct VIFRegisters uint32_t row[4]; // Transfer row data uint32_t col[4]; // Transfer column data }; +static_assert(sizeof(VIFRegisters) == (23u * sizeof(uint32_t)), "VIFRegisters layout changed unexpectedly"); // PS2 DMA registers struct DMARegisters @@ -341,11 +539,12 @@ struct DMARegisters uint32_t asr1; // Address stack 1 uint32_t sadr; // Source address }; +static_assert(sizeof(DMARegisters) == (7u * sizeof(uint32_t)), "DMARegisters layout changed unexpectedly"); struct JumpTable { - uint32_t address; // Base address of the jump table - uint32_t baseRegister; // Register used for index + uint32_t address = 0; // Base address of the jump table + uint32_t baseRegister = 0; // Register used for index std::vector targets; // Jump targets }; @@ -355,6 +554,11 @@ public: PS2Memory(); ~PS2Memory(); + PS2Memory(const PS2Memory &) = delete; + PS2Memory &operator=(const PS2Memory &) = delete; + PS2Memory(PS2Memory &&) = delete; + PS2Memory &operator=(PS2Memory &&) = delete; + // Initialize memory bool initialize(size_t ramSize = PS2_RAM_SIZE); @@ -382,6 +586,10 @@ public: // TLB handling uint32_t translateAddress(uint32_t virtualAddress); + bool tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const; + bool tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid); + int32_t tlbProbe(uint32_t vpn) const; + size_t tlbEntryCount() const { return m_tlbEntries.size(); } // Hardware register interface bool writeIORegister(uint32_t address, uint32_t value); @@ -449,6 +657,15 @@ public: class PS2Runtime { public: + struct IoPaths + { + std::filesystem::path elfPath; + std::filesystem::path elfDirectory; + std::filesystem::path hostRoot; + std::filesystem::path cdRoot; + std::filesystem::path cdImage; + }; + PS2Runtime(); ~PS2Runtime(); @@ -462,6 +679,10 @@ public: RecompiledFunction lookupFunction(uint32_t address); bool hasFunction(uint32_t address) const; + static const IoPaths &getIoPaths(); + static void setIoPaths(const IoPaths &paths); + static void configureIoPathsFromElf(const std::string &elfPath); + void SignalException(R5900Context *ctx, PS2Exception exception); void executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address); @@ -469,6 +690,7 @@ public: public: void handleSyscall(uint8_t *rdram, R5900Context *ctx); + void handleSyscall(uint8_t *rdram, R5900Context *ctx, uint32_t encodedSyscallId); void handleBreak(uint8_t *rdram, R5900Context *ctx); void handleTrap(uint8_t *rdram, R5900Context *ctx); @@ -477,6 +699,60 @@ public: void handleTLBWR(uint8_t *rdram, R5900Context *ctx); void handleTLBP(uint8_t *rdram, R5900Context *ctx); void clearLLBit(R5900Context *ctx); + void configureGuestHeap(uint32_t guestBase, uint32_t guestLimit = PS2_RAM_SIZE); + uint32_t guestMalloc(uint32_t size, uint32_t alignment = 16u); + uint32_t guestCalloc(uint32_t count, uint32_t size, uint32_t alignment = 16u); + uint32_t guestRealloc(uint32_t guestAddr, uint32_t newSize, uint32_t alignment = 16u); + void guestFree(uint32_t guestAddr); + uint32_t guestHeapBase() const; + uint32_t guestHeapEnd() const; + void dispatchLoop(uint8_t *rdram, R5900Context *ctx); + void requestStop(); + bool isStopRequested() const; + + uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); + uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); + uint32_t Load32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); + uint64_t Load64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); + __m128i Load128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); + + void Store8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint8_t value); + void Store16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint16_t value); + void Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value); + void Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value); + void Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value); + + static inline bool isSpecialAddress(uint32_t addr) + { + // BIOS (physical + cached/uncached aliases) + if ((addr >= PS2_BIOS_BASE && addr < (PS2_BIOS_BASE + PS2_BIOS_SIZE)) || + (addr >= 0xBFC00000u && addr < (0xBFC00000u + PS2_BIOS_SIZE))) + { + return true; + } + + // Scratchpad (16KB) + if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) + return true; + + // EE MMIO window (Timers, DMAC, INTC, etc) + if (addr >= PS2_IO_BASE && addr < (PS2_IO_BASE + PS2_IO_SIZE)) + return true; + + // GS privileged regs + if (addr >= PS2_GS_PRIV_REG_BASE && addr < (PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE)) + return true; + + // KSEG2/KSEG3 (TLB mapped) + if (addr >= 0xC0000000u) + return true; + + // VU Memory (Micro/Data) mapped into EE space + if (addr >= PS2_VU0_CODE_BASE && addr < (PS2_VU1_DATA_BASE + PS2_VU1_DATA_SIZE)) + return true; + + return false; + } public: inline R5900Context &cpu() { return m_cpuContext; } @@ -485,17 +761,47 @@ public: inline PS2Memory &memory() { return m_memory; } inline const PS2Memory &memory() const { return m_memory; } -public: - bool check_overflow = false; - private: + struct GuestHeapBlock + { + uint32_t addr = 0; + uint32_t size = 0; + bool free = true; + }; + + static uint32_t alignGuestHeapValue(uint32_t value, uint32_t alignment); + static bool isGuestHeapAlignmentValid(uint32_t alignment); + static uint32_t normalizeGuestHeapAlignment(uint32_t alignment); + uint32_t clampGuestHeapBase(uint32_t guestBase) const; + uint32_t clampGuestHeapLimit(uint32_t guestLimit) const; + void resetGuestHeapLocked(uint32_t guestBase, uint32_t guestLimit); + void ensureGuestHeapInitializedLocked(); + int32_t findGuestHeapBlockIndexLocked(uint32_t guestAddr) const; + uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment); + void freeGuestBlockLocked(uint32_t guestAddr); + void coalesceGuestHeapLocked(); + void HandleIntegerOverflow(R5900Context *ctx); private: PS2Memory m_memory; R5900Context m_cpuContext; + mutable std::mutex m_guestHeapMutex; + std::vector m_guestHeapBlocks; + uint32_t m_guestHeapBase = 0x00100000u; + uint32_t m_guestHeapEnd = 0x00100000u; + uint32_t m_guestHeapLimit = PS2_RAM_SIZE; + uint32_t m_guestHeapSuggestedBase = 0x00100000u; + bool m_guestHeapConfigured = false; std::unordered_map m_functionTable; + std::atomic m_stopRequested{false}; + + // TODO remove this later + std::atomic m_debugPc{0}; + std::atomic m_debugRa{0}; + std::atomic m_debugSp{0}; + std::atomic m_debugGp{0}; struct LoadedModule { diff --git a/ps2xRuntime/include/ps2_runtime_macros.h b/ps2xRuntime/include/ps2_runtime_macros.h index 8f33073..a6e10b4 100644 --- a/ps2xRuntime/include/ps2_runtime_macros.h +++ b/ps2xRuntime/include/ps2_runtime_macros.h @@ -1,44 +1,85 @@ #ifndef PS2_RUNTIME_MACROS_H #define PS2_RUNTIME_MACROS_H #include +#include #if defined(_MSC_VER) - #include +#include #elif defined(USE_SSE2NEON) - #include "sse2neon.h" +#include "sse2neon.h" #else - #include // For SSE/AVX intrinsics +#include // For SSE/AVX intrinsics #endif -inline uint32_t ps2_clz32(uint32_t val) { -#if defined(_MSC_VER) - unsigned long idx; - if (_BitScanReverse(&idx, val)) { - return 31u - idx; + +#include "ps2_runtime.h" + +static inline int32_t Ps2ExtractEpi32(__m128i v, int index) +{ + switch (index & 3) + { + case 0: + return _mm_extract_epi32(v, 0); + case 1: + return _mm_extract_epi32(v, 1); + case 2: + return _mm_extract_epi32(v, 2); + default: + return _mm_extract_epi32(v, 3); } - return 32u; -#else - return val == 0 ? 32u : (uint32_t)__builtin_clz(val); -#endif } +static inline int64_t Ps2ExtractEpi64(__m128i v, int index) +{ + if ((index & 1) == 0) + { + return _mm_cvtsi128_si64(v); + } + else + { + return _mm_extract_epi64(v, 1); + } +} + +static inline uint32_t ps2_clz32(uint32_t x) +{ + return static_cast(std::countl_zero(x)); +} + +#define PS2_BLENDV_PS(a, b, mask) _mm_blendv_ps((a), (b), (mask)) +#define PS2_MIN_EPI32(a, b) _mm_min_epi32((a), (b)) +#define PS2_MAX_EPI32(a, b) _mm_max_epi32((a), (b)) + +#define PS2_EXTRACT_EPI32(v, i) Ps2ExtractEpi32((v), (i)) +#define PS2_EXTRACT_EPI64(v, i) Ps2ExtractEpi64((v), (i)) + +#define PS2_EXTRACT_EPI32_0(v) Ps2ExtractEpi32((v), 0) +#define PS2_EXTRACT_EPI32_1(v) Ps2ExtractEpi32((v), 1) +#define PS2_EXTRACT_EPI32_2(v) Ps2ExtractEpi32((v), 2) +#define PS2_EXTRACT_EPI32_3(v) Ps2ExtractEpi32((v), 3) + +#define PS2_EXTRACT_EPI64_0(v) Ps2ExtractEpi64((v), 0) +#define PS2_EXTRACT_EPI64_1(v) Ps2ExtractEpi64((v), 1) + // Basic MIPS arithmetic operations #define ADD32(a, b) ((uint32_t)((a) + (b))) -#define ADD32_OV(rs, rt, result32, overflow) \ - do { \ - int32_t _a = (int32_t)(rs); \ - int32_t _b = (int32_t)(rt); \ - int32_t _r = _a + _b; \ - overflow = (((_a ^ _b) >= 0) && ((_a ^ _r) < 0)); \ - result32 = (uint32_t)_r; \ - } while (0); +#define ADD32_OV(rs, rt, result32, overflow) \ + do \ + { \ + int32_t _a = (int32_t)(rs); \ + int32_t _b = (int32_t)(rt); \ + int32_t _r = _a + _b; \ + overflow = (((_a ^ _b) >= 0) && ((_a ^ _r) < 0)); \ + result32 = (uint32_t)_r; \ + } while (0); #define SUB32(a, b) ((uint32_t)((a) - (b))) -#define SUB32_OV(rs, rt, result32, overflow) \ - do { \ - int32_t _a = (int32_t)(rs); \ - int32_t _b = (int32_t)(rt); \ - int32_t _r = _a - _b; \ - overflow = (((_a ^ _b) < 0) && ((_a ^ _r) < 0)); \ - result32 = (uint32_t)_r; \ - } while (0); +#define SUB32_OV(rs, rt, result32, overflow) \ + do \ + { \ + int32_t _a = (int32_t)(rs); \ + int32_t _b = (int32_t)(rt); \ + int32_t _r = _a - _b; \ + overflow = (((_a ^ _b) < 0) && ((_a ^ _r) < 0)); \ + result32 = (uint32_t)_r; \ + } while (0); #define MUL32(a, b) ((uint32_t)((a) * (b))) #define DIV32(a, b) ((uint32_t)((a) / (b))) #define AND32(a, b) ((uint32_t)((a) & (b))) @@ -60,8 +101,8 @@ inline uint32_t ps2_clz32(uint32_t val) { #define PS2_PEXTUB(a, b) _mm_unpackhi_epi8((__m128i)(b), (__m128i)(a)) #define PS2_PADDW(a, b) _mm_add_epi32((__m128i)(a), (__m128i)(b)) #define PS2_PSUBW(a, b) _mm_sub_epi32((__m128i)(a), (__m128i)(b)) -#define PS2_PMAXW(a, b) _mm_max_epi32((__m128i)(a), (__m128i)(b)) -#define PS2_PMINW(a, b) _mm_min_epi32((__m128i)(a), (__m128i)(b)) +#define PS2_PMAXW(a, b) PS2_MAX_EPI32((__m128i)(a), (__m128i)(b)) +#define PS2_PMINW(a, b) PS2_MIN_EPI32((__m128i)(a), (__m128i)(b)) #define PS2_PADDH(a, b) _mm_add_epi16((__m128i)(a), (__m128i)(b)) #define PS2_PSUBH(a, b) _mm_sub_epi16((__m128i)(a), (__m128i)(b)) #define PS2_PMAXH(a, b) _mm_max_epi16((__m128i)(a), (__m128i)(b)) @@ -79,18 +120,175 @@ inline uint32_t ps2_clz32(uint32_t val) { #define PS2_VMUL(a, b) _mm_mul_ps((__m128)(a), (__m128)(b)) #define PS2_VDIV(a, b) _mm_div_ps((__m128)(a), (__m128)(b)) #define PS2_VMULQ(a, q) _mm_mul_ps((__m128)(a), _mm_set1_ps(q)) +#define PS2_VBLEND(a, b, mask) PS2_BLENDV_PS((__m128)(a), (__m128)(b), (__m128)(mask)) -// Memory access helpers -#define READ8(addr) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK))) -#define READ16(addr) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK))) -#define READ32(addr) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK))) -#define READ64(addr) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK))) -#define READ128(addr) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK)))) -#define WRITE8(addr, val) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) -#define WRITE16(addr, val) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) -#define WRITE32(addr, val) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) -#define WRITE64(addr, val) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val)) -#define WRITE128(addr, val) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK))) = (val)) +// Memory access helpers - Hybrid Fast/Slow Path +// Fast path: Direct RDRAM access (masked). +// Slow path: Full runtime->Load/Store + +static inline uint8_t Ps2FastRead8(const uint8_t *rdram, uint32_t addr) +{ + return rdram[addr & PS2_RAM_MASK]; +} + +static inline uint16_t Ps2FastRead16(const uint8_t *rdram, uint32_t addr) +{ + uint16_t value; + std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value)); + return value; +} + +static inline uint32_t Ps2FastRead32(const uint8_t *rdram, uint32_t addr) +{ + uint32_t value; + std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value)); + return value; +} + +static inline uint64_t Ps2FastRead64(const uint8_t *rdram, uint32_t addr) +{ + uint64_t value; + std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value)); + return value; +} + +static inline __m128i Ps2FastRead128(const uint8_t *rdram, uint32_t addr) +{ + __m128i value; + std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value)); + return value; +} + +static inline void Ps2FastWrite8(uint8_t *rdram, uint32_t addr, uint8_t value) +{ + rdram[addr & PS2_RAM_MASK] = value; +} + +static inline void Ps2FastWrite16(uint8_t *rdram, uint32_t addr, uint16_t value) +{ + std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value)); +} + +static inline void Ps2FastWrite32(uint8_t *rdram, uint32_t addr, uint32_t value) +{ + std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value)); +} + +static inline void Ps2FastWrite64(uint8_t *rdram, uint32_t addr, uint64_t value) +{ + std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value)); +} + +static inline void Ps2FastWrite128(uint8_t *rdram, uint32_t addr, __m128i value) +{ + std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value)); +} + +#define FAST_READ8(addr) Ps2FastRead8(rdram, (uint32_t)(addr)) +#define FAST_READ16(addr) Ps2FastRead16(rdram, (uint32_t)(addr)) +#define FAST_READ32(addr) Ps2FastRead32(rdram, (uint32_t)(addr)) +#define FAST_READ64(addr) Ps2FastRead64(rdram, (uint32_t)(addr)) +#define FAST_READ128(addr) Ps2FastRead128(rdram, (uint32_t)(addr)) + +#define FAST_WRITE8(addr, val) Ps2FastWrite8(rdram, (uint32_t)(addr), (uint8_t)(val)) +#define FAST_WRITE16(addr, val) Ps2FastWrite16(rdram, (uint32_t)(addr), (uint16_t)(val)) +#define FAST_WRITE32(addr, val) Ps2FastWrite32(rdram, (uint32_t)(addr), (uint32_t)(val)) +#define FAST_WRITE64(addr, val) Ps2FastWrite64(rdram, (uint32_t)(addr), (uint64_t)(val)) +#define FAST_WRITE128(addr, val) Ps2FastWrite128(rdram, (uint32_t)(addr), (val)) + +#define READ8(addr) ([&]() -> uint8_t { \ + uint32_t _addr = (uint32_t)(addr); \ + return PS2Runtime::isSpecialAddress(_addr) \ + ? runtime->Load8(rdram, ctx, _addr) \ + : FAST_READ8(_addr); }()) + +#define READ16(addr) ([&]() -> uint16_t { \ + uint32_t _addr = (uint32_t)(addr); \ + return PS2Runtime::isSpecialAddress(_addr) \ + ? runtime->Load16(rdram, ctx, _addr) \ + : FAST_READ16(_addr); }()) + +#define READ32(addr) ([&]() -> uint32_t { \ + uint32_t _addr = (uint32_t)(addr); \ + return PS2Runtime::isSpecialAddress(_addr) \ + ? runtime->Load32(rdram, ctx, _addr) \ + : FAST_READ32(_addr); }()) + +#define READ64(addr) ([&]() -> uint64_t { \ + uint32_t _addr = (uint32_t)(addr); \ + return PS2Runtime::isSpecialAddress(_addr) \ + ? runtime->Load64(rdram, ctx, _addr) \ + : FAST_READ64(_addr); }()) + +#define READ128(addr) ([&]() -> __m128i { \ + uint32_t _addr = (uint32_t)(addr); \ + return PS2Runtime::isSpecialAddress(_addr) \ + ? runtime->Load128(rdram, ctx, _addr) \ + : FAST_READ128(_addr); }()) + +#define WRITE8(addr, val) \ + do \ + { \ + uint32_t _addr = (addr); \ + if (PS2Runtime::isSpecialAddress(_addr)) \ + runtime->Store8(rdram, ctx, _addr, (val)); \ + else \ + { \ + ps2TraceGuestWrite(rdram, _addr, 1u, (uint8_t)(val), 0u, "WRITE8", ctx); \ + FAST_WRITE8(_addr, (val)); \ + } \ + } while (0) + +#define WRITE16(addr, val) \ + do \ + { \ + uint32_t _addr = (addr); \ + if (PS2Runtime::isSpecialAddress(_addr)) \ + runtime->Store16(rdram, ctx, _addr, (val)); \ + else \ + { \ + ps2TraceGuestWrite(rdram, _addr, 2u, (uint16_t)(val), 0u, "WRITE16", ctx); \ + FAST_WRITE16(_addr, (val)); \ + } \ + } while (0) + +#define WRITE32(addr, val) \ + do \ + { \ + uint32_t _addr = (addr); \ + if (PS2Runtime::isSpecialAddress(_addr)) \ + runtime->Store32(rdram, ctx, _addr, (val)); \ + else \ + { \ + ps2TraceGuestWrite(rdram, _addr, 4u, (uint32_t)(val), 0u, "WRITE32", ctx); \ + FAST_WRITE32(_addr, (val)); \ + } \ + } while (0) + +#define WRITE64(addr, val) \ + do \ + { \ + uint32_t _addr = (addr); \ + if (PS2Runtime::isSpecialAddress(_addr)) \ + runtime->Store64(rdram, ctx, _addr, (val)); \ + else \ + { \ + ps2TraceGuestWrite(rdram, _addr, 8u, (uint64_t)(val), 0u, "WRITE64", ctx); \ + FAST_WRITE64(_addr, (val)); \ + } \ + } while (0) + +#define WRITE128(addr, val) \ + do \ + { \ + uint32_t _addr = (addr); \ + if (PS2Runtime::isSpecialAddress(_addr)) \ + runtime->Store128(rdram, ctx, _addr, (val)); \ + else \ + { \ + FAST_WRITE128(_addr, (val)); \ + } \ + } while (0) // Packed Compare Greater Than (PCGT) #define PS2_PCGTW(a, b) _mm_cmpgt_epi32((__m128i)(a), (__m128i)(b)) @@ -113,54 +311,71 @@ inline uint32_t ps2_clz32(uint32_t val) { #define PS2_PPACB(a, b) _mm_packus_epi16(_mm_packs_epi32((__m128i)(b), (__m128i)(a)), _mm_setzero_si128()) // Packed Interleave (PINT) -#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0))) -#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0))) +#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0))) +#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0))) // Packed Multiply-Add (PMADD) -#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1,0,3,2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1,0,3,2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)))) +#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1, 0, 3, 2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1, 0, 3, 2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0)))) // Packed Variable Shifts #define PS2_PSLLVW(a, b) _mm_custom_sllv_epi32((__m128i)(a), (__m128i)(b)) #define PS2_PSRLVW(a, b) _mm_custom_srlv_epi32((__m128i)(a), (__m128i)(b)) #define PS2_PSRAVW(a, b) _mm_custom_srav_epi32((__m128i)(a), (__m128i)(b)) -// Helper function declarations for custom variable shifts -inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count) { - int32_t a_arr[4], count_arr[4], result[4]; - _mm_storeu_si128((__m128i*)a_arr, a); - _mm_storeu_si128((__m128i*)count_arr, count); - for (int i = 0; i < 4; i++) { +inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count) +{ + alignas(16) int32_t a_arr[4]; + alignas(16) int32_t count_arr[4]; + alignas(16) int32_t result[4]; + + std::memcpy(a_arr, &a, sizeof(a)); + std::memcpy(count_arr, &count, sizeof(count)); + + for (int i = 0; i < 4; i++) + { result[i] = a_arr[i] << (count_arr[i] & 0x1F); } - return _mm_loadu_si128((__m128i*)result); + + __m128i out; + std::memcpy(&out, result, sizeof(out)); + return out; } -inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count) { +inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count) +{ int32_t a_arr[4], count_arr[4], result[4]; - _mm_storeu_si128((__m128i*)a_arr, a); - _mm_storeu_si128((__m128i*)count_arr, count); - for (int i = 0; i < 4; i++) { + _mm_storeu_si128((__m128i *)a_arr, a); + _mm_storeu_si128((__m128i *)count_arr, count); + for (int i = 0; i < 4; i++) + { result[i] = (uint32_t)a_arr[i] >> (count_arr[i] & 0x1F); } - return _mm_loadu_si128((__m128i*)result); + return _mm_loadu_si128((__m128i *)result); } -inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) { +inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) +{ int32_t a_arr[4], count_arr[4], result[4]; - _mm_storeu_si128((__m128i*)a_arr, a); - _mm_storeu_si128((__m128i*)count_arr, count); - for (int i = 0; i < 4; i++) { + _mm_storeu_si128((__m128i *)a_arr, a); + _mm_storeu_si128((__m128i *)count_arr, count); + for (int i = 0; i < 4; i++) + { result[i] = a_arr[i] >> (count_arr[i] & 0x1F); } - return _mm_loadu_si128((__m128i*)result); + return _mm_loadu_si128((__m128i *)result); } // PMFHL function implementations -#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(lo, hi) -#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(lo, hi) -#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(lo, hi) -#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)) -#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)), _MM_SHUFFLE(3,1,2,0)) +inline __m128i ps2_u64_to_epi64_pair(uint64_t value) +{ + return _mm_set1_epi64x(static_cast(value)); +} + +#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)) +#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)) +#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)) +#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)), _MM_SHUFFLE(3, 1, 2, 0)) +#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)), _MM_SHUFFLE(3, 1, 2, 0)), _MM_SHUFFLE(3, 1, 2, 0)) // FPU (COP1) operations #define FPU_ADD_S(a, b) ((float)(a) + (float)(b)) @@ -212,45 +427,58 @@ inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) { #define PS2_VCALLMS(addr) // VU0 microprogram calls not supported directly #define PS2_VCALLMSR(reg) // VU0 microprogram calls not supported directly -#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : static_cast(_mm_extract_epi32(ctx_ptr->r[reg_idx], 0))) -#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : _mm_extract_epi32(ctx_ptr->r[reg_idx], 0)) -#define GPR_U64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0ULL : static_cast(_mm_extract_epi64(ctx_ptr->r[reg_idx], 0))) -#define GPR_S64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0LL : _mm_extract_epi64(ctx_ptr->r[reg_idx], 0)) +#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : static_cast(PS2_EXTRACT_EPI32_0(ctx_ptr->r[reg_idx]))) +#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : PS2_EXTRACT_EPI32_0(ctx_ptr->r[reg_idx])) +#define GPR_U64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0ULL : static_cast(PS2_EXTRACT_EPI64_0(ctx_ptr->r[reg_idx]))) +#define GPR_S64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0LL : PS2_EXTRACT_EPI64_0(ctx_ptr->r[reg_idx])) #define GPR_VEC(ctx_ptr, reg_idx) ((reg_idx == 0) ? _mm_setzero_si128() : ctx_ptr->r[reg_idx]) -#define SET_GPR_U32(ctx_ptr, reg_idx, val) \ - do \ - { \ - if (reg_idx != 0) \ - ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \ +static inline void Ps2SetGprLow64(R5900Context *ctx, int reg, __m128i new_low) +{ + if (reg != 0) + { + ctx->r[reg] = _mm_castpd_si128(_mm_move_sd(_mm_castsi128_pd(ctx->r[reg]), _mm_castsi128_pd(new_low))); + } +} + +#define SET_GPR_U32(ctx_ptr, reg_idx, val) \ + do \ + { \ + if ((reg_idx) != 0) \ + { \ + __m128i _newVal = _mm_cvtsi32_si128((int)(val)); \ + \ + Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \ + } \ } while (0) -#define SET_GPR_S32(ctx_ptr, reg_idx, val) \ - do \ - { \ - if (reg_idx != 0) \ - ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \ +#define SET_GPR_S32(ctx_ptr, reg_idx, val) \ + do \ + { \ + if ((reg_idx) != 0) \ + { \ + __m128i _newVal = _mm_cvtsi64_si128((int64_t)(int32_t)(val)); \ + Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \ + } \ } while (0) -#define SET_GPR_U64(ctx_ptr, reg_idx, val) \ - do \ - { \ - if (reg_idx != 0) \ - ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \ +#define SET_GPR_U64(ctx_ptr, reg_idx, val) \ + do \ + { \ + if ((reg_idx) != 0) \ + { \ + __m128i _newVal = _mm_cvtsi64_si128((int64_t)(val)); \ + Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \ + } \ } while (0) -#define SET_GPR_S64(ctx_ptr, reg_idx, val) \ - do \ - { \ - if (reg_idx != 0) \ - ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \ - } while (0) +#define SET_GPR_S64(ctx_ptr, reg_idx, val) SET_GPR_U64(ctx_ptr, reg_idx, val) #define SET_GPR_VEC(ctx_ptr, reg_idx, val) \ do \ { \ if (reg_idx != 0) \ - ctx_ptr->r[reg_idx] = (val); \ + ctx_ptr->r[reg_idx] = (val); \ } while (0) #endif // PS2_RUNTIME_MACROS_H diff --git a/ps2xRuntime/include/ps2_stubs.h b/ps2xRuntime/include/ps2_stubs.h index 213c048..963caa9 100644 --- a/ps2xRuntime/include/ps2_stubs.h +++ b/ps2xRuntime/include/ps2_stubs.h @@ -11,6 +11,9 @@ namespace ps2_stubs PS2_STUB_LIST(PS2_DECLARE_STUB) #undef PS2_DECLARE_STUB + void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void TODO_NAMED(const char *name, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); } diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index 97a4457..cb0fd58 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -11,27 +11,17 @@ extern std::atomic g_activeThreads; static std::mutex g_sys_fd_mutex; -#define PS2_FIO_O_RDONLY 0x0001 -#define PS2_FIO_O_WRONLY 0x0002 -#define PS2_FIO_O_RDWR 0x0003 -#define PS2_FIO_O_NBLOCK 0x0010 -#define PS2_FIO_O_APPEND 0x0100 -#define PS2_FIO_O_CREAT 0x0200 -#define PS2_FIO_O_TRUNC 0x0400 -#define PS2_FIO_O_EXCL 0x0800 -#define PS2_FIO_O_NOWAIT 0x8000 - -#define PS2_SEEK_SET 0 -#define PS2_SEEK_CUR 1 -#define PS2_SEEK_END 2 - namespace ps2_syscalls { - #define PS2_DECLARE_SYSCALL(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +#define PS2_DECLARE_SYSCALL(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); PS2_SYSCALL_LIST(PS2_DECLARE_SYSCALL) - #undef PS2_DECLARE_SYSCALL +#undef PS2_DECLARE_SYSCALL - void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId); } #endif // PS2_SYSCALLS_H diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index d4fae7f..e01c107 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -2,10 +2,34 @@ #include #include #include -#include +#include namespace { + inline void inRange(uint32_t offset, size_t bytes, size_t regionSize, const char *op, uint32_t address) + { + if (static_cast(offset) + static_cast(bytes) > static_cast(regionSize)) + { + throw std::runtime_error(std::string(op) + " out-of-bounds at address: 0x" + std::to_string(address)); + } + } + + template + inline T loadScalar(const uint8_t *base, uint32_t offset, size_t regionSize, const char *op, uint32_t address) + { + inRange(offset, sizeof(T), regionSize, op, address); + T value{}; + std::memcpy(&value, base + offset, sizeof(T)); + return value; + } + + template + inline void storeScalar(uint8_t *base, uint32_t offset, size_t regionSize, T value, const char *op, uint32_t address) + { + inRange(offset, sizeof(T), regionSize, op, address); + std::memcpy(base + offset, &value, sizeof(T)); + } + inline bool isGsPrivReg(uint32_t addr) { return addr >= PS2_GS_PRIV_REG_BASE && addr < PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE; @@ -59,38 +83,9 @@ namespace } } - inline void logGsWrite(uint32_t addr, uint64_t value) - { - static std::unordered_map logCount; - int &count = logCount[addr]; - if (count < 10) - { - std::cout << "[GS] write 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl; - } - ++count; - } - - constexpr uint32_t kSchedulerBase = 0x00363a10; - constexpr uint32_t kSchedulerSpan = 0x00000420; - static int g_schedWriteLogCount = 0; - - inline void logSchedulerWrite(uint32_t physAddr, uint32_t size, uint64_t value) - { - if (physAddr < kSchedulerBase || physAddr >= kSchedulerBase + kSchedulerSpan) - { - return; - } - if (g_schedWriteLogCount >= 64) - { - return; - } - std::cout << "[sched write" << size << "] addr=0x" << std::hex << physAddr - << " val=0x" << value << std::dec << std::endl; - ++g_schedWriteLogCount; - } } -// Helpers for GS VRAM addressing (PSMCT32 only in this minimal path). +// Helpers for GS VRAM addressing (PSMCT32 path). static inline uint32_t gs_vram_offset(uint32_t basePage, uint32_t x, uint32_t y, uint32_t fbw) { // basePage is in 2048-byte units; fbw is in blocks of 64 pixels. @@ -99,8 +94,9 @@ static inline uint32_t gs_vram_offset(uint32_t basePage, uint32_t x, uint32_t y, } PS2Memory::PS2Memory() - : m_rdram(nullptr), m_scratchpad(nullptr), m_gsVRAM(nullptr), m_seenGifCopy(false) + : m_rdram(nullptr), m_scratchpad(nullptr), iop_ram(nullptr), m_seenGifCopy(false), m_gsVRAM(nullptr) { + ps2SetScratchpadHostPtr(nullptr); } PS2Memory::~PS2Memory() @@ -113,6 +109,7 @@ PS2Memory::~PS2Memory() if (m_scratchpad) { + ps2SetScratchpadHostPtr(nullptr); delete[] m_scratchpad; m_scratchpad = nullptr; } @@ -122,45 +119,53 @@ PS2Memory::~PS2Memory() delete[] m_gsVRAM; m_gsVRAM = nullptr; } + + if (iop_ram) + { + delete[] iop_ram; + iop_ram = nullptr; + } } bool PS2Memory::initialize(size_t ramSize) { + auto cleanup = [this]() + { + delete[] m_rdram; + delete[] m_scratchpad; + delete[] iop_ram; + delete[] m_gsVRAM; + m_rdram = nullptr; + m_scratchpad = nullptr; + ps2SetScratchpadHostPtr(nullptr); + iop_ram = nullptr; + m_gsVRAM = nullptr; + }; + + cleanup(); + m_seenGifCopy = false; + m_dmaStartCount.store(0, std::memory_order_relaxed); + m_gifCopyCount.store(0, std::memory_order_relaxed); + m_gsWriteCount.store(0, std::memory_order_relaxed); + m_vifWriteCount.store(0, std::memory_order_relaxed); + m_codeRegions.clear(); + try { // Allocate main RAM m_rdram = new uint8_t[ramSize]; - if (!m_rdram) - { - std::cerr << "Failed to allocate " << ramSize << " bytes for RDRAM" << std::endl; - return false; - } std::memset(m_rdram, 0, ramSize); // Allocate scratchpad m_scratchpad = new uint8_t[PS2_SCRATCHPAD_SIZE]; - if (!m_scratchpad) - { - std::cerr << "Failed to allocate " << PS2_SCRATCHPAD_SIZE << " bytes for scratchpad" << std::endl; - delete[] m_rdram; - m_rdram = nullptr; - return false; - } std::memset(m_scratchpad, 0, PS2_SCRATCHPAD_SIZE); + ps2SetScratchpadHostPtr(m_scratchpad); - // Initialize TLB entries - m_tlbEntries.clear(); + // Initialize EE TLB entries (R5900 has 48 entries). + m_tlbEntries.assign(48, TLBEntry{0, 0, 0, false}); // Allocate IOP RAM iop_ram = new uint8_t[2 * 1024 * 1024]; // 2MB - if (!iop_ram) - { - delete[] m_rdram; - delete[] m_scratchpad; - m_rdram = nullptr; - m_scratchpad = nullptr; - return false; - } // Initialize IOP RAM with zeros std::memset(iop_ram, 0, 2 * 1024 * 1024); @@ -173,16 +178,6 @@ bool PS2Memory::initialize(size_t ramSize) // Allocate GS VRAM (4MB) m_gsVRAM = new uint8_t[PS2_GS_VRAM_SIZE]; - if (!m_gsVRAM) - { - delete[] m_rdram; - delete[] m_scratchpad; - delete[] iop_ram; - m_rdram = nullptr; - m_scratchpad = nullptr; - iop_ram = nullptr; - return false; - } std::memset(m_gsVRAM, 0, PS2_GS_VRAM_SIZE); // Initialize VIF registers @@ -197,6 +192,7 @@ bool PS2Memory::initialize(size_t ramSize) catch (const std::exception &e) { std::cerr << "Error initializing PS2 memory: " << e.what() << std::endl; + cleanup(); return false; } } @@ -214,34 +210,93 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) return virtualAddress - PS2_SCRATCHPAD_BASE; } - if (virtualAddress < PS2_RAM_SIZE || - (virtualAddress >= 0x80000000 && virtualAddress < 0x80000000 + PS2_RAM_SIZE)) + // KSEG0/KSEG1 direct-mapped window. + if (virtualAddress >= 0x80000000 && virtualAddress < 0xC0000000) { return virtualAddress & 0x1FFFFFFF; } + // In this runtime, low segments are treated as physical-style addresses already. + if (virtualAddress < 0x80000000) + { + return virtualAddress; + } + + // KSEG2/KSEG3 are TLB mapped. if (virtualAddress >= 0xC0000000) { for (const auto &entry : m_tlbEntries) { if (entry.valid) { - uint32_t vpn_masked = (virtualAddress >> 12) & ~entry.mask; - uint32_t entry_vpn_masked = entry.vpn & ~entry.mask; - - if (vpn_masked == entry_vpn_masked) + // PageMask uses bits [24:13]. Build an address-level mask (plus 4KB base page bits). + const uint32_t mask = entry.mask & 0x01FFE000u; + const uint32_t compareMask = ~(mask | 0xFFFu); + if ((virtualAddress & compareMask) == (entry.vpn & compareMask)) { // TLB hit - uint32_t offset = virtualAddress & 0xFFF; // Page offset - uint32_t page = entry.pfn | (virtualAddress & entry.mask); - return (page << 12) | offset; + const uint32_t pageOffsetMask = mask | 0xFFFu; + const uint32_t physBase = entry.pfn << 12; + return physBase | (virtualAddress & pageOffsetMask); } } } throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress)); } - return virtualAddress & 0x1FFFFFFF; + return virtualAddress; +} + +bool PS2Memory::tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const +{ + if (index >= m_tlbEntries.size()) + { + return false; + } + + const TLBEntry &entry = m_tlbEntries[index]; + vpn = entry.vpn; + pfn = entry.pfn; + mask = entry.mask; + valid = entry.valid; + return true; +} + +bool PS2Memory::tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid) +{ + if (index >= m_tlbEntries.size()) + { + return false; + } + + TLBEntry &entry = m_tlbEntries[index]; + entry.vpn = vpn & 0xFFFFF000u; + entry.pfn = pfn & 0x000FFFFFu; + entry.mask = mask & 0x01FFE000u; + entry.valid = valid; + return true; +} + +int32_t PS2Memory::tlbProbe(uint32_t vpn) const +{ + const uint32_t normalizedVpn = vpn & 0xFFFFF000u; + for (uint32_t i = 0; i < static_cast(m_tlbEntries.size()); ++i) + { + const TLBEntry &entry = m_tlbEntries[i]; + if (!entry.valid) + { + continue; + } + + const uint32_t mask = entry.mask & 0x01FFE000u; + const uint32_t compareMask = ~(mask | 0xFFFu); + if ((normalizedVpn & compareMask) == (entry.vpn & compareMask)) + { + return static_cast(i); + } + } + + return -1; } uint8_t PS2Memory::read8(uint32_t address) @@ -260,16 +315,11 @@ uint8_t PS2Memory::read8(uint32_t address) else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { uint32_t regAddr = physAddr & ~0x3; - if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) - { - uint32_t value = m_ioRegisters[regAddr]; - uint32_t shift = (physAddr & 3) * 8; - return (value >> shift) & 0xFF; - } - return 0; + uint32_t value = readIORegister(regAddr); + uint32_t shift = (physAddr & 3) * 8; + return static_cast((value >> shift) & 0xFF); } - // TODO: Handle other memory regions return 0; } @@ -285,22 +335,18 @@ uint16_t PS2Memory::read16(uint32_t address) if (scratch) { - return *reinterpret_cast(&m_scratchpad[physAddr]); + return loadScalar(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read16 scratchpad", address); } if (physAddr < PS2_RAM_SIZE) { - return *reinterpret_cast(&m_rdram[physAddr]); + return loadScalar(m_rdram, physAddr, PS2_RAM_SIZE, "read16 rdram", address); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { uint32_t regAddr = physAddr & ~0x3; - if (m_ioRegisters.find(regAddr) != m_ioRegisters.end()) - { - uint32_t value = m_ioRegisters[regAddr]; - uint32_t shift = (physAddr & 2) * 8; - return (value >> shift) & 0xFFFF; - } - return 0; + uint32_t value = readIORegister(regAddr); + uint32_t shift = (physAddr & 2) * 8; + return static_cast((value >> shift) & 0xFFFF); } return 0; @@ -326,19 +372,15 @@ uint32_t PS2Memory::read32(uint32_t address) if (scratch) { - return *reinterpret_cast(&m_scratchpad[physAddr]); + return loadScalar(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read32 scratchpad", address); } if (physAddr < PS2_RAM_SIZE) { - return *reinterpret_cast(&m_rdram[physAddr]); + return loadScalar(m_rdram, physAddr, PS2_RAM_SIZE, "read32 rdram", address); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { - if (m_ioRegisters.find(physAddr) != m_ioRegisters.end()) - { - return m_ioRegisters[physAddr]; - } - return 0; + return readIORegister(physAddr); } return 0; @@ -362,11 +404,11 @@ uint64_t PS2Memory::read64(uint32_t address) if (scratch) { - return *reinterpret_cast(&m_scratchpad[physAddr]); + return loadScalar(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read64 scratchpad", address); } if (physAddr < PS2_RAM_SIZE) { - return *reinterpret_cast(&m_rdram[physAddr]); + return loadScalar(m_rdram, physAddr, PS2_RAM_SIZE, "read64 rdram", address); } // 64-bit IO operations are not common, but who knows @@ -385,10 +427,12 @@ __m128i PS2Memory::read128(uint32_t address) if (scratch) { + inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "read128 scratchpad", address); return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr])); } if (physAddr < PS2_RAM_SIZE) { + inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "read128 rdram", address); return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr])); } @@ -409,7 +453,6 @@ void PS2Memory::write8(uint32_t address, uint8_t value) else if (physAddr < PS2_RAM_SIZE) { m_rdram[physAddr] = value; - logSchedulerWrite(physAddr, 8, value); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { @@ -418,9 +461,7 @@ void PS2Memory::write8(uint32_t address, uint8_t value) uint32_t shift = (physAddr & 3) * 8; uint32_t mask = ~(0xFF << shift); uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); - m_ioRegisters[regAddr] = newValue; - - // TODO: Handle potential side effects of IO register writes + writeIORegister(regAddr, newValue); } } @@ -436,12 +477,11 @@ void PS2Memory::write16(uint32_t address, uint16_t value) if (scratch) { - *reinterpret_cast(&m_scratchpad[physAddr]) = value; + storeScalar(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write16 scratchpad", address); } else if (physAddr < PS2_RAM_SIZE) { - *reinterpret_cast(&m_rdram[physAddr]) = value; - logSchedulerWrite(physAddr, 16, value); + storeScalar(m_rdram, physAddr, PS2_RAM_SIZE, value, "write16 rdram", address); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { @@ -449,9 +489,7 @@ void PS2Memory::write16(uint32_t address, uint16_t value) uint32_t shift = (physAddr & 2) * 8; uint32_t mask = ~(0xFFFF << shift); uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift); - m_ioRegisters[regAddr] = newValue; - - // TODO: Handle potential side effects of IO register writes + writeIORegister(regAddr, newValue); } } @@ -471,7 +509,6 @@ void PS2Memory::write32(uint32_t address, uint32_t value) uint64_t mask = 0xFFFFFFFFULL << (off * 8); uint64_t newVal = (*reg & ~mask) | ((uint64_t)value << (off * 8)); *reg = newVal; - logGsWrite(address, newVal); } return; } @@ -481,25 +518,17 @@ void PS2Memory::write32(uint32_t address, uint32_t value) if (scratch) { - *reinterpret_cast(&m_scratchpad[physAddr]) = value; + storeScalar(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write32 scratchpad", address); } else if (physAddr < PS2_RAM_SIZE) { // Check if this might be code modification markModified(address, 4); - *reinterpret_cast(&m_rdram[physAddr]) = value; - logSchedulerWrite(physAddr, 32, value); + storeScalar(m_rdram, physAddr, PS2_RAM_SIZE, value, "write32 rdram", address); } else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE) { - static int ioLogCount = 0; - if (ioLogCount < 64) - { - std::cout << "[IO write32] addr=0x" << std::hex << physAddr << " val=0x" << value << std::dec << std::endl; - ++ioLogCount; - } - // Handle IO register writes with potential side effects writeIORegister(physAddr, value); } } @@ -517,7 +546,6 @@ void PS2Memory::write64(uint32_t address, uint64_t value) if (reg) { *reg = value; - logGsWrite(address, value); } return; } @@ -527,12 +555,11 @@ void PS2Memory::write64(uint32_t address, uint64_t value) if (scratch) { - *reinterpret_cast(&m_scratchpad[physAddr]) = value; + storeScalar(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write64 scratchpad", address); } else if (physAddr < PS2_RAM_SIZE) { - *reinterpret_cast(&m_rdram[physAddr]) = value; - logSchedulerWrite(physAddr, 64, value); + storeScalar(m_rdram, physAddr, PS2_RAM_SIZE, value, "write64 rdram", address); } else { @@ -553,18 +580,17 @@ void PS2Memory::write128(uint32_t address, __m128i value) if (scratch) { + inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "write128 scratchpad", address); _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr]), value); } else if (physAddr < PS2_RAM_SIZE) { + inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "write128 rdram", address); _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value); } - else if (physAddr < PS2_GS_VRAM_SIZE) - { - _mm_storeu_si128(reinterpret_cast<__m128i *>(&m_gsVRAM[physAddr]), value); - } else { + // Non-RAM 128-bit stores are modeled as two 64-bit stores. uint64_t lo = _mm_extract_epi64(value, 0); uint64_t hi = _mm_extract_epi64(value, 1); @@ -575,180 +601,108 @@ void PS2Memory::write128(uint32_t address, __m128i value) bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) { + m_ioRegisters[address] = value; + if (address >= 0x10008000 && address < 0x1000F000) { - static int dmaLogCount = 0; - if (dmaLogCount < 100) + if ((address & 0xFF) == 0x00 && (value & 0x100)) { - uint32_t channelBase = address & 0xFFFFFF00; - uint32_t offset = address & 0xFF; - std::cout << "[DMA reg] ch=0x" << std::hex << channelBase - << " off=0x" << offset << " = 0x" << value << std::dec << std::endl; - dmaLogCount++; - if (offset == 0x00 && (value & 0x100)) + const uint32_t channelBase = address & 0xFFFFFF00; + const uint32_t madr = m_ioRegisters[channelBase + 0x10]; + const uint32_t qwc = m_ioRegisters[channelBase + 0x20]; + m_dmaStartCount.fetch_add(1, std::memory_order_relaxed); + + if ((channelBase == 0x1000A000 || channelBase == 0x10009000) && m_gsVRAM) { - uint32_t madr = m_ioRegisters[channelBase + 0x10]; - uint32_t qwc = m_ioRegisters[channelBase + 0x20]; - uint32_t tadr = m_ioRegisters[channelBase + 0x30]; - std::cout << "[DMA start] ch=0x" << std::hex << channelBase - << " madr=0x" << madr << " qwc=0x" << qwc - << " tadr=0x" << tadr << std::dec << std::endl; - m_dmaStartCount.fetch_add(1, std::memory_order_relaxed); + auto doCopy = [&](uint32_t srcAddr, uint32_t qwCount) + { + const uint64_t bytes64 = static_cast(qwCount) * 16ull; + uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(bytes64); + uint32_t src = 0; + try + { + src = translateAddress(srcAddr); + } + catch (const std::exception &) + { + return; + } + uint32_t basePage = static_cast(gs_regs.dispfb1 & 0x1FF); + uint32_t dest = basePage * 2048; + if (dest >= PS2_GS_VRAM_SIZE) + { + return; + } + if (dest + bytes > PS2_GS_VRAM_SIZE) + { + bytes = std::min(bytes, PS2_GS_VRAM_SIZE - dest); + } + if (src >= PS2_RAM_SIZE) + { + return; + } + if (src + bytes > PS2_RAM_SIZE) + { + bytes = std::min(bytes, PS2_RAM_SIZE - src); + } + if (bytes == 0) + { + return; + } + std::memcpy(m_gsVRAM + dest, m_rdram + src, bytes); + m_seenGifCopy = true; + m_gifCopyCount.fetch_add(1, std::memory_order_relaxed); + }; + + if (qwc > 0) + { + doCopy(madr, qwc); + } + else + { + uint32_t tadr = m_ioRegisters[channelBase + 0x30]; + uint32_t physTag = translateAddress(tadr); + if (physTag + 16 <= PS2_RAM_SIZE) + { + const uint8_t *tp = m_rdram + physTag; + uint64_t tag = loadScalar(tp, 0, 16, "dma chain tag", tadr); + uint16_t tagQwc = static_cast(tag & 0xFFFF); + uint32_t id = static_cast((tag >> 28) & 0x7); + uint32_t addr = static_cast((tag >> 32) & 0x7FFFFFF); + if (id == 0 || id == 1 || id == 2) + { + doCopy(addr, tagQwc); + } + } + } + m_ioRegisters[address] &= ~0x100; } } + return true; } - m_ioRegisters[address] = value; if (address >= 0x10000000 && address < 0x10010000) { - // Timer/counter registers - if (address >= 0x10000000 && address < 0x10000100) - { - std::cout << "Timer register write: " << std::hex << address << " = " << value << std::dec << std::endl; - return true; - } - - // VIF0/VIF1 registers if (address >= 0x10003800 && address < 0x10003A00) { - static int vif0Log = 0; - if (vif0Log < 50) - { - std::cout << "[VIF0] write 0x" << std::hex << address << " = 0x" << value << std::dec << std::endl; - ++vif0Log; - } m_vifWriteCount.fetch_add(1, std::memory_order_relaxed); } if (address >= 0x10003C00 && address < 0x10003E00) { - static int vif1Log = 0; - if (vif1Log < 50) - { - std::cout << "[VIF1] write 0x" << std::hex << address << " = 0x" << value << std::dec << std::endl; - ++vif1Log; - } m_vifWriteCount.fetch_add(1, std::memory_order_relaxed); } - - // DMA registers - if (address >= 0x10008000 && address < 0x1000F000) - { - std::cout << "DMA register write: " << std::hex << address << " = " << value << std::dec << std::endl; - - // Dump current DMA regs for all channels - static bool dumpedDma = false; - if (!dumpedDma) - { - for (int ch = 0; ch < 10; ++ch) - { - uint32_t base = 0x10008000 + ch * 0x100; - uint32_t chcr_v = m_ioRegisters[base + 0x00]; - uint32_t madr_v = m_ioRegisters[base + 0x10]; - uint32_t qwc_v = m_ioRegisters[base + 0x20]; - uint32_t tadr_v = m_ioRegisters[base + 0x30]; - std::cout << "[DMA dump] ch" << ch - << " chcr=0x" << std::hex << chcr_v - << " madr=0x" << madr_v - << " qwc=0x" << qwc_v - << " tadr=0x" << tadr_v << std::dec << std::endl; - } - dumpedDma = true; - } - - if ((address & 0xFF) == 0x00) - { // CHCR registers - if (value & 0x100) - { - uint32_t channelBase = address & 0xFFFFFF00; - uint32_t madr = m_ioRegisters[channelBase + 0x10]; // Memory address - uint32_t qwc = m_ioRegisters[channelBase + 0x20]; // Quadword count - - std::cout << "Starting DMA transfer on channel " << ((address >> 8) & 0xF) - << ", MADR: " << std::hex << madr - << ", QWC: " << qwc << std::dec << std::endl; - - // Minimal GIF (channel 2) and VIF1 (channel 1) image transfer: copy from EE memory to GS VRAM. - // Only handles simple linear IMAGE transfers; treats destination as current DISPFBUF1 FBP. - if ((channelBase == 0x1000A000 || channelBase == 0x10009000) && m_gsVRAM) - { - auto doCopy = [&](uint32_t srcAddr, uint32_t qwCount) - { - uint32_t bytes = qwCount * 16; - uint32_t src = translateAddress(srcAddr); - uint32_t basePage = static_cast(gs_regs.dispfb1 & 0x1FF); - uint32_t dest = basePage * 2048; - std::cout << "[GIF] ch=" << ((channelBase == 0x1000A000) ? 2 : 1) - << " IMAGE copy bytes=" << bytes - << " src=0x" << std::hex << srcAddr - << " (phys 0x" << src << ")" - << " dest=0x" << dest << std::dec << std::endl; - if (dest + bytes > PS2_GS_VRAM_SIZE) - { - bytes = std::min(bytes, PS2_GS_VRAM_SIZE - dest); - } - if (src + bytes > PS2_RAM_SIZE) - { - bytes = std::min(bytes, PS2_RAM_SIZE - src); - } - std::memcpy(m_gsVRAM + dest, m_rdram + src, bytes); - m_seenGifCopy = true; - m_gifCopyCount.fetch_add(1, std::memory_order_relaxed); - }; - - // Dump GIF tag/header - uint32_t phys = translateAddress(madr); - if (phys + 16 <= PS2_RAM_SIZE) - { - const uint8_t *p = m_rdram + phys; - uint64_t tag0 = *reinterpret_cast(p + 0); - uint64_t tag1 = *reinterpret_cast(p + 8); - std::cout << "[GIF] tag0=0x" << std::hex << tag0 << " tag1=0x" << tag1 << std::dec << std::endl; - } - - if (qwc > 0) - { - doCopy(madr, qwc); - } - else - { - // Simple DMA chain walker for one tag from TADR (REF/NEXT). - uint32_t tadr = m_ioRegisters[channelBase + 0x30]; - uint32_t physTag = translateAddress(tadr); - if (physTag + 16 <= PS2_RAM_SIZE) - { - const uint8_t *tp = m_rdram + physTag; - uint64_t tag = *reinterpret_cast(tp); - uint16_t tagQwc = static_cast(tag & 0xFFFF); - uint32_t id = static_cast((tag >> 28) & 0x7); - uint32_t addr = static_cast((tag >> 32) & 0x7FFFFFF); - std::cout << "[DMA chain] ch=" << ((channelBase == 0x1000A000) ? 2 : 1) - << " tag id=0x" << std::hex << id - << " qwc=" << tagQwc - << " addr=0x" << addr - << " raw=0x" << tag << std::dec << std::endl; - if (id == 0 || id == 1 || id == 2) - { - doCopy(addr, tagQwc); - } - } - } - m_ioRegisters[address] &= ~0x100; - } - } - } - return true; - } - if (address >= 0x10000200 && address < 0x10000300) { - std::cout << "Interrupt register write: " << std::hex << address << " = " << value << std::dec << std::endl; + return true; + } + if (address >= 0x10000000 && address < 0x10000100) + { return true; } } - else if (address >= 0x12000000 && address < 0x12001000) + + if (address >= 0x12000000 && address < 0x12001000) { - // GS registers - std::cout << "GS register write: " << std::hex << address << " = " << value << std::dec << std::endl; m_gsWriteCount.fetch_add(1, std::memory_order_relaxed); return true; } @@ -758,55 +712,70 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) uint32_t PS2Memory::readIORegister(uint32_t address) { - auto it = m_ioRegisters.find(address); - if (it != m_ioRegisters.end()) - { - return it->second; - } - if (address >= 0x10000000 && address < 0x10010000) { - // Timer registers if (address >= 0x10000000 && address < 0x10000100) { if ((address & 0xF) == 0x00) - { // COUNT registers - uint32_t timerCount = 0; // Should calculate based on elapsed time - std::cout << "Timer COUNT read: " << std::hex << address << " = " << timerCount << std::dec << std::endl; - return timerCount; + { + return 0; } } - // DMA status registers if (address >= 0x10008000 && address < 0x1000F000) { if ((address & 0xFF) == 0x00) - { // CHCR registers - uint32_t channelStatus = m_ioRegisters[address] & ~0x100; // Clear busy bit - std::cout << "DMA status read: " << std::hex << address << " = " << channelStatus << std::dec << std::endl; + { + uint32_t channelStatus = m_ioRegisters[address] & ~0x100; + m_ioRegisters[address] = channelStatus; return channelStatus; } } - // Interrupt status registers if (address >= 0x10000200 && address < 0x10000300) { - std::cout << "Interrupt status read: " << std::hex << address << std::dec << std::endl; - // Should calculate based on pending interrupts return 0; } } + auto it = m_ioRegisters.find(address); + if (it != m_ioRegisters.end()) + { + return it->second; + } + return 0; } void PS2Memory::registerCodeRegion(uint32_t start, uint32_t end) { + if (end <= start) + { + std::cerr << "Ignoring invalid code region: start=0x" << std::hex << start + << " end=0x" << end << std::dec << std::endl; + return; + } + + if ((end - start) > PS2_RAM_SIZE) + { + std::cerr << "Ignoring oversized code region: start=0x" << std::hex << start + << " end=0x" << end << std::dec << std::endl; + return; + } + + for (const auto &existing : m_codeRegions) + { + if (existing.start == start && existing.end == end) + { + return; + } + } + CodeRegion region; region.start = start; region.end = end; - size_t sizeInWords = (end - start) / 4; + size_t sizeInWords = (end - start + 3u) / 4u; region.modified.resize(sizeInWords, false); m_codeRegions.push_back(region); @@ -820,15 +789,23 @@ bool PS2Memory::isAddressInRegion(uint32_t address, const CodeRegion ®ion) void PS2Memory::markModified(uint32_t address, uint32_t size) { + if (size == 0) + { + return; + } + + const uint64_t writeEnd = static_cast(address) + static_cast(size); for (auto ®ion : m_codeRegions) { - if (address + size <= region.start || address >= region.end) + const uint64_t regionStart = region.start; + const uint64_t regionEnd = region.end; + if (writeEnd <= regionStart || static_cast(address) >= regionEnd) { continue; } - uint32_t overlapStart = std::max(address, region.start); - uint32_t overlapEnd = std::min(address + size, region.end); + uint32_t overlapStart = static_cast(std::max(address, regionStart)); + uint32_t overlapEnd = static_cast(std::min(writeEnd, regionEnd)); for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) { @@ -844,15 +821,23 @@ void PS2Memory::markModified(uint32_t address, uint32_t size) bool PS2Memory::isCodeModified(uint32_t address, uint32_t size) { + if (size == 0) + { + return false; + } + + const uint64_t writeEnd = static_cast(address) + static_cast(size); for (const auto ®ion : m_codeRegions) { - if (address + size <= region.start || address >= region.end) + const uint64_t regionStart = region.start; + const uint64_t regionEnd = region.end; + if (writeEnd <= regionStart || static_cast(address) >= regionEnd) { continue; } - uint32_t overlapStart = std::max(address, region.start); - uint32_t overlapEnd = std::min(address + size, region.end); + uint32_t overlapStart = static_cast(std::max(address, regionStart)); + uint32_t overlapEnd = static_cast(std::min(writeEnd, regionEnd)); for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) { @@ -869,15 +854,23 @@ bool PS2Memory::isCodeModified(uint32_t address, uint32_t size) void PS2Memory::clearModifiedFlag(uint32_t address, uint32_t size) { + if (size == 0) + { + return; + } + + const uint64_t writeEnd = static_cast(address) + static_cast(size); for (auto ®ion : m_codeRegions) { - if (address + size <= region.start || address >= region.end) + const uint64_t regionStart = region.start; + const uint64_t regionEnd = region.end; + if (writeEnd <= regionStart || static_cast(address) >= regionEnd) { continue; } - uint32_t overlapStart = std::max(address, region.start); - uint32_t overlapEnd = std::min(address + size, region.end); + uint32_t overlapStart = static_cast(std::max(address, regionStart)); + uint32_t overlapEnd = static_cast(std::min(writeEnd, regionEnd)); for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4) { diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index cb54baa..c241c0f 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -4,7 +4,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -13,9 +16,13 @@ #define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian #define ET_EXEC 2 // Executable file +#define EM_MIPS 8 // MIPS architecture +#define PT_LOAD 1 // Loadable segment -#define EM_MIPS 8 // MIPS architecture - +static constexpr int FB_WIDTH = 640; +static constexpr int FB_HEIGHT = 448; +static constexpr uint32_t DEFAULT_FB_SIZE = FB_WIDTH * FB_HEIGHT * 4; +static constexpr uint32_t DEFAULT_FB_ADDR = (PS2_RAM_SIZE - DEFAULT_FB_SIZE - 0x10000u); struct ElfHeader { uint32_t magic; @@ -52,28 +59,130 @@ struct ProgramHeader uint32_t align; }; -#define PT_LOAD 1 // Loadable segment +namespace +{ + constexpr uint32_t kGuestHeapDefaultBase = 0x00100000u; + constexpr uint32_t kGuestHeapDefaultAlignment = 16u; + constexpr uint32_t kGuestHeapSafetyPad = 0x1000u; + constexpr uint32_t kGuestHeapHardLimit = 0x01F00000u; -static constexpr int FB_WIDTH = 640; -static constexpr int FB_HEIGHT = 448; -static constexpr uint32_t DEFAULT_FB_ADDR = 0x00100000; // location in RDRAM the guest will draw to -static constexpr uint32_t DEFAULT_FB_SIZE = FB_WIDTH * FB_HEIGHT * 4; + constexpr uint32_t COP0_CAUSE_EXCCODE_MASK = 0x0000007Cu; + constexpr uint32_t COP0_CAUSE_BD = 0x80000000u; + constexpr uint32_t COP0_STATUS_EXL = 0x00000002u; + constexpr uint32_t COP0_STATUS_BEV = 0x00400000u; + constexpr uint32_t EXCEPTION_VECTOR_GENERAL = 0x80000080u; + constexpr uint32_t EXCEPTION_VECTOR_TLB_REFILL = 0x80000000u; + constexpr uint32_t EXCEPTION_VECTOR_BOOT = 0xBFC00200u; + + uint32_t selectExceptionVector(const R5900Context *ctx, bool tlbRefill) + { + if (ctx->cop0_status & COP0_STATUS_BEV) + { + return EXCEPTION_VECTOR_BOOT; + } + return tlbRefill ? EXCEPTION_VECTOR_TLB_REFILL : EXCEPTION_VECTOR_GENERAL; + } + + void raiseCop0Exception(R5900Context *ctx, uint32_t exceptionCode, bool tlbRefill = false) + { + ctx->cop0_epc = ctx->pc; + ctx->cop0_cause = (ctx->cop0_cause & ~(COP0_CAUSE_EXCCODE_MASK | COP0_CAUSE_BD)) | + ((exceptionCode << 2) & COP0_CAUSE_EXCCODE_MASK); + ctx->cop0_status |= COP0_STATUS_EXL; + ctx->pc = selectExceptionVector(ctx, tlbRefill); + } + + std::filesystem::path normalizeAbsolutePath(const std::filesystem::path &path) + { + if (path.empty()) + { + return {}; + } + + std::error_code ec; + const std::filesystem::path absolute = std::filesystem::absolute(path, ec); + if (ec) + { + return path.lexically_normal(); + } + return absolute.lexically_normal(); + } + + PS2Runtime::IoPaths &runtimeIoPaths() + { + static PS2Runtime::IoPaths paths = []() + { + PS2Runtime::IoPaths defaults; + std::error_code ec; + const std::filesystem::path cwd = std::filesystem::current_path(ec); + defaults.elfDirectory = ec ? std::filesystem::path(".") : cwd.lexically_normal(); + defaults.hostRoot = defaults.elfDirectory; + defaults.cdRoot = defaults.elfDirectory; + return defaults; + }(); + + return paths; + } + + uint32_t readGuestU32Wrapped(const uint8_t *rdram, uint32_t addr) + { + if (!rdram) + { + return 0; + } + + uint32_t value = 0; + value |= static_cast(rdram[(addr + 0u) & PS2_RAM_MASK]) << 0; + value |= static_cast(rdram[(addr + 1u) & PS2_RAM_MASK]) << 8; + value |= static_cast(rdram[(addr + 2u) & PS2_RAM_MASK]) << 16; + value |= static_cast(rdram[(addr + 3u) & PS2_RAM_MASK]) << 24; + return value; + } + + std::string readGuestPrintableString(const uint8_t *rdram, uint32_t addr, size_t maxLen) + { + std::string out; + if (!rdram || maxLen == 0) + { + return out; + } + + out.reserve(std::min(maxLen, 64)); + for (size_t i = 0; i < maxLen; ++i) + { + const char ch = static_cast(rdram[(addr + static_cast(i)) & PS2_RAM_MASK]); + if (ch == '\0') + { + break; + } + if (ch >= 0x20 && ch < 0x7F) + { + out.push_back(ch); + } + else + { + out.push_back('.'); + } + } + return out; + } +} static void UploadFrame(Texture2D &tex, PS2Runtime *rt) { // Try to use GS dispfb/display registers to locate the visible buffer. const GSRegisters &gs = rt->memory().gs(); - // DISPFBUF1 fields: FBP (bits 0-8) * 2048 bytes, FBW (bits 10-15) blocks of 64 pixels, PSM (bits 16-20) + // DISPFBUF1 fields: FBP bits 0-8, FBW bits 9-14, PSM bits 15-19. uint32_t dispfb = static_cast(gs.dispfb1 & 0xFFFFFFFFULL); uint32_t fbp = dispfb & 0x1FF; - uint32_t fbw = (dispfb >> 10) & 0x3F; - uint32_t psm = (dispfb >> 16) & 0x1F; + uint32_t fbw = (dispfb >> 9) & 0x3F; + uint32_t psm = (dispfb >> 15) & 0x1F; - // DISPLAY1 fields: DX,DY not used here; DW,DH are width/height minus 1 (11 bits each) + // DISPLAY1 fields used here: DW bits 32-43, DH bits 44-54. uint64_t display64 = gs.display1; - uint32_t dw = static_cast((display64 >> 23) & 0x7FF); - uint32_t dh = static_cast((display64 >> 34) & 0x7FF); + uint32_t dw = static_cast((display64 >> 32) & 0xFFF); + uint32_t dh = static_cast((display64 >> 44) & 0x7FF); // Default to 640x448 if regs look strange. uint32_t width = (dw + 1); @@ -87,49 +196,30 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt) if (height > FB_HEIGHT) height = FB_HEIGHT; - static uint64_t prev_dispfb = ~0ull; - static uint64_t prev_display = ~0ull; - static bool vramLogged = false; - if (gs.dispfb1 != prev_dispfb || gs.display1 != prev_display) - { - std::cout << "[GS] dispfb1=0x" << std::hex << gs.dispfb1 - << " display1=0x" << gs.display1 << std::dec << std::endl; - prev_dispfb = gs.dispfb1; - prev_display = gs.display1; - // Allow VRAM peek to re-log when the buffer changes. - vramLogged = false; - } - - // Only handle PSMCT32 (0) in this minimal blitter. + // Only handle PSMCT32 (0). if (psm != 0) { - uint8_t *src = rt->memory().getRDRAM() + (DEFAULT_FB_ADDR & 0x1FFFFFFF); - UpdateTexture(tex, src); + // I can`t stand a random RAM glitch screen so lets use some magenta to calm down + Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, MAGENTA); + UpdateTexture(tex, blank.data); + UnloadImage(blank); return; } uint32_t baseBytes = fbp * 2048; - uint32_t strideBytes = (fbw ? fbw : (FB_WIDTH / 64)) * 64 * 4; + const uint32_t bytesPerPixel = (psm == 2u || psm == 0x0Au) ? 2u : 4u; + uint32_t strideBytes = (fbw ? fbw : (FB_WIDTH / 64)) * 64 * bytesPerPixel; + + std::vector scratch(FB_WIDTH * FB_HEIGHT * 4, 0); // maybe we can do this static + uint8_t *rdram = rt->memory().getRDRAM(); uint8_t *gsvram = rt->memory().getGSVRAM(); - std::vector scratch(FB_WIDTH * FB_HEIGHT * 4, 0); - for (uint32_t y = 0; y < height; ++y) { uint32_t srcOff = baseBytes + y * strideBytes; uint32_t dstOff = y * FB_WIDTH * 4; uint32_t copyW = width * 4; uint32_t srcIdx = srcOff; - if (!vramLogged) - { - uint32_t sum = 0; - for (int i = 0; i < 32 && (srcIdx + i) < PS2_GS_VRAM_SIZE; ++i) - { - sum += gsvram[srcIdx + i]; - } - std::cout << "[VRAM peek] sum first32=0x" << std::hex << sum << std::dec << std::endl; - vramLogged = true; - } if (srcIdx + copyW <= PS2_GS_VRAM_SIZE && gsvram) { std::memcpy(&scratch[dstOff], gsvram + srcIdx, copyW); @@ -143,21 +233,6 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt) } } - // Peek first few bytes to see if anything is drawn. - uint32_t peekOff = 0; - uint32_t sum = 0; - for (int i = 0; i < 32; ++i) - { - sum += scratch[peekOff + i]; - } - static int peekCount = 0; - if (peekCount < 4) - { - std::cout << "[FB peek] sum first32=0x" << std::hex << sum << std::dec - << " w=" << width << " h=" << height << std::endl; - ++peekCount; - } - UpdateTexture(tex, scratch.data()); } @@ -173,6 +248,12 @@ PS2Runtime::PS2Runtime() m_functionTable.clear(); m_loadedModules.clear(); + m_guestHeapBlocks.clear(); + m_guestHeapBase = kGuestHeapDefaultBase; + m_guestHeapEnd = kGuestHeapDefaultBase; + m_guestHeapLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE); + m_guestHeapSuggestedBase = kGuestHeapDefaultBase; + m_guestHeapConfigured = false; } PS2Runtime::~PS2Runtime() @@ -199,6 +280,8 @@ bool PS2Runtime::initialize(const char *title) bool PS2Runtime::loadELF(const std::string &elfPath) { + configureIoPathsFromElf(elfPath); + std::ifstream file(elfPath, std::ios::binary); if (!file) { @@ -223,6 +306,8 @@ bool PS2Runtime::loadELF(const std::string &elfPath) m_cpuContext.pc = header.entry; + uint32_t maxLoadedRdramEnd = kGuestHeapDefaultBase; + for (uint16_t i = 0; i < header.phnum; i++) { ProgramHeader ph; @@ -233,7 +318,9 @@ bool PS2Runtime::loadELF(const std::string &elfPath) { std::cout << "Loading segment: 0x" << std::hex << ph.vaddr << " - 0x" << (ph.vaddr + ph.memsz) - << " (size: 0x" << ph.memsz << ")" << std::dec << std::endl; + << " (filesz: 0x" << ph.filesz + << ", memsz: 0x" << ph.memsz << ")" + << std::dec << std::endl; // Allocate temporary buffer for the segment std::vector buffer(ph.filesz); @@ -260,6 +347,15 @@ bool PS2Runtime::loadELF(const std::string &elfPath) std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); } + if (!(ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) + { + const uint64_t segmentEnd = static_cast(physAddr) + static_cast(ph.memsz); + if (segmentEnd <= PS2_RAM_SIZE) + { + maxLoadedRdramEnd = std::max(maxLoadedRdramEnd, static_cast(segmentEnd)); + } + } + // Track executable regions for self-modifying code invalidation if (ph.flags & 0x1) // PF_X { @@ -268,6 +364,22 @@ bool PS2Runtime::loadELF(const std::string &elfPath) } } + const uint32_t paddedEnd = (maxLoadedRdramEnd > (PS2_RAM_SIZE - kGuestHeapSafetyPad)) + ? PS2_RAM_SIZE + : (maxLoadedRdramEnd + kGuestHeapSafetyPad); + const uint32_t suggestedHeapBase = alignGuestHeapValue(paddedEnd, kGuestHeapDefaultAlignment); + { + std::lock_guard lock(m_guestHeapMutex); + if (!m_guestHeapConfigured) + { + const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE); + m_guestHeapSuggestedBase = std::min(suggestedHeapBase, hardLimit); + m_guestHeapBase = m_guestHeapSuggestedBase; + m_guestHeapEnd = m_guestHeapSuggestedBase; + m_guestHeapLimit = hardLimit; + } + } + LoadedModule module; module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1); module.baseAddress = 0x00100000; // Typical base address for PS2 executables @@ -280,6 +392,57 @@ bool PS2Runtime::loadELF(const std::string &elfPath) return true; } +const PS2Runtime::IoPaths &PS2Runtime::getIoPaths() +{ + return runtimeIoPaths(); +} + +void PS2Runtime::setIoPaths(const IoPaths &paths) +{ + IoPaths normalized = paths; + normalized.elfPath = normalizeAbsolutePath(normalized.elfPath); + normalized.elfDirectory = normalizeAbsolutePath(normalized.elfDirectory); + normalized.hostRoot = normalizeAbsolutePath(normalized.hostRoot); + normalized.cdRoot = normalizeAbsolutePath(normalized.cdRoot); + normalized.cdImage = normalizeAbsolutePath(normalized.cdImage); + + if (normalized.elfDirectory.empty() && !normalized.elfPath.empty()) + { + normalized.elfDirectory = normalized.elfPath.parent_path(); + } + + if (normalized.hostRoot.empty()) + { + normalized.hostRoot = normalized.elfDirectory; + } + if (normalized.cdRoot.empty()) + { + normalized.cdRoot = normalized.elfDirectory; + } + + runtimeIoPaths() = normalized; +} + +void PS2Runtime::configureIoPathsFromElf(const std::string &elfPath) +{ + IoPaths paths = runtimeIoPaths(); + paths.elfPath = normalizeAbsolutePath(std::filesystem::path(elfPath)); + if (!paths.elfPath.empty()) + { + paths.elfDirectory = paths.elfPath.parent_path(); + } + + if (!paths.elfDirectory.empty()) + { + paths.hostRoot = paths.elfDirectory; + paths.cdRoot = paths.elfDirectory; + } + + paths.cdImage.clear(); + + setIoPaths(paths); +} + void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func) { m_functionTable[address] = func; @@ -303,6 +466,8 @@ PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address) static RecompiledFunction defaultFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { std::cerr << "Error: Called unimplemented function at address 0x" << std::hex << ctx->pc << std::dec << std::endl; + + runtime->requestStop(); }; return defaultFunction; @@ -312,9 +477,12 @@ void PS2Runtime::SignalException(R5900Context *ctx, PS2Exception exception) { if (exception == EXCEPTION_INTEGER_OVERFLOW) { - // PS2 behavior: jump to exception handler HandleIntegerOverflow(ctx); + return; } + + raiseCop0Exception(ctx, static_cast(exception), + exception == EXCEPTION_TLB_REFILL); } void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address) @@ -330,86 +498,727 @@ void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint3 } ++count; - // Clear/seed status so dependent code sees "success". + // Seed status so dependent code sees success. ctx->vu0_clip_flags = 0; ctx->vu0_clip_flags2 = 0; ctx->vu0_mac_flags = 0; ctx->vu0_status = 0; ctx->vu0_q = 1.0f; - - // TODO: Implement a real interpreter. For now, no register mutations beyond defaults. } void PS2Runtime::vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address) { - // VCALLMS/VCALLMSR paths both end up here; reuse the same minimal stub. + // VCALLMS and VCALLMSR both route here. executeVU0Microprogram(rdram, ctx, address); } void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx) { - std::cout << "Syscall encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + handleSyscall(rdram, ctx, 0); +} + +void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx, uint32_t encodedSyscallId) +{ + // Try immediate first + if (encodedSyscallId != 0 && ps2_syscalls::dispatchNumericSyscall(encodedSyscallId, rdram, ctx, this)) + { + return; + } + + // Try $v1 (standard) + const uint32_t syscallFromV1 = getRegU32(ctx, 3); // $v1 + if (ps2_syscalls::dispatchNumericSyscall(syscallFromV1, rdram, ctx, this)) + { + return; + } + + // Try $v0 (negative syscalls) + const uint32_t syscallFromV0 = getRegU32(ctx, 2); // $v0 (some ABIs) + if (syscallFromV0 != syscallFromV1 && + ps2_syscalls::dispatchNumericSyscall(syscallFromV0, rdram, ctx, this)) + { + return; + } + + // God help you + ps2_syscalls::TODO(rdram, ctx, this, encodedSyscallId); } void PS2Runtime::handleBreak(uint8_t *rdram, R5900Context *ctx) { - std::cout << "Break encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + raiseCop0Exception(ctx, EXCEPTION_BREAKPOINT); } void PS2Runtime::handleTrap(uint8_t *rdram, R5900Context *ctx) { - std::cout << "Trap encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + raiseCop0Exception(ctx, EXCEPTION_TRAP); } void PS2Runtime::handleTLBR(uint8_t *rdram, R5900Context *ctx) { - std::cout << "TLBR (TLB Read) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + uint32_t vpn = 0; + uint32_t pfn = 0; + uint32_t mask = 0; + bool valid = false; + + const uint32_t index = ctx->cop0_index & 0x3Fu; + if (!m_memory.tlbRead(index, vpn, pfn, mask, valid)) + { + raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION); + return; + } + + // Preserve low ASID bits in EntryHi. + ctx->cop0_entryhi = (ctx->cop0_entryhi & 0x00000FFFu) | (vpn & 0xFFFFF000u); + ctx->cop0_entrylo0 = (ctx->cop0_entrylo0 & ~0x03FFFFC2u) | + ((pfn & 0x000FFFFFu) << 6) | + (valid ? 0x2u : 0u); + ctx->cop0_pagemask = mask & 0x01FFE000u; } void PS2Runtime::handleTLBWI(uint8_t *rdram, R5900Context *ctx) { - std::cout << "TLBWI (TLB Write Indexed) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + const uint32_t index = ctx->cop0_index & 0x3Fu; + const uint32_t vpn = ctx->cop0_entryhi & 0xFFFFF000u; + const uint32_t pfn = (ctx->cop0_entrylo0 >> 6) & 0x000FFFFFu; + const uint32_t mask = ctx->cop0_pagemask & 0x01FFE000u; + const bool valid = (ctx->cop0_entrylo0 & 0x2u) != 0u; + + if (!m_memory.tlbWrite(index, vpn, pfn, mask, valid)) + { + raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION); + } } void PS2Runtime::handleTLBWR(uint8_t *rdram, R5900Context *ctx) { - std::cout << "TLBWR (TLB Write Random) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + const uint32_t entryCount = static_cast(m_memory.tlbEntryCount()); + if (entryCount == 0) + { + raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION); + return; + } + + const uint32_t wired = std::min(ctx->cop0_wired, entryCount - 1); + uint32_t random = ctx->cop0_random % entryCount; + if (random < wired) + { + random = wired; + } + + const uint32_t vpn = ctx->cop0_entryhi & 0xFFFFF000u; + const uint32_t pfn = (ctx->cop0_entrylo0 >> 6) & 0x000FFFFFu; + const uint32_t mask = ctx->cop0_pagemask & 0x01FFE000u; + const bool valid = (ctx->cop0_entrylo0 & 0x2u) != 0u; + + if (!m_memory.tlbWrite(random, vpn, pfn, mask, valid)) + { + raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION); + return; + } + + // Keep COP0 bookkeeping in sync with the selected slot. + ctx->cop0_index = (ctx->cop0_index & ~0x3Fu) | (random & 0x3Fu); + ctx->cop0_random = (random <= wired) ? (entryCount - 1) : (random - 1); } void PS2Runtime::handleTLBP(uint8_t *rdram, R5900Context *ctx) { - std::cout << "TLBP (TLB Probe) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + const int32_t index = m_memory.tlbProbe(ctx->cop0_entryhi & 0xFFFFF000u); + if (index >= 0) + { + ctx->cop0_index = (ctx->cop0_index & ~0x8000003Fu) | + (static_cast(index) & 0x3Fu); + } + else + { + // MIPS sets probe failure bit (P) in Index[31]. + ctx->cop0_index |= 0x80000000u; + } } void PS2Runtime::clearLLBit(R5900Context *ctx) { - ctx->cop0_status &= ~0x00000002; // LL bit is bit 1 in the status register - std::cout << "LL bit cleared at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; + // LL/SC reservation is tracked separately from COP0 Status. + ctx->llbit = 0; + ctx->lladdr = 0; +} + +uint32_t PS2Runtime::alignGuestHeapValue(uint32_t value, uint32_t alignment) +{ + if (alignment == 0) + { + return value; + } + + const uint32_t mask = alignment - 1u; + if (value > (std::numeric_limits::max() - mask)) + { + return std::numeric_limits::max(); + } + return (value + mask) & ~mask; +} + +bool PS2Runtime::isGuestHeapAlignmentValid(uint32_t alignment) +{ + return alignment != 0u && (alignment & (alignment - 1u)) == 0u; +} + +uint32_t PS2Runtime::normalizeGuestHeapAlignment(uint32_t alignment) +{ + if (!isGuestHeapAlignmentValid(alignment)) + { + return kGuestHeapDefaultAlignment; + } + return std::max(alignment, kGuestHeapDefaultAlignment); +} + +uint32_t PS2Runtime::clampGuestHeapBase(uint32_t guestBase) const +{ + uint32_t normalized = guestBase; + if (normalized >= PS2_RAM_SIZE) + { + normalized &= PS2_RAM_MASK; + } + const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE); + return std::min(normalized, hardLimit); +} + +uint32_t PS2Runtime::clampGuestHeapLimit(uint32_t guestLimit) const +{ + const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE); + if (guestLimit == 0u || guestLimit > hardLimit) + { + return hardLimit; + } + return guestLimit; +} + +void PS2Runtime::resetGuestHeapLocked(uint32_t guestBase, uint32_t guestLimit) +{ + uint32_t base = alignGuestHeapValue(clampGuestHeapBase(guestBase), kGuestHeapDefaultAlignment); + uint32_t limit = clampGuestHeapLimit(guestLimit); + if (base == 0u) + { + const uint32_t fallbackBase = (m_guestHeapSuggestedBase != 0u) ? m_guestHeapSuggestedBase : kGuestHeapDefaultBase; + base = alignGuestHeapValue(clampGuestHeapBase(fallbackBase), kGuestHeapDefaultAlignment); + } + + if (limit <= base) + { + base = alignGuestHeapValue(clampGuestHeapBase(m_guestHeapSuggestedBase), kGuestHeapDefaultAlignment); + limit = clampGuestHeapLimit(0u); + } + + if (limit <= base) + { + base = 0u; + limit = 0u; + } + + m_guestHeapBlocks.clear(); + if (limit > base) + { + m_guestHeapBlocks.push_back({base, limit - base, true}); + } + + m_guestHeapBase = base; + m_guestHeapEnd = base; + m_guestHeapLimit = limit; + m_guestHeapConfigured = true; +} + +void PS2Runtime::ensureGuestHeapInitializedLocked() +{ + if (m_guestHeapConfigured) + { + return; + } + + const uint32_t suggested = (m_guestHeapSuggestedBase == 0u) ? kGuestHeapDefaultBase : m_guestHeapSuggestedBase; + resetGuestHeapLocked(suggested, clampGuestHeapLimit(0u)); +} + +int32_t PS2Runtime::findGuestHeapBlockIndexLocked(uint32_t guestAddr) const +{ + const uint32_t normalizedAddr = guestAddr & PS2_RAM_MASK; + for (size_t i = 0; i < m_guestHeapBlocks.size(); ++i) + { + const GuestHeapBlock &block = m_guestHeapBlocks[i]; + if (!block.free && block.addr == normalizedAddr) + { + return static_cast(i); + } + } + return -1; +} + +uint32_t PS2Runtime::allocateGuestBlockLocked(uint32_t size, uint32_t alignment) +{ + if (size == 0u) + { + return 0u; + } + + const uint32_t normalizedAlignment = normalizeGuestHeapAlignment(alignment); + if (size > (std::numeric_limits::max() - (kGuestHeapDefaultAlignment - 1u))) + { + return 0u; + } + + const uint32_t allocSize = alignGuestHeapValue(size, kGuestHeapDefaultAlignment); + if (allocSize == 0u) + { + return 0u; + } + + for (size_t i = 0; i < m_guestHeapBlocks.size(); ++i) + { + const GuestHeapBlock block = m_guestHeapBlocks[i]; + if (!block.free) + { + continue; + } + + const uint64_t blockStart = block.addr; + const uint64_t blockEnd = blockStart + static_cast(block.size); + const uint32_t alignedAddr = alignGuestHeapValue(block.addr, normalizedAlignment); + if (alignedAddr < block.addr) + { + continue; + } + + const uint64_t alignedStart = alignedAddr; + if (alignedStart > blockEnd) + { + continue; + } + + const uint64_t allocEnd = alignedStart + static_cast(allocSize); + if (allocEnd > blockEnd) + { + continue; + } + + const uint32_t prefixSize = static_cast(alignedStart - blockStart); + const uint32_t suffixSize = static_cast(blockEnd - allocEnd); + + std::vector replacement; + replacement.reserve(3); + if (prefixSize > 0u) + { + replacement.push_back({block.addr, prefixSize, true}); + } + replacement.push_back({alignedAddr, allocSize, false}); + if (suffixSize > 0u) + { + replacement.push_back({static_cast(allocEnd), suffixSize, true}); + } + + m_guestHeapBlocks.erase(m_guestHeapBlocks.begin() + static_cast(i)); + m_guestHeapBlocks.insert(m_guestHeapBlocks.begin() + static_cast(i), + replacement.begin(), + replacement.end()); + + m_guestHeapEnd = std::max(m_guestHeapEnd, static_cast(allocEnd)); + return alignedAddr; + } + + return 0u; +} + +void PS2Runtime::coalesceGuestHeapLocked() +{ + if (m_guestHeapBlocks.empty()) + { + return; + } + + size_t i = 1; + while (i < m_guestHeapBlocks.size()) + { + GuestHeapBlock &prev = m_guestHeapBlocks[i - 1]; + GuestHeapBlock &curr = m_guestHeapBlocks[i]; + const uint64_t prevEnd = static_cast(prev.addr) + static_cast(prev.size); + if (prev.free && curr.free && prevEnd == curr.addr) + { + prev.size += curr.size; + m_guestHeapBlocks.erase(m_guestHeapBlocks.begin() + static_cast(i)); + continue; + } + ++i; + } +} + +void PS2Runtime::freeGuestBlockLocked(uint32_t guestAddr) +{ + const int32_t index = findGuestHeapBlockIndexLocked(guestAddr); + if (index < 0) + { + return; + } + + m_guestHeapBlocks[static_cast(index)].free = true; + coalesceGuestHeapLocked(); +} + +void PS2Runtime::configureGuestHeap(uint32_t guestBase, uint32_t guestLimit) +{ + std::lock_guard lock(m_guestHeapMutex); + uint32_t normalizedBase = alignGuestHeapValue(clampGuestHeapBase(guestBase), kGuestHeapDefaultAlignment); + if (normalizedBase == 0u) + { + normalizedBase = (m_guestHeapSuggestedBase != 0u) ? m_guestHeapSuggestedBase : kGuestHeapDefaultBase; + } + m_guestHeapSuggestedBase = normalizedBase; + resetGuestHeapLocked(normalizedBase, guestLimit); +} + +uint32_t PS2Runtime::guestMalloc(uint32_t size, uint32_t alignment) +{ + std::lock_guard lock(m_guestHeapMutex); + ensureGuestHeapInitializedLocked(); + return allocateGuestBlockLocked(size, alignment); +} + +uint32_t PS2Runtime::guestCalloc(uint32_t count, uint32_t size, uint32_t alignment) +{ + if (count == 0u || size == 0u) + { + return 0u; + } + if (count > (std::numeric_limits::max() / size)) + { + return 0u; + } + + const uint32_t totalSize = count * size; + const uint32_t guestAddr = guestMalloc(totalSize, alignment); + if (guestAddr != 0u) + { + uint8_t *rdram = m_memory.getRDRAM(); + if (rdram) + { + std::memset(rdram + guestAddr, 0, totalSize); + } + } + + return guestAddr; +} + +uint32_t PS2Runtime::guestRealloc(uint32_t guestAddr, uint32_t newSize, uint32_t alignment) +{ + if (guestAddr == 0u) + { + return guestMalloc(newSize, alignment); + } + if (newSize == 0u) + { + guestFree(guestAddr); + return 0u; + } + + if (newSize > (std::numeric_limits::max() - (kGuestHeapDefaultAlignment - 1u))) + { + return 0u; + } + + const uint32_t normalizedAlignment = normalizeGuestHeapAlignment(alignment); + const uint32_t requestedSize = alignGuestHeapValue(newSize, kGuestHeapDefaultAlignment); + + std::lock_guard lock(m_guestHeapMutex); + ensureGuestHeapInitializedLocked(); + + const int32_t index = findGuestHeapBlockIndexLocked(guestAddr); + if (index < 0) + { + return 0u; + } + + const size_t blockIndex = static_cast(index); + const uint32_t oldAddr = m_guestHeapBlocks[blockIndex].addr; + const uint32_t oldSize = m_guestHeapBlocks[blockIndex].size; + + if (requestedSize <= oldSize) + { + if (requestedSize < oldSize) + { + const uint32_t tailAddr = oldAddr + requestedSize; + const uint32_t tailSize = oldSize - requestedSize; + m_guestHeapBlocks[blockIndex].size = requestedSize; + m_guestHeapBlocks.insert(m_guestHeapBlocks.begin() + static_cast(blockIndex + 1u), + GuestHeapBlock{tailAddr, tailSize, true}); + coalesceGuestHeapLocked(); + } + return oldAddr; + } + + if (blockIndex + 1u < m_guestHeapBlocks.size()) + { + GuestHeapBlock &next = m_guestHeapBlocks[blockIndex + 1u]; + const uint64_t blockEnd = static_cast(m_guestHeapBlocks[blockIndex].addr) + + static_cast(m_guestHeapBlocks[blockIndex].size); + if (next.free && blockEnd == next.addr) + { + const uint64_t combined = static_cast(m_guestHeapBlocks[blockIndex].size) + + static_cast(next.size); + if (combined >= requestedSize) + { + const uint32_t extraNeeded = requestedSize - m_guestHeapBlocks[blockIndex].size; + m_guestHeapBlocks[blockIndex].size = requestedSize; + if (next.size == extraNeeded) + { + m_guestHeapBlocks.erase(m_guestHeapBlocks.begin() + static_cast(blockIndex + 1u)); + } + else + { + next.addr += extraNeeded; + next.size -= extraNeeded; + } + m_guestHeapEnd = std::max(m_guestHeapEnd, oldAddr + requestedSize); + return oldAddr; + } + } + } + + const uint32_t newAddr = allocateGuestBlockLocked(newSize, normalizedAlignment); + if (newAddr == 0u) + { + return 0u; + } + + uint8_t *rdram = m_memory.getRDRAM(); + if (rdram) + { + const uint32_t copyBytes = std::min(oldSize, newSize); + std::memmove(rdram + newAddr, rdram + oldAddr, copyBytes); + } + + freeGuestBlockLocked(oldAddr); + return newAddr; +} + +void PS2Runtime::guestFree(uint32_t guestAddr) +{ + if (guestAddr == 0u) + { + return; + } + + std::lock_guard lock(m_guestHeapMutex); + ensureGuestHeapInitializedLocked(); + freeGuestBlockLocked(guestAddr); +} + +uint32_t PS2Runtime::guestHeapBase() const +{ + std::lock_guard lock(m_guestHeapMutex); + return m_guestHeapConfigured ? m_guestHeapBase : m_guestHeapSuggestedBase; +} + +uint32_t PS2Runtime::guestHeapEnd() const +{ + std::lock_guard lock(m_guestHeapMutex); + return m_guestHeapConfigured ? m_guestHeapEnd : m_guestHeapSuggestedBase; +} + +void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx) +{ + uint32_t lastPc = 0; + int stuckCount = 0; + + while (!isStopRequested()) + { + const uint32_t pc = ctx->pc; + + // this helps a lot but lets not forget to remove later + if (pc == lastPc) + { + stuckCount++; + if (stuckCount > 1000) + { + std::cerr << "CPU Stuck at PC 0x" << std::hex << pc << ". PC not updating." << std::endl; + requestStop(); + break; + } + } + else + { + stuckCount = 0; + } + lastPc = pc; + + m_debugPc.store(pc, std::memory_order_relaxed); + m_debugRa.store(static_cast(_mm_extract_epi32(ctx->r[31], 0)), std::memory_order_relaxed); + m_debugSp.store(static_cast(_mm_extract_epi32(ctx->r[29], 0)), std::memory_order_relaxed); + m_debugGp.store(static_cast(_mm_extract_epi32(ctx->r[28], 0)), std::memory_order_relaxed); + + RecompiledFunction fn = lookupFunction(pc); + + fn(rdram, ctx, this); + + if (ctx->pc == 0u) + { + requestStop(); + break; + } + } +} + +uint8_t PS2Runtime::Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) +{ + try + { + return m_memory.read8(vaddr); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD); + return 0; + } +} + +uint16_t PS2Runtime::Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) +{ + try + { + return m_memory.read16(vaddr); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD); + return 0; + } +} + +uint32_t PS2Runtime::Load32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) +{ + try + { + return m_memory.read32(vaddr); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD); + return 0; + } +} + +uint64_t PS2Runtime::Load64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) +{ + try + { + return m_memory.read64(vaddr); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD); + return 0; + } +} + +__m128i PS2Runtime::Load128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) +{ + try + { + return m_memory.read128(vaddr); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD); + return _mm_setzero_si128(); + } +} + +void PS2Runtime::Store8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint8_t value) +{ + ps2TraceGuestWrite(rdram, vaddr, 1u, value, 0u, "WRITE8", ctx); + try + { + m_memory.write8(vaddr, value); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE); + } +} + +void PS2Runtime::Store16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint16_t value) +{ + ps2TraceGuestWrite(rdram, vaddr, 2u, value, 0u, "WRITE16", ctx); + try + { + m_memory.write16(vaddr, value); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE); + } +} + +void PS2Runtime::Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value) +{ + ps2TraceGuestWrite(rdram, vaddr, 4u, value, 0u, "WRITE32", ctx); + try + { + m_memory.write32(vaddr, value); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE); + } +} + +void PS2Runtime::Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value) +{ + ps2TraceGuestWrite(rdram, vaddr, 8u, value, 0u, "WRITE64", ctx); + try + { + m_memory.write64(vaddr, value); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE); + } +} + +void PS2Runtime::Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value) +{ + alignas(16) uint64_t _parts[2]; + _mm_storeu_si128(reinterpret_cast<__m128i *>(_parts), value); + ps2TraceGuestWrite(rdram, vaddr, 16u, _parts[0], _parts[1], "WRITE128", ctx); + try + { + m_memory.write128(vaddr, value); + } + catch (const std::exception &) + { + SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE); + } +} + +void PS2Runtime::requestStop() +{ + m_stopRequested.store(true, std::memory_order_relaxed); +} + +bool PS2Runtime::isStopRequested() const +{ + return m_stopRequested.load(std::memory_order_relaxed); } void PS2Runtime::HandleIntegerOverflow(R5900Context *ctx) { - std::cerr << "Integer overflow exception at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl; - - // Set the EPC (Exception Program Counter) to the current PC - m_cpuContext.cop0_epc = ctx->pc; - - // Set the cause register to indicate an integer overflow - m_cpuContext.cop0_cause |= (EXCEPTION_INTEGER_OVERFLOW << 2); - - // Jump to the exception handler (usually at 0x80000000) - m_cpuContext.pc = 0x80000000; // Default PS2 exception handler address + raiseCop0Exception(ctx, EXCEPTION_INTEGER_OVERFLOW); } void PS2Runtime::run() { - RecompiledFunction entryPoint = lookupFunction(m_cpuContext.pc); + m_cpuContext.r[4] = _mm_setzero_si128(); + m_cpuContext.r[5] = _mm_setzero_si128(); + m_cpuContext.r[29] = _mm_set_epi64x(0, static_cast(PS2_RAM_SIZE - 0x10u)); - m_cpuContext.r[4] = _mm_set1_epi32(0); // A0 = 0 (argc) - m_cpuContext.r[5] = _mm_set1_epi32(0); // A1 = 0 (argv) - m_cpuContext.r[29] = _mm_set1_epi32(0x02000000); // SP = top of RAM - - std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; + std::cout << "Starting execution at address 0x" << std::hex << m_debugPc.load(std::memory_order_relaxed) << std::dec << std::endl; // A blank image to use as a framebuffer Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK); @@ -418,13 +1227,14 @@ void PS2Runtime::run() g_activeThreads.store(1, std::memory_order_relaxed); - std::thread gameThread([&, entryPoint]() + std::thread gameThread([&]() { ThreadNaming::SetCurrentThreadName("GameThread"); try { - entryPoint(m_memory.getRDRAM(), &m_cpuContext, this); - std::cout << "Game thread returned. PC=0x" << std::hex << m_cpuContext.pc + dispatchLoop(m_memory.getRDRAM(), &m_cpuContext); + uint32_t pc = m_debugPc.load(std::memory_order_relaxed); + std::cout << "Game thread returned. PC=0x" << std::hex << pc << " RA=0x" << static_cast(_mm_extract_epi32(m_cpuContext.r[31], 0)) << std::dec << std::endl; } catch (const std::exception &e) @@ -436,13 +1246,18 @@ void PS2Runtime::run() uint64_t tick = 0; while (g_activeThreads.load(std::memory_order_relaxed) > 0) { + const uint32_t pc = m_debugPc.load(std::memory_order_relaxed); + const uint32_t ra = m_debugRa.load(std::memory_order_relaxed); + const uint32_t sp = m_debugSp.load(std::memory_order_relaxed); + const uint32_t gp = m_debugGp.load(std::memory_order_relaxed); + if ((tick++ % 120) == 0) { std::cout << "[run] activeThreads=" << g_activeThreads.load(std::memory_order_relaxed); - std::cout << " pc=0x" << std::hex << m_cpuContext.pc - << " ra=0x" << static_cast(_mm_extract_epi32(m_cpuContext.r[31], 0)) - << " sp=0x" << static_cast(_mm_extract_epi32(m_cpuContext.r[29], 0)) - << " gp=0x" << static_cast(_mm_extract_epi32(m_cpuContext.r[28], 0)) << std::dec << std::endl; + std::cout << " pc=0x" << std::hex << pc + << " ra=0x" << ra + << " sp=0x" << sp + << " gp=0x" << gp; } if ((tick % 600) == 0) { @@ -473,6 +1288,7 @@ void PS2Runtime::run() if (WindowShouldClose()) { std::cout << "[run] window close requested, breaking out of loop" << std::endl; + requestStop(); break; } } diff --git a/ps2xRuntime/src/lib/ps2_stubs.cpp b/ps2xRuntime/src/lib/ps2_stubs.cpp index e243ac0..170f7d8 100644 --- a/ps2xRuntime/src/lib/ps2_stubs.cpp +++ b/ps2xRuntime/src/lib/ps2_stubs.cpp @@ -1,17 +1,483 @@ #include "ps2_stubs.h" #include "ps2_runtime.h" +#include "ps2_syscalls.h" #include +#include +#include +#include #include #include #include #include +#include +#include +#include #include #include #include #include +#ifndef PS2_CD_REMAP_IDX_TO_AFS +#define PS2_CD_REMAP_IDX_TO_AFS 1 +#endif + namespace { + constexpr uint32_t kCdSectorSize = 2048; + constexpr uint32_t kCdPseudoLbnStart = 0x00100000; + + struct CdFileEntry + { + std::filesystem::path hostPath; + uint32_t sizeBytes = 0; + uint32_t baseLbn = 0; + uint32_t sectors = 0; + }; + + std::unordered_map g_cdFilesByKey; + std::unordered_map g_cdLeafIndex; + std::filesystem::path g_cdLeafIndexRoot; + bool g_cdLeafIndexBuilt = false; + uint32_t g_nextPseudoLbn = kCdPseudoLbnStart; + int32_t g_lastCdError = 0; + uint32_t g_cdMode = 0; + uint32_t g_cdStreamingLbn = 0; + bool g_cdInitialized = false; + + constexpr uint32_t kIopHeapBase = 0x01A00000; + constexpr uint32_t kIopHeapLimit = 0x01F00000; + constexpr uint32_t kIopHeapAlign = 16; + uint32_t g_iopHeapNext = kIopHeapBase; + + std::string toLowerAscii(std::string value) + { + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char c) + { return static_cast(std::tolower(c)); }); + return value; + } + + std::string stripIsoVersionSuffix(std::string value) + { + const std::size_t semicolon = value.find(';'); + if (semicolon == std::string::npos) + { + return value; + } + + bool numericSuffix = semicolon + 1 < value.size(); + for (std::size_t i = semicolon + 1; i < value.size(); ++i) + { + if (!std::isdigit(static_cast(value[i]))) + { + numericSuffix = false; + break; + } + } + + if (numericSuffix) + { + value.erase(semicolon); + } + return value; + } + + std::string normalizePathSeparators(std::string value) + { + std::replace(value.begin(), value.end(), '\\', '/'); + return value; + } + + void trimLeadingSeparators(std::string &value) + { + while (!value.empty() && (value.front() == '/' || value.front() == '\\')) + { + value.erase(value.begin()); + } + } + + std::string normalizeCdPathNoPrefix(std::string path) + { + path = normalizePathSeparators(std::move(path)); + std::string lower = toLowerAscii(path); + if (lower.rfind("cdrom0:", 0) == 0) + { + path = path.substr(7); + } + else if (lower.rfind("cdrom:", 0) == 0) + { + path = path.substr(6); + } + + trimLeadingSeparators(path); + while (!path.empty() && std::isspace(static_cast(path.front()))) + { + path.erase(path.begin()); + } + while (!path.empty() && std::isspace(static_cast(path.back()))) + { + path.pop_back(); + } + path = stripIsoVersionSuffix(std::move(path)); + return path; + } + + std::filesystem::path getCdRootPath() + { + const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); + if (!paths.cdRoot.empty()) + { + return paths.cdRoot; + } + if (!paths.elfDirectory.empty()) + { + return paths.elfDirectory; + } + + std::error_code ec; + const std::filesystem::path cwd = std::filesystem::current_path(ec); + return ec ? std::filesystem::path(".") : cwd.lexically_normal(); + } + + std::filesystem::path getCdImagePath() + { + return PS2Runtime::getIoPaths().cdImage; + } + + uint32_t sectorsForBytes(uint64_t byteCount) + { + const uint64_t sectors = (byteCount + (kCdSectorSize - 1)) / kCdSectorSize; + return sectors > 0 ? static_cast(sectors) : 1; + } + + std::string cdPathKey(const std::string &ps2Path) + { + return toLowerAscii(normalizeCdPathNoPrefix(ps2Path)); + } + + std::filesystem::path cdHostPath(const std::string &ps2Path) + { + const std::string normalized = normalizeCdPathNoPrefix(ps2Path); + std::filesystem::path resolved = getCdRootPath(); + if (!normalized.empty()) + { + resolved /= std::filesystem::path(normalized); + } + return resolved.lexically_normal(); + } + + bool resolveCaseInsensitivePath(const std::filesystem::path &root, + const std::filesystem::path &relative, + std::filesystem::path &resolvedOut) + { + std::filesystem::path current = root; + for (const auto &component : relative) + { + const std::filesystem::path direct = current / component; + std::error_code ec; + if (std::filesystem::exists(direct, ec) && !ec) + { + current = direct; + continue; + } + + bool matched = false; + const std::string needle = toLowerAscii(component.string()); + std::error_code iterEc; + for (const auto &entry : std::filesystem::directory_iterator(current, iterEc)) + { + if (iterEc) + { + break; + } + + const std::string candidate = toLowerAscii(entry.path().filename().string()); + if (candidate == needle) + { + current = entry.path(); + matched = true; + break; + } + } + + if (!matched) + { + return false; + } + } + + std::error_code fileEc; + if (std::filesystem::is_regular_file(current, fileEc) && !fileEc) + { + resolvedOut = current; + return true; + } + return false; + } + + void ensureCdLeafIndex(const std::filesystem::path &root) + { + if (g_cdLeafIndexBuilt && g_cdLeafIndexRoot == root) + { + return; + } + + g_cdLeafIndex.clear(); + g_cdLeafIndexRoot = root; + g_cdLeafIndexBuilt = true; + + std::error_code ec; + if (!std::filesystem::exists(root, ec) || ec) + { + return; + } + + for (const auto &entry : std::filesystem::recursive_directory_iterator( + root, std::filesystem::directory_options::skip_permission_denied, ec)) + { + if (ec) + { + break; + } + if (!entry.is_regular_file()) + { + continue; + } + + const std::string leaf = toLowerAscii(entry.path().filename().string()); + g_cdLeafIndex.emplace(leaf, entry.path()); + } + } + + bool registerCdFile(const std::string &ps2Path, CdFileEntry &entryOut) + { + const std::string key = cdPathKey(ps2Path); + if (key.empty()) + { + g_lastCdError = -1; + return false; + } + + auto existing = g_cdFilesByKey.find(key); + if (existing != g_cdFilesByKey.end()) + { + entryOut = existing->second; + g_lastCdError = 0; + return true; + } + + const std::filesystem::path root = getCdRootPath(); + std::filesystem::path path = cdHostPath(ps2Path); + std::error_code ec; + if (!std::filesystem::exists(path, ec) || ec || !std::filesystem::is_regular_file(path, ec)) + { + const std::filesystem::path relative(normalizeCdPathNoPrefix(ps2Path)); + std::filesystem::path resolvedCasePath; + if (resolveCaseInsensitivePath(root, relative, resolvedCasePath)) + { + path = resolvedCasePath; + ec.clear(); + } + else + { + ensureCdLeafIndex(root); + const std::string leaf = toLowerAscii(relative.filename().string()); + auto it = g_cdLeafIndex.find(leaf); + if (it != g_cdLeafIndex.end()) + { + path = it->second; + ec.clear(); + } + else + { + g_lastCdError = -1; + return false; + } + } + } + + const uint64_t sizeBytes = std::filesystem::file_size(path, ec); + if (ec) + { + g_lastCdError = -1; + return false; + } + + CdFileEntry entry; + entry.hostPath = path; + entry.sizeBytes = static_cast(std::min(sizeBytes, 0xFFFFFFFFu)); + entry.baseLbn = g_nextPseudoLbn; + entry.sectors = sectorsForBytes(sizeBytes); + + g_nextPseudoLbn += entry.sectors + 1; + g_cdFilesByKey.emplace(key, entry); + entryOut = entry; + g_lastCdError = 0; + return true; + } + + bool readHostRange(const std::filesystem::path &path, uint64_t offsetBytes, uint8_t *dst, size_t byteCount) + { + if (!dst) + { + g_lastCdError = -1; + return false; + } + if (byteCount == 0) + { + g_lastCdError = 0; + return true; + } + + std::memset(dst, 0, byteCount); + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) + { + g_lastCdError = -1; + return false; + } + + file.seekg(static_cast(offsetBytes), std::ios::beg); + if (!file.good()) + { + g_lastCdError = -1; + return false; + } + + file.read(reinterpret_cast(dst), static_cast(byteCount)); + g_lastCdError = 0; + return true; + } + + bool readCdSectors(uint32_t lbn, uint32_t sectors, uint8_t *dst, size_t byteCount) + { + for (const auto &[key, entry] : g_cdFilesByKey) + { + const uint32_t endLbn = entry.baseLbn + entry.sectors; + if (lbn < entry.baseLbn || lbn >= endLbn) + { + continue; + } + + const uint64_t relativeLbn = static_cast(lbn - entry.baseLbn); + const uint64_t offset = relativeLbn * kCdSectorSize; + return readHostRange(entry.hostPath, offset, dst, byteCount); + } + + const std::filesystem::path cdImage = getCdImagePath(); + if (!cdImage.empty()) + { + const uint64_t offset = static_cast(lbn) * kCdSectorSize; + return readHostRange(cdImage, offset, dst, byteCount); + } + + std::cerr << "sceCdRead unresolved LBN 0x" << std::hex << lbn + << " sectors=" << std::dec << sectors + << " (no mapped file and no configured CD image)" << std::endl; + g_lastCdError = -1; + return false; + } + + bool writeCdSearchResult(uint8_t *rdram, uint32_t fileAddr, const std::string &ps2Path, const CdFileEntry &entry) + { + // sceCdlFILE layout: u32 lsn, u32 size, char name[16], u8 date[8] + uint8_t *fileStruct = getMemPtr(rdram, fileAddr); + if (!fileStruct) + { + return false; + } + + std::array packed{}; + std::memcpy(packed.data() + 0, &entry.baseLbn, sizeof(entry.baseLbn)); + std::memcpy(packed.data() + 4, &entry.sizeBytes, sizeof(entry.sizeBytes)); + + std::filesystem::path leafPath(normalizeCdPathNoPrefix(ps2Path)); + std::string leaf = leafPath.filename().string(); + leaf = stripIsoVersionSuffix(std::move(leaf)); + std::strncpy(reinterpret_cast(packed.data() + 8), leaf.c_str(), 15); + + std::memcpy(fileStruct, packed.data(), packed.size()); + return true; + } + + bool hostFileHasAfsMagic(const std::filesystem::path &path) + { + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) + { + return false; + } + + char magic[4] = {}; + file.read(magic, sizeof(magic)); + if (file.gcount() < 3) + { + return false; + } + + return magic[0] == 'A' && magic[1] == 'F' && magic[2] == 'S'; + } + + bool tryRemapGdInitSearchToAfs(const std::string &ps2Path, + uint32_t callerRa, + const CdFileEntry &foundEntry, + CdFileEntry &entryOut, + std::string &resolvedPathOut) + { +#if !PS2_CD_REMAP_IDX_TO_AFS + { + return false; + } +#endif + + if (callerRa != 0x2d9444u) + { + return false; + } + + std::filesystem::path relative(normalizeCdPathNoPrefix(ps2Path)); + const std::string ext = toLowerAscii(relative.extension().string()); + const std::string leaf = toLowerAscii(relative.filename().string()); + + if (ext == ".idx") + { + if (foundEntry.sizeBytes > (kCdSectorSize * 8u)) + { + return false; + } + + std::filesystem::path afsRelative = relative; + afsRelative.replace_extension(".AFS"); + + CdFileEntry afsEntry; + if (!registerCdFile(afsRelative.generic_string(), afsEntry)) + { + return false; + } + if (!hostFileHasAfsMagic(afsEntry.hostPath)) + { + return false; + } + + entryOut = afsEntry; + resolvedPathOut = afsRelative.generic_string(); + return true; + } + + return false; + } + + uint8_t toBcd(uint32_t value) + { + const uint32_t clamped = value % 100; + return static_cast(((clamped / 10) << 4) | (clamped % 10)); + } + + uint32_t fromBcd(uint8_t value) + { + return static_cast(((value >> 4) & 0x0F) * 10 + (value & 0x0F)); + } + std::unordered_map g_file_map; uint32_t g_next_file_handle = 1; // Start file handles > 0 (0 is NULL) std::mutex g_file_mutex; @@ -36,7 +502,6 @@ namespace auto it = g_file_map.find(handle); return (it != g_file_map.end()) ? it->second : nullptr; } - } namespace @@ -65,22 +530,1019 @@ namespace namespace { - std::unordered_map g_alloc_map; // Map handle -> host ptr - std::unordered_map g_size_map; // Map host ptr -> size - uint32_t g_next_handle = 0x7F000000; // Start handles in a high, unlikely range - std::mutex g_alloc_mutex; // Mutex for thread safety - - uint32_t generate_handle() + bool tryReadWordFromRdram(uint8_t *rdram, uint32_t addr, uint32_t &outWord) { - // Very basic handle generation. We could wrap around or collide eventually. - uint32_t handle = 0; - do + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) { - handle = g_next_handle++; - if (g_next_handle == 0) // Skip 0 if it wraps around - g_next_handle = 1; - } while (handle == 0 || g_alloc_map.count(handle)); - return handle; + return false; + } + std::memcpy(&outWord, ptr, sizeof(outWord)); + return true; + } + + bool tryReadWordFromGuest(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, uint32_t &outWord) + { + if (tryReadWordFromRdram(rdram, addr, outWord)) + { + return true; + } + + if (runtime) + { + try + { + PS2Memory &mem = runtime->memory(); + outWord = static_cast(mem.read8(addr + 0u)) | + (static_cast(mem.read8(addr + 1u)) << 8u) | + (static_cast(mem.read8(addr + 2u)) << 16u) | + (static_cast(mem.read8(addr + 3u)) << 24u); + return true; + } + catch (...) + { + return false; + } + } + return false; + } + + bool tryReadByteFromGuest(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, uint8_t &outByte) + { + const uint8_t *chPtr = getConstMemPtr(rdram, addr); + if (chPtr) + { + outByte = *chPtr; + return true; + } + + if (runtime) + { + try + { + outByte = runtime->memory().read8(addr); + return true; + } + catch (...) + { + return false; + } + } + return false; + } + + bool writeGuestBytes(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, const uint8_t *src, size_t len) + { + if (!src || len == 0) + { + return true; + } + + bool allViaPtrs = true; + for (size_t i = 0; i < len; ++i) + { + const uint64_t guestAddr = static_cast(addr) + i; + if (guestAddr > 0xFFFFFFFFull) + { + return false; + } + uint8_t *dst = getMemPtr(rdram, static_cast(guestAddr)); + if (!dst) + { + allViaPtrs = false; + break; + } + *dst = src[i]; + } + if (allViaPtrs) + { + return true; + } + + if (runtime) + { + try + { + PS2Memory &mem = runtime->memory(); + for (size_t i = 0; i < len; ++i) + { + const uint64_t guestAddr = static_cast(addr) + i; + if (guestAddr > 0xFFFFFFFFull) + { + return false; + } + mem.write8(static_cast(guestAddr), src[i]); + } + return true; + } + catch (...) + { + return false; + } + } + + return false; + } + + std::string readPs2CStringBounded(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, size_t maxLen = 512) + { + std::string out; + if (addr == 0 || maxLen == 0) + { + return out; + } + + out.reserve(std::min(maxLen, 128)); + for (size_t i = 0; i < maxLen; ++i) + { + const uint64_t guestAddr = static_cast(addr) + i; + if (guestAddr > 0xFFFFFFFFull) + { + break; + } + + uint8_t chByte = 0; + if (!tryReadByteFromGuest(rdram, runtime, static_cast(guestAddr), chByte)) + { + break; + } + + const char ch = static_cast(chByte); + if (ch == '\0') + { + break; + } + out.push_back(ch); + } + + return out; + } + + std::string readPs2CStringBounded(uint8_t *rdram, uint32_t addr, size_t maxLen = 512) + { + return readPs2CStringBounded(rdram, nullptr, addr, maxLen); + } + + std::string sanitizeForLog(const std::string &value) + { + std::string out; + out.reserve(value.size()); + for (unsigned char ch : value) + { + if (ch == '\n' || ch == '\r' || ch == '\t' || (ch >= 0x20 && ch < 0x7F)) + { + out.push_back(static_cast(ch)); + } + else + { + out.push_back('.'); + } + } + return out; + } + + class Ps2VarArgCursor + { + public: + Ps2VarArgCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, int fixedArgs) + : m_rdram(rdram), + m_ctx(ctx), + m_runtime(runtime), + m_fixedArgs(fixedArgs), + m_stackBase(getRegU32(ctx, 29) + 0x10) + { + if (m_fixedArgs < 0) + { + m_fixedArgs = 0; + } + m_slotIndex = static_cast(m_fixedArgs); + } + + uint32_t nextU32() + { + const uint32_t value = readWordAtSlot(m_slotIndex); + ++m_slotIndex; + return value; + } + + uint64_t nextU64() + { + // O32 ABI aligns 64-bit variadic values on even 32-bit slots. + if ((m_slotIndex & 1u) != 0u) + { + ++m_slotIndex; + } + const uint64_t low = readWordAtSlot(m_slotIndex); + const uint64_t high = readWordAtSlot(m_slotIndex + 1u); + m_slotIndex += 2u; + return low | (high << 32); + } + + private: + uint32_t readWordAtSlot(uint32_t slotIndex) const + { + if (slotIndex < 4u) + { + // slot0..slot3 -> a0..a3 (r4..r7) + return getRegU32(m_ctx, 4 + static_cast(slotIndex)); + } + + const uint32_t stackIndex = slotIndex - 4u; + const uint32_t stackAddr = m_stackBase + stackIndex * 4u; + uint32_t value = 0; + (void)tryReadWordFromGuest(m_rdram, m_runtime, stackAddr, value); + return value; + } + + uint8_t *m_rdram; + R5900Context *m_ctx; + PS2Runtime *m_runtime; + int m_fixedArgs; + uint32_t m_stackBase; + uint32_t m_slotIndex = 0; + }; + + class Ps2VaListCursor + { + public: + Ps2VaListCursor(uint8_t *rdram, PS2Runtime *runtime, uint32_t vaListAddr) + : m_rdram(rdram), m_runtime(runtime), m_curr(vaListAddr) + { + } + + uint32_t nextU32() + { + uint32_t value = 0; + (void)tryReadWordFromGuest(m_rdram, m_runtime, m_curr, value); + m_curr += 4; + return value; + } + + uint64_t nextU64() + { + m_curr = (m_curr + 7u) & ~7u; + const uint64_t low = nextU32(); + const uint64_t high = nextU32(); + return low | (high << 32); + } + + private: + uint8_t *m_rdram; + PS2Runtime *m_runtime; + uint32_t m_curr = 0; + }; + + template + std::string formatPs2StringCore(uint8_t *rdram, const char *format, NextU32Fn nextU32, NextU64Fn nextU64, ReadStringFn readString) + { + if (!format) + { + return {}; + } + + std::string out; + out.reserve(std::strlen(format) + 32); + const char *p = format; + + while (*p) + { + if (*p != '%') + { + out.push_back(*p++); + continue; + } + + const char *specStart = p++; + if (*p == '%') + { + out.push_back('%'); + ++p; + continue; + } + + int parsedWidth = -1; + int parsedPrecision = -1; + + while (*p && std::strchr("-+ #0", *p)) + { + ++p; + } + + if (*p == '*') + { + parsedWidth = static_cast(nextU32()); + ++p; + } + else + { + if (*p && std::isdigit(static_cast(*p))) + { + parsedWidth = 0; + } + while (*p && std::isdigit(static_cast(*p))) + { + parsedWidth = (parsedWidth * 10) + (*p - '0'); + ++p; + } + } + + if (*p == '.') + { + ++p; + if (*p == '*') + { + parsedPrecision = static_cast(nextU32()); + ++p; + } + else + { + parsedPrecision = 0; + while (*p && std::isdigit(static_cast(*p))) + { + parsedPrecision = (parsedPrecision * 10) + (*p - '0'); + ++p; + } + } + } + if (parsedPrecision < 0) + { + parsedPrecision = -1; + } + (void)parsedWidth; + + enum class LengthMod + { + None, + H, + HH, + L, + LL, + J, + Z, + T, + BigL + }; + + LengthMod length = LengthMod::None; + if (*p == 'h') + { + ++p; + if (*p == 'h') + { + ++p; + length = LengthMod::HH; + } + else + { + length = LengthMod::H; + } + } + else if (*p == 'l') + { + ++p; + if (*p == 'l') + { + ++p; + length = LengthMod::LL; + } + else + { + length = LengthMod::L; + } + } + else if (*p == 'j') + { + ++p; + length = LengthMod::J; + } + else if (*p == 'z') + { + ++p; + length = LengthMod::Z; + } + else if (*p == 't') + { + ++p; + length = LengthMod::T; + } + else if (*p == 'L') + { + ++p; + length = LengthMod::BigL; + } + + if (*p == '\0') + { + out.append(specStart); + break; + } + + const bool use64Integer = (length == LengthMod::LL || length == LengthMod::J); + auto readUnsignedInteger = [&]() -> uint64_t + { + return use64Integer ? nextU64() : static_cast(nextU32()); + }; + auto readSignedInteger = [&]() -> int64_t + { + if (use64Integer) + { + return static_cast(nextU64()); + } + return static_cast(static_cast(nextU32())); + }; + + const char spec = *p++; + switch (spec) + { + case 's': + { + const uint32_t strAddr = nextU32(); + if (strAddr == 0) + { + out.append("(null)"); + } + else + { + std::string str = readString(strAddr); + if (parsedPrecision >= 0 && + str.size() > static_cast(parsedPrecision)) + { + str.resize(static_cast(parsedPrecision)); + } + out.append(str); + } + break; + } + case 'c': + { + const char ch = static_cast(nextU32() & 0xFF); + out.push_back(ch); + break; + } + case 'd': + case 'i': + out.append(std::to_string(readSignedInteger())); + break; + case 'u': + out.append(std::to_string(readUnsignedInteger())); + break; + case 'x': + case 'X': + { + std::ostringstream ss; + if (spec == 'X') + { + ss.setf(std::ios::uppercase); + } + ss << std::hex << readUnsignedInteger(); + out.append(ss.str()); + break; + } + case 'o': + { + std::ostringstream ss; + ss << std::oct << readUnsignedInteger(); + out.append(ss.str()); + break; + } + case 'p': + { + std::ostringstream ss; + ss << "0x" << std::hex << nextU32(); + out.append(ss.str()); + break; + } + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + { + const uint64_t bits = nextU64(); + double value = 0.0; + std::memcpy(&value, &bits, sizeof(value)); + char numBuf[128]; + std::snprintf(numBuf, sizeof(numBuf), "%g", value); + out.append(numBuf); + break; + } + case 'n': + { + // Avoid arbitrary guest memory mutation through %n in stub formatting. + (void)nextU32(); + break; + } + default: + out.append(specStart, p - specStart); + break; + } + } + + return out; + } + + std::string formatPs2StringWithArgs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, const char *format, int fixedArgs) + { + Ps2VarArgCursor cursor(rdram, ctx, runtime, fixedArgs); + return formatPs2StringCore( + rdram, + format, + [&cursor]() + { return cursor.nextU32(); }, + [&cursor]() + { return cursor.nextU64(); }, + [rdram, runtime](uint32_t addr) + { return readPs2CStringBounded(rdram, runtime, addr); }); + } + + std::string formatPs2StringWithVaList(uint8_t *rdram, PS2Runtime *runtime, const char *format, uint32_t vaListAddr) + { + Ps2VaListCursor cursor(rdram, runtime, vaListAddr); + return formatPs2StringCore( + rdram, + format, + [&cursor]() + { return cursor.nextU32(); }, + [&cursor]() + { return cursor.nextU64(); }, + [rdram, runtime](uint32_t addr) + { return readPs2CStringBounded(rdram, runtime, addr); }); + } + + constexpr uint32_t kMaxStubWarningsPerName = 8; + std::unordered_map g_stubWarningCount; + std::mutex g_stubWarningMutex; + constexpr uint32_t kMaxPrintfLogs = 200; + constexpr size_t kMaxFormattedOutputBytes = 4096; + uint32_t g_printfLogCount = 0; + std::mutex g_printfLogMutex; + + constexpr std::array kDmaChannelBases = { + 0x10008000u, 0x10009000u, 0x1000A000u, 0x1000B000u, 0x1000B400u, + 0x1000C000u, 0x1000C400u, 0x1000C800u, 0x1000D000u, 0x1000D400u}; + std::mutex g_dmaStubMutex; + std::unordered_map g_dmaPendingPolls; + uint32_t g_dmaStubLogCount = 0; + constexpr uint32_t kMaxDmaStubLogs = 64; + + bool isKnownDmaChannelBase(uint32_t value) + { + return std::find(kDmaChannelBases.begin(), kDmaChannelBases.end(), value) != kDmaChannelBases.end(); + } + + uint32_t toDmaPhys(uint32_t addr) + { + return addr & 0x1FFFFFFFu; + } + + uint32_t normalizeQwcFromArg(uint32_t value) + { + if (value == 0) + { + return 0; + } + if (value > 0xFFFFu) + { + return std::min((value + 15u) >> 4u, 0xFFFFu); + } + return value & 0xFFFFu; + } + + struct ParsedDmaTag + { + bool valid = false; + uint32_t qwc = 0; + uint32_t id = 0; + uint32_t addr = 0; + }; + + ParsedDmaTag tryParseDmaTag(uint8_t *rdram, uint32_t guestAddr) + { + ParsedDmaTag out; + if (guestAddr == 0) + { + return out; + } + + const uint8_t *ptr = getConstMemPtr(rdram, guestAddr); + if (!ptr) + { + return out; + } + + uint64_t tag = 0; + std::memcpy(&tag, ptr, sizeof(tag)); + out.valid = true; + out.qwc = static_cast(tag & 0xFFFFu); + out.id = static_cast((tag >> 28) & 0x7u); + out.addr = static_cast((tag >> 32) & 0x7FFFFFFFu); + return out; + } + + uint32_t resolveDmaChannelBase(uint8_t *rdram, uint32_t chanArg) + { + if (isKnownDmaChannelBase(chanArg)) + { + return chanArg; + } + if (chanArg < kDmaChannelBases.size()) + { + return kDmaChannelBases[chanArg]; + } + + const uint32_t masked = chanArg & 0xFFFFFF00u; + if (isKnownDmaChannelBase(masked)) + { + return masked; + } + + uint32_t candidate0 = 0; + if (!tryReadWordFromRdram(rdram, chanArg, candidate0)) + { + return 0; + } + if (isKnownDmaChannelBase(candidate0)) + { + return candidate0; + } + + uint32_t candidate1 = 0; + if (!tryReadWordFromRdram(rdram, chanArg + 4u, candidate1)) + { + return 0; + } + if (isKnownDmaChannelBase(candidate1)) + { + return candidate1; + } + + return 0; + } + + int32_t submitDmaSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, bool preferNormalCount) + { + if (!runtime) + { + return -1; + } + + const uint32_t chanArg = getRegU32(ctx, 4); + const uint32_t payloadArg = getRegU32(ctx, 5); + const uint32_t countArg = getRegU32(ctx, 6); + const uint32_t channelBase = resolveDmaChannelBase(rdram, chanArg); + if (channelBase == 0) + { + return -1; + } + + const uint32_t payloadPhys = toDmaPhys(payloadArg); + uint32_t madr = 0; + uint32_t qwc = 0; + uint32_t tadr = payloadPhys; + uint32_t chcr = 0x00000181u; // DIR=1, TIE=1, STR=1 (normal mode). + + if (preferNormalCount) + { + qwc = normalizeQwcFromArg(countArg); + madr = payloadPhys; + } + else + { + const ParsedDmaTag tag = tryParseDmaTag(rdram, payloadPhys); + if (tag.valid && tag.qwc != 0) + { + qwc = tag.qwc; + switch (tag.id) + { + case 0: // REFE + case 3: // REF + case 4: // REFS + madr = toDmaPhys(tag.addr); + break; + default: + // CNT/NEXT/CALL/RET-style tags carry payload inline after the tag. + madr = toDmaPhys(payloadPhys + 0x10u); + break; + } + } + else + { + // Fall back to chain mode so the runtime DMA path can walk TADR. + chcr = 0x00000185u; // MODE=1 chain, DIR=1, TIE=1, STR=1. + } + } + + PS2Memory &mem = runtime->memory(); + mem.writeIORegister(channelBase + 0x20u, qwc & 0xFFFFu); + mem.writeIORegister(channelBase + 0x10u, madr); + mem.writeIORegister(channelBase + 0x30u, tadr); + mem.writeIORegister(channelBase + 0x00u, chcr); + + std::lock_guard lock(g_dmaStubMutex); + g_dmaPendingPolls[channelBase] = 1; + if (g_dmaStubLogCount < kMaxDmaStubLogs) + { + std::cout << "[sceDmaSend] ch=0x" << std::hex << channelBase + << " madr=0x" << madr + << " qwc=0x" << qwc + << " tadr=0x" << tadr + << " chcr=0x" << chcr << std::dec << std::endl; + ++g_dmaStubLogCount; + } + + return 0; + } + + int32_t submitDmaSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + if (!runtime) + { + return -1; + } + + const uint32_t chanArg = getRegU32(ctx, 4); + const uint32_t mode = getRegU32(ctx, 5); + const uint32_t channelBase = resolveDmaChannelBase(rdram, chanArg); + if (channelBase == 0) + { + return -1; + } + + bool modelBusy = false; + { + std::lock_guard lock(g_dmaStubMutex); + auto it = g_dmaPendingPolls.find(channelBase); + if (it != g_dmaPendingPolls.end() && it->second > 0) + { + modelBusy = true; + if (mode != 0) + { + --it->second; + if (it->second == 0) + { + g_dmaPendingPolls.erase(it); + } + } + else + { + // Blocking mode: complete immediately in this runtime. + g_dmaPendingPolls.erase(it); + } + } + } + + const uint32_t chcr = runtime->memory().readIORegister(channelBase + 0x00u); + const bool hwBusy = (chcr & 0x100u) != 0; + return ((modelBusy || hwBusy) && mode != 0) ? 1 : 0; + } + +} + +namespace +{ + struct GsGParam + { + uint8_t interlace; + uint8_t omode; + uint8_t ffmode; + uint8_t version; + }; + + struct GsDispEnvMem + { + uint64_t display; + uint64_t dispfb; + }; + + struct GsImageMem + { + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; + uint16_t vram_addr; + uint8_t vram_width; + uint8_t psm; + }; + +#pragma pack(push, 1) + struct GsDrawEnvMem + { + uint16_t offset_x; + uint16_t offset_y; + uint16_t clip_x; + uint16_t clip_y; + uint16_t clip_w; + uint16_t clip_h; + uint16_t vram_addr; + uint8_t fbw; + uint8_t psm; + uint16_t vram_x; + uint16_t vram_y; + uint32_t draw_mask; + uint8_t auto_clear; + uint8_t pad[3]; + uint8_t bg_r; + uint8_t bg_g; + uint8_t bg_b; + uint8_t bg_a; + float bg_q; + }; +#pragma pack(pop) + + static_assert(sizeof(GsImageMem) == 12, "GsImageMem size mismatch"); + static_assert(sizeof(GsDrawEnvMem) == 36, "GsDrawEnvMem size mismatch"); + + constexpr uint32_t kGsParamScratchOffset = 0x100; + GsGParam g_gparam{1, 2, 1, 3}; // Default: interlaced NTSC, frame mode. + + static uint64_t makePmode(uint32_t en1, uint32_t en2, uint32_t mmod, uint32_t amod, uint32_t slbg, uint32_t alp) + { + return (static_cast(en1 & 1) << 0) | + (static_cast(en2 & 1) << 1) | + (static_cast(1) << 2) | + (static_cast(mmod & 1) << 5) | + (static_cast(amod & 1) << 6) | + (static_cast(slbg & 1) << 7) | + (static_cast(alp & 0xFF) << 8); + } + + static uint64_t makeDispFb(uint32_t fbp, uint32_t fbw, uint32_t psm, uint32_t dbx, uint32_t dby) + { + return (static_cast(fbp & 0x1FF) << 0) | + (static_cast(fbw & 0x3F) << 9) | + (static_cast(psm & 0x1F) << 15) | + (static_cast(dbx & 0x7FF) << 32) | + (static_cast(dby & 0x7FF) << 43); + } + + static uint64_t makeDisplay(uint32_t dx, uint32_t dy, uint32_t magh, uint32_t magv, uint32_t dw, uint32_t dh) + { + return (static_cast(dx & 0x0FFF) << 0) | + (static_cast(dy & 0x07FF) << 12) | + (static_cast(magh & 0x0F) << 23) | + (static_cast(magv & 0x03) << 27) | + (static_cast(dw & 0x0FFF) << 32) | + (static_cast(dh & 0x07FF) << 44); + } + + static uint32_t readStackU32(uint8_t *rdram, R5900Context *ctx, uint32_t offset) + { + uint32_t sp = getRegU32(ctx, 29); + const uint8_t *ptr = getConstMemPtr(rdram, sp + offset); + if (!ptr) + return 0; + uint32_t value = 0; + std::memcpy(&value, ptr, sizeof(value)); + return value; + } + + static uint32_t bytesForPixels(uint8_t psm, uint32_t pixelCount) + { + const uint64_t pixels = static_cast(pixelCount); + uint64_t bytes = 0; + switch (psm) + { + case 0: // PSMCT32 + case 1: // PSMCT24 (treat as 32) + case 27: // PSMT8H (packed in 32-bit lanes) + case 36: // PSMT4HL (packed in 32-bit lanes) + case 44: // PSMT4HH (packed in 32-bit lanes) + bytes = pixels * 4ull; + break; + case 2: // PSMCT16 + case 10: // PSMCT16S + bytes = pixels * 2ull; + break; + case 19: // PSMT8 + bytes = pixels; + break; + case 20: // PSMT4 + bytes = (pixels + 1ull) / 2ull; + break; + default: + bytes = pixels * 4ull; + break; + } + if (bytes > 0xFFFFFFFFull) + { + return 0xFFFFFFFFu; + } + return static_cast(bytes); + } + + struct GsSetDefImageArgs + { + uint32_t x = 0; + uint32_t y = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t vramAddr = 0; + uint32_t vramWidth = 0; + uint32_t psm = 0; + }; + + static GsSetDefImageArgs decodeGsSetDefImageArgs(uint8_t *rdram, R5900Context *ctx) + { + GsSetDefImageArgs decoded{}; + + const uint32_t reg8 = getRegU32(ctx, 8); + const uint32_t reg9 = getRegU32(ctx, 9); + const uint32_t reg10 = getRegU32(ctx, 10); + const uint32_t reg11 = getRegU32(ctx, 11); + + const uint32_t stack0 = readStackU32(rdram, ctx, 16); + const uint32_t stack1 = readStackU32(rdram, ctx, 20); + const uint32_t stack2 = readStackU32(rdram, ctx, 24); + const uint32_t stack3 = readStackU32(rdram, ctx, 28); + + const bool looksLikeCanonicalRegs = (reg10 != 0u || reg11 != 0u); + const bool looksLikeCanonicalStack = (stack2 != 0u || stack3 != 0u); + + if (looksLikeCanonicalRegs || looksLikeCanonicalStack) + { + decoded.vramAddr = getRegU32(ctx, 5); + decoded.vramWidth = getRegU32(ctx, 6); + decoded.psm = getRegU32(ctx, 7); + + if (looksLikeCanonicalRegs) + { + decoded.x = reg8; + decoded.y = reg9; + decoded.width = reg10; + decoded.height = reg11; + } + else + { + decoded.x = stack0; + decoded.y = stack1; + decoded.width = stack2; + decoded.height = stack3; + } + return decoded; + } + + // Legacy code + // a1=x, a2=y, a3=w, stack/reg extension for h/vram/fbw/psm. + decoded.x = getRegU32(ctx, 5); + decoded.y = getRegU32(ctx, 6); + decoded.width = getRegU32(ctx, 7); + decoded.height = stack0 != 0u ? stack0 : reg8; + decoded.vramAddr = stack1 != 0u ? stack1 : reg9; + decoded.vramWidth = stack2 != 0u ? stack2 : reg10; + decoded.psm = stack3 != 0u ? stack3 : reg11; + return decoded; + } + + static bool readGsImage(uint8_t *rdram, uint32_t addr, GsImageMem &out) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + return false; + std::memcpy(&out, ptr, sizeof(out)); + return true; + } + + static bool writeGsImage(uint8_t *rdram, uint32_t addr, const GsImageMem &img) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + return false; + std::memcpy(ptr, &img, sizeof(img)); + return true; + } + + static bool writeGsDispEnv(uint8_t *rdram, uint32_t addr, uint64_t display, uint64_t dispfb) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + return false; + GsDispEnvMem env{display, dispfb}; + std::memcpy(ptr, &env, sizeof(env)); + return true; + } + + static bool readGsDispEnv(uint8_t *rdram, uint32_t addr, GsDispEnvMem &out) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + return false; + std::memcpy(&out, ptr, sizeof(out)); + return true; + } + + static uint32_t writeGsGParamToScratch(PS2Runtime *runtime) + { + if (!runtime) + return 0; + uint8_t *scratch = runtime->memory().getScratchpad(); + if (!scratch) + return 0; + std::memcpy(scratch + kGsParamScratchOffset, &g_gparam, sizeof(g_gparam)); + return PS2_SCRATCHPAD_BASE + kGsParamScratchOffset; } } @@ -89,162 +1551,34 @@ namespace ps2_stubs void malloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - size_t size = getRegU32(ctx, 4); // $a0 - uint32_t handle = 0; - - if (size > 0) - { - void *ptr = ::malloc(size); - if (ptr) - { - std::lock_guard lock(g_alloc_mutex); - handle = generate_handle(); - g_alloc_map[handle] = ptr; - g_size_map[ptr] = size; - std::cout << "ps2_stub malloc: size=" << size << " -> handle=0x" << std::hex << handle << std::dec << std::endl; - } - else - { - std::cerr << "ps2_stub malloc error: Host allocation failed for size " << size << std::endl; - } - } - // returns handle (0 if size=0 or allocation failed) - setReturnU32(ctx, handle); + const uint32_t size = getRegU32(ctx, 4); // $a0 + const uint32_t guestAddr = runtime ? runtime->guestMalloc(size) : 0u; + setReturnU32(ctx, guestAddr); } void free(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t handle = getRegU32(ctx, 4); // $a0 - - std::cout << "ps2_stub free: handle=0x" << std::hex << handle << std::dec << std::endl; - - if (handle != 0) + const uint32_t guestAddr = getRegU32(ctx, 4); // $a0 + if (runtime && guestAddr != 0u) { - std::lock_guard lock(g_alloc_mutex); - auto it = g_alloc_map.find(handle); - if (it != g_alloc_map.end()) - { - void *ptr = it->second; - ::free(ptr); - g_size_map.erase(ptr); - g_alloc_map.erase(it); - } - else - { - // Commented out because some programs might free static/non-heap memory - // std::cerr << "ps2_stub free error: Invalid handle 0x" << std::hex << handle << std::dec << std::endl; - } + runtime->guestFree(guestAddr); } - // free dont have return } void calloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - size_t num = getRegU32(ctx, 4); // $a0 - size_t size = getRegU32(ctx, 5); // $a1 - uint32_t handle = 0; - size_t total_size = num * size; - - if (total_size > 0 && (size == 0 || total_size / size == num)) // maybe we can ignore this overflow check - { - void *ptr = ::calloc(num, size); - if (ptr) - { - std::lock_guard lock(g_alloc_mutex); - handle = generate_handle(); - g_alloc_map[handle] = ptr; - g_size_map[ptr] = total_size; - std::cout << "ps2_stub calloc: num=" << num << ", size=" << size << " -> handle=0x" << std::hex << handle << std::dec << std::endl; - } - else - { - std::cerr << "ps2_stub calloc error: Host allocation failed for " << num << " * " << size << " bytes" << std::endl; - } - } - // retuns handle (0 if size=0 or allocation failed) - setReturnU32(ctx, handle); + const uint32_t count = getRegU32(ctx, 4); // $a0 + const uint32_t size = getRegU32(ctx, 5); // $a1 + const uint32_t guestAddr = runtime ? runtime->guestCalloc(count, size) : 0u; + setReturnU32(ctx, guestAddr); } void realloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t old_handle = getRegU32(ctx, 4); // $a0 - size_t new_size = getRegU32(ctx, 5); // $a1 - uint32_t new_handle = 0; - void *old_ptr = nullptr; - - std::cout << "ps2_stub realloc: old_handle=0x" << std::hex << old_handle << ", new_size=" << std::dec << new_size << std::endl; - - if (old_handle == 0) - { - void *new_ptr_alloc = ::malloc(new_size); - if (new_ptr_alloc) - { - std::lock_guard lock(g_alloc_mutex); - new_handle = generate_handle(); - g_alloc_map[new_handle] = new_ptr_alloc; - g_size_map[new_ptr_alloc] = new_size; - } - else if (new_size > 0) - { - std::cerr << "ps2_stub realloc (as malloc) error: Host allocation failed for size " << new_size << std::endl; - } - } - else if (new_size == 0) - { - std::lock_guard lock(g_alloc_mutex); - auto it = g_alloc_map.find(old_handle); - if (it != g_alloc_map.end()) - { - old_ptr = it->second; - ::free(old_ptr); - g_size_map.erase(old_ptr); - g_alloc_map.erase(it); - } - else - { - std::cerr << "ps2_stub realloc (as free) error: Invalid handle 0x" << std::hex << old_handle << std::dec << std::endl; - } - new_handle = 0; - } - else - { - std::lock_guard lock(g_alloc_mutex); - auto it = g_alloc_map.find(old_handle); - if (it != g_alloc_map.end()) - { - old_ptr = it->second; - void *new_ptr = ::realloc(old_ptr, new_size); - if (new_ptr) - { - if (new_ptr != old_ptr) - { - g_size_map.erase(old_ptr); - g_alloc_map.erase(it); - - new_handle = generate_handle(); - g_alloc_map[new_handle] = new_ptr; - g_size_map[new_ptr] = new_size; - } - else - { - g_size_map[new_ptr] = new_size; - new_handle = old_handle; - } - } - else - { - std::cerr << "ps2_stub realloc error: Host reallocation failed for handle 0x" << std::hex << old_handle << " to size " << std::dec << new_size << std::endl; - new_handle = 0; - } - } - else - { - std::cerr << "ps2_stub realloc error: Invalid handle 0x" << std::hex << old_handle << std::dec << std::endl; - new_handle = 0; - } - } - - setReturnU32(ctx, new_handle); + const uint32_t oldGuestAddr = getRegU32(ctx, 4); // $a0 + const uint32_t newSize = getRegU32(ctx, 5); // $a1 + const uint32_t newGuestAddr = runtime ? runtime->guestRealloc(oldGuestAddr, newSize) : 0u; + setReturnU32(ctx, newGuestAddr); } void memcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -259,6 +1593,7 @@ namespace ps2_stubs if (hostDest && hostSrc) { ::memcpy(hostDest, hostSrc, size); + ps2TraceGuestRangeWrite(rdram, destAddr, static_cast(size), "memcpy", ctx); } else { @@ -283,6 +1618,7 @@ namespace ps2_stubs if (hostDest) { ::memset(hostDest, value, size); + ps2TraceGuestRangeWrite(rdram, destAddr, size, "memset", ctx); } else { @@ -305,6 +1641,7 @@ namespace ps2_stubs if (hostDest && hostSrc) { ::memmove(hostDest, hostSrc, size); + ps2TraceGuestRangeWrite(rdram, destAddr, static_cast(size), "memmove", ctx); } else { @@ -357,6 +1694,7 @@ namespace ps2_stubs if (hostDest && hostSrc) { ::strcpy(hostDest, hostSrc); + ps2TraceGuestRangeWrite(rdram, destAddr, static_cast(::strlen(hostSrc) + 1u), "strcpy", ctx); } else { @@ -382,6 +1720,7 @@ namespace ps2_stubs if (hostDest && hostSrc) { ::strncpy(hostDest, hostSrc, size); + ps2TraceGuestRangeWrite(rdram, destAddr, size, "strncpy", ctx); } else { @@ -599,15 +1938,32 @@ namespace ps2_stubs void printf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t format_addr = getRegU32(ctx, 4); // $a0 - const char *format = reinterpret_cast(getConstMemPtr(rdram, format_addr)); + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); int ret = -1; - if (format) + if (format_addr != 0) { - // TODO we will Ignores all arguments beyond the format string - std::cout << "PS2 printf: "; - ret = std::printf("%s", format); // Just print the format string itself - std::cout << std::flush; // Ensure output appears + std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 1); + if (rendered.size() > 2048) + { + rendered.resize(2048); + } + const std::string logLine = sanitizeForLog(rendered); + uint32_t count = 0; + { + std::lock_guard lock(g_printfLogMutex); + count = ++g_printfLogCount; + } + if (count <= kMaxPrintfLogs) + { + std::cout << "PS2 printf: " << logLine; + std::cout << std::flush; + } + else if (count == kMaxPrintfLogs + 1) + { + std::cerr << "PS2 printf logging suppressed after " << kMaxPrintfLogs << " lines" << std::endl; + } + ret = static_cast(rendered.size()); } else { @@ -623,21 +1979,53 @@ namespace ps2_stubs uint32_t str_addr = getRegU32(ctx, 4); // $a0 uint32_t format_addr = getRegU32(ctx, 5); // $a1 - char *str = reinterpret_cast(getMemPtr(rdram, str_addr)); - const char *format = reinterpret_cast(getConstMemPtr(rdram, format_addr)); + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); int ret = -1; - if (str && format) + if (format_addr != 0) { - // TODO we will Ignores all arguments beyond the format string - ::strcpy(str, format); - ret = (int)::strlen(str); + const uint32_t watchBase = ps2PathWatchPhysAddr(); + const uint32_t watchEnd = watchBase + PS2_PATH_WATCH_BYTES; + const uint32_t dest = str_addr & PS2_RAM_MASK; + const bool touchesWatch = dest < watchEnd && dest >= watchBase; + static uint32_t watchSprintfLogCount = 0; + if (touchesWatch && watchSprintfLogCount < 64u) + { + const uint32_t arg0 = getRegU32(ctx, 6); + const uint32_t arg1 = getRegU32(ctx, 7); + std::cout << "[watch:sprintf] dest=0x" << std::hex << str_addr + << " fmt@0x" << format_addr + << " arg0=0x" << arg0 + << " arg1=0x" << arg1 + << " fmt=\"" << sanitizeForLog(readPs2CStringBounded(rdram, runtime, format_addr, 64)) << "\"" + << " s0=\"" << sanitizeForLog(readPs2CStringBounded(rdram, runtime, arg0, 64)) << "\"" + << " s1=\"" << sanitizeForLog(readPs2CStringBounded(rdram, runtime, arg1, 64)) << "\"" + << std::dec << std::endl; + ++watchSprintfLogCount; + } + + std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 2); + if (rendered.size() >= kMaxFormattedOutputBytes) + { + rendered.resize(kMaxFormattedOutputBytes - 1); + } + const size_t writeLen = rendered.size() + 1u; + if (writeGuestBytes(rdram, runtime, str_addr, reinterpret_cast(rendered.c_str()), writeLen)) + { + ps2TraceGuestRangeWrite(rdram, str_addr, static_cast(writeLen), "sprintf", ctx); + ret = static_cast(rendered.size()); + } + else + { + std::cerr << "sprintf error: Failed to write destination buffer at 0x" + << std::hex << str_addr << std::dec << std::endl; + } } else { - std::cerr << "sprintf error: Invalid address provided." - << " Dest: 0x" << std::hex << str_addr << " (host ptr valid: " << (str != nullptr) << ")" - << ", Format: 0x" << format_addr << " (host ptr valid: " << (format != nullptr) << ")" << std::dec + std::cerr << "sprintf error: Invalid format address provided." + << " Dest: 0x" << std::hex << str_addr + << ", Format: 0x" << format_addr << std::dec << std::endl; } @@ -650,27 +2038,39 @@ namespace ps2_stubs uint32_t str_addr = getRegU32(ctx, 4); // $a0 size_t size = getRegU32(ctx, 5); // $a1 uint32_t format_addr = getRegU32(ctx, 6); // $a2 - char *str = reinterpret_cast(getMemPtr(rdram, str_addr)); - const char *format = reinterpret_cast(getConstMemPtr(rdram, format_addr)); + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); int ret = -1; - if (str && format && size > 0) + if (format_addr != 0) { - // TODO we will Ignores all arguments beyond the format string + std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 3); + ret = static_cast(rendered.size()); - ::strncpy(str, format, size); - str[size - 1] = '\0'; - ret = (int)::strlen(str); - } - else if (size == 0 && format) - { - ret = (int)::strlen(format); + if (size > 0) + { + const size_t copyLen = std::min(size - 1, rendered.size()); + std::vector output(copyLen + 1u, 0u); + if (copyLen > 0u) + { + std::memcpy(output.data(), rendered.data(), copyLen); + } + if (writeGuestBytes(rdram, runtime, str_addr, output.data(), output.size())) + { + ps2TraceGuestRangeWrite(rdram, str_addr, static_cast(output.size()), "snprintf", ctx); + } + else + { + std::cerr << "snprintf error: Failed to write destination buffer at 0x" + << std::hex << str_addr << std::dec << std::endl; + ret = -1; + } + } } else { std::cerr << "snprintf error: Invalid address provided or size is zero." - << " Dest: 0x" << std::hex << str_addr << " (host ptr valid: " << (str != nullptr) << ")" - << ", Format: 0x" << format_addr << " (host ptr valid: " << (format != nullptr) << ")" << std::dec + << " Dest: 0x" << std::hex << str_addr + << ", Format: 0x" << format_addr << std::dec << ", Size: " << size << std::endl; } @@ -824,19 +2224,19 @@ namespace ps2_stubs uint32_t file_handle = getRegU32(ctx, 4); // $a0 uint32_t format_addr = getRegU32(ctx, 5); // $a1 FILE *fp = get_file_ptr(file_handle); - const char *format = reinterpret_cast(getConstMemPtr(rdram, format_addr)); + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); int ret = -1; - if (fp && format) + if (fp && format_addr != 0) { - // TODO this implementation ignores all arguments beyond the format string - ret = std::fprintf(fp, "%s", format); + std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 2); + ret = std::fprintf(fp, "%s", rendered.c_str()); } else { std::cerr << "fprintf error: Invalid file handle or format address." << " Handle: 0x" << std::hex << file_handle << " (file valid: " << (fp != nullptr) << ")" - << ", Format: 0x" << format_addr << " (host ptr valid: " << (format != nullptr) << ")" << std::dec + << ", Format: 0x" << format_addr << std::dec << std::endl; } @@ -939,12 +2339,50 @@ namespace ps2_stubs ctx->f[0] = ::sinf(arg); } + void __kernel_sinf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const float x = ctx->f[12]; + const float y = ctx->f[13]; + const int32_t iy = static_cast(getRegU32(ctx, 4)); + ctx->f[0] = ::sinf(x + (iy != 0 ? y : 0.0f)); + } + void cos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { float arg = ctx->f[12]; ctx->f[0] = ::cosf(arg); } + void __kernel_cosf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const float x = ctx->f[12]; + const float y = ctx->f[13]; + ctx->f[0] = ::cosf(x + y); + } + + void __ieee754_rem_pio2f(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const float x = ctx->f[12]; + constexpr float kPi = 3.14159265358979323846f; + constexpr float kHalfPi = kPi * 0.5f; + constexpr float kInvHalfPi = 2.0f / kPi; + const int32_t n = static_cast(std::nearbyintf(x * kInvHalfPi)); + const float y0 = x - (static_cast(n) * kHalfPi); + const float y1 = 0.0f; + + const uint32_t yOutAddr = getRegU32(ctx, 4); + if (float *yOut0 = reinterpret_cast(getMemPtr(rdram, yOutAddr)); yOut0) + { + *yOut0 = y0; + } + if (float *yOut1 = reinterpret_cast(getMemPtr(rdram, yOutAddr + 4)); yOut1) + { + *yOut1 = y1; + } + + setReturnS32(ctx, n); + } + void tan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { float arg = ctx->f[12]; @@ -1007,50 +2445,47 @@ namespace ps2_stubs uint32_t sectors = getRegU32(ctx, 5); // $a1 - sector count uint32_t buf = getRegU32(ctx, 6); // $a2 - destination buffer in RDRAM - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sceCdRead: lbn=0x" << std::hex << lbn - << " sectors=" << std::dec << sectors - << " buf=0x" << std::hex << buf << std::dec << std::endl; - ++logCount; - } - - size_t bytes = static_cast(sectors) * 2048; // CD/DVD sector size + uint32_t offset = buf & PS2_RAM_MASK; + size_t bytes = static_cast(sectors) * kCdSectorSize; if (bytes > 0) { - uint32_t offset = buf & PS2_RAM_MASK; - size_t maxBytes = PS2_RAM_SIZE - offset; + const size_t maxBytes = PS2_RAM_SIZE - offset; if (bytes > maxBytes) + { bytes = maxBytes; - std::memset(rdram + offset, 0, bytes); + } } - setReturnS32(ctx, 1); // Success + uint8_t *dst = rdram + offset; + bool ok = true; + if (bytes > 0) + { + ok = readCdSectors(lbn, sectors, dst, bytes); + if (!ok) + { + std::memset(dst, 0, bytes); + } + } + + if (ok) + { + g_cdStreamingLbn = lbn + sectors; + setReturnS32(ctx, 1); // command accepted/success + } + else + { + setReturnS32(ctx, 0); + } } void sceCdSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sceCdSync" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); // 0 = completed/not busy } void sceCdGetError(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sceCdGetError" << std::endl; - ++logCount; - } - - setReturnS32(ctx, 0); // no error + setReturnS32(ctx, g_lastCdError); } void njSetBorderColor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1248,9 +2683,40 @@ namespace ps2_stubs std::cout << "ps2_stub syFree" << std::endl; ++logCount; } + + const uint32_t guestAddr = getRegU32(ctx, 4); // $a0 + if (runtime && guestAddr != 0u) + { + runtime->guestFree(guestAddr); + } + setReturnS32(ctx, 0); } + void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t requestedSize = getRegU32(ctx, 4); // $a0 + uint32_t resultAddr = 0u; + + if (runtime && requestedSize != 0u) + { + // Match game expectation for allocator alignment while keeping pointers in EE RAM. + resultAddr = runtime->guestMalloc(requestedSize, 64u); + } + + static int logCount = 0; + if (logCount < 16) + { + std::cout << "ps2_stub syMalloc" + << " size=0x" << std::hex << requestedSize + << " -> 0x" << resultAddr + << std::dec << std::endl; + ++logCount; + } + + setReturnU32(ctx, resultAddr); + } + void InitSdcParameter(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { static int logCount = 0; @@ -1276,11 +2742,56 @@ namespace ps2_stubs void syMallocInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { static int logCount = 0; - if (logCount < 8) + if (runtime) + { + const uint32_t heapBase = getRegU32(ctx, 4); // $a0 + const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (optional size) + + constexpr uint32_t kHeapBaseFloor = 0x00100000u; + uint32_t normalizedBase = heapBase; + if (normalizedBase >= 0x80000000u && normalizedBase < 0xC0000000u) + { + normalizedBase &= 0x1FFFFFFFu; + } + else if (normalizedBase >= PS2_RAM_SIZE) + { + normalizedBase &= PS2_RAM_MASK; + } + + const bool suspiciousKsegBase = (heapBase & 0xE0000000u) == 0x80000000u && normalizedBase < kHeapBaseFloor; + if (normalizedBase == 0u || suspiciousKsegBase) + { + // Keep the ELF-driven suggestion instead of collapsing heap to low memory. + normalizedBase = runtime->guestHeapBase(); + } + + // Treat absurd "size" values as unspecified limit. + uint32_t heapLimit = 0u; + if (heapSize != 0u && heapSize <= PS2_RAM_SIZE && normalizedBase < PS2_RAM_SIZE) + { + const uint64_t candidateLimit = static_cast(normalizedBase) + static_cast(heapSize); + heapLimit = static_cast(std::min(candidateLimit, PS2_RAM_SIZE)); + } + runtime->configureGuestHeap(normalizedBase, heapLimit); + if (logCount < 8) + { + std::cout << "ps2_stub syMallocInit" + << " reqBase=0x" << std::hex << heapBase + << " reqSize=0x" << heapSize + << " normBase=0x" << normalizedBase + << " reqLimit=0x" << heapLimit + << " finalBase=0x" << runtime->guestHeapBase() + << " finalEnd=0x" << runtime->guestHeapEnd() + << std::dec << std::endl; + ++logCount; + } + } + else if (logCount < 8) { std::cout << "ps2_stub syMallocInit" << std::endl; ++logCount; } + setReturnS32(ctx, 0); } @@ -1508,22 +3019,31 @@ namespace ps2_stubs void _calloc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_calloc_r", rdram, ctx, runtime); + const uint32_t count = getRegU32(ctx, 5); // $a1 + const uint32_t size = getRegU32(ctx, 6); // $a2 + const uint32_t guestAddr = runtime ? runtime->guestCalloc(count, size) : 0u; + setReturnU32(ctx, guestAddr); } void _free_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_free_r", rdram, ctx, runtime); + const uint32_t guestAddr = getRegU32(ctx, 5); // $a1 + if (runtime && guestAddr != 0u) + { + runtime->guestFree(guestAddr); + } } void _malloc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_malloc_r", rdram, ctx, runtime); + const uint32_t size = getRegU32(ctx, 5); // $a1 + const uint32_t guestAddr = runtime ? runtime->guestMalloc(size) : 0u; + setReturnU32(ctx, guestAddr); } void _malloc_trim_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_malloc_trim_r", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void _mbtowc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1533,12 +3053,45 @@ namespace ps2_stubs void _printf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_printf", rdram, ctx, runtime); + printf(rdram, ctx, runtime); } void _printf_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_printf_r", rdram, ctx, runtime); + uint32_t format_addr = getRegU32(ctx, 5); // $a1 + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); + int ret = -1; + + if (format_addr != 0) + { + std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 2); + if (rendered.size() > 2048) + { + rendered.resize(2048); + } + const std::string logLine = sanitizeForLog(rendered); + uint32_t count = 0; + { + std::lock_guard lock(g_printfLogMutex); + count = ++g_printfLogCount; + } + if (count <= kMaxPrintfLogs) + { + std::cout << "PS2 printf: " << logLine; + std::cout << std::flush; + } + else if (count == kMaxPrintfLogs + 1) + { + std::cerr << "PS2 printf logging suppressed after " << kMaxPrintfLogs << " lines" << std::endl; + } + ret = static_cast(rendered.size()); + } + else + { + std::cerr << "_printf_r error: Invalid format string address provided: 0x" << std::hex << format_addr << std::dec << std::endl; + } + + setReturnS32(ctx, ret); } void _sceCdRI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1613,7 +3166,7 @@ namespace ps2_stubs void _sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("_sceSifLoadElfPart", rdram, ctx, runtime); + ps2_syscalls::SifLoadElfPart(rdram, ctx, runtime); } void _sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1643,7 +3196,7 @@ namespace ps2_stubs void close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("close", rdram, ctx, runtime); + ps2_syscalls::fioClose(rdram, ctx, runtime); } void DmaAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1658,7 +3211,14 @@ namespace ps2_stubs void fstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("fstat", rdram, ctx, runtime); + uint32_t statAddr = getRegU32(ctx, 5); + if (uint8_t *statBuf = getMemPtr(rdram, statAddr)) + { + std::memset(statBuf, 0, 128); + setReturnS32(ctx, 0); + return; + } + setReturnS32(ctx, -1); } void getpid(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1673,7 +3233,7 @@ namespace ps2_stubs void lseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("lseek", rdram, ctx, runtime); + ps2_syscalls::fioLseek(rdram, ctx, runtime); } void mcCallMessageTypeSe(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1898,12 +3458,12 @@ namespace ps2_stubs void open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("open", rdram, ctx, runtime); + ps2_syscalls::fioOpen(rdram, ctx, runtime); } void Pad_init(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("Pad_init", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void Pad_set(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1918,192 +3478,446 @@ namespace ps2_stubs void read(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("read", rdram, ctx, runtime); + ps2_syscalls::fioRead(rdram, ctx, runtime); } void sceCdApplyNCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdApplyNCmd", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdBreak(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdBreak", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdCallback", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceCdChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdChangeThreadPriority", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdDelayThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdDelayThread", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceCdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdDiskReady", rdram, ctx, runtime); + setReturnS32(ctx, 2); } void sceCdGetDiskType(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdGetDiskType", rdram, ctx, runtime); + // SCECdPS2DVD + setReturnS32(ctx, 0x14); } void sceCdGetReadPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdGetReadPos", rdram, ctx, runtime); + setReturnU32(ctx, g_cdStreamingLbn); } void sceCdGetToc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdGetToc", rdram, ctx, runtime); + uint32_t tocAddr = getRegU32(ctx, 4); + if (uint8_t *toc = getMemPtr(rdram, tocAddr)) + { + std::memset(toc, 0, 1024); + } + setReturnS32(ctx, 1); } void sceCdInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdInit", rdram, ctx, runtime); + g_cdInitialized = true; + g_lastCdError = 0; + setReturnS32(ctx, 1); } void sceCdInitEeCB(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdInitEeCB", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdIntToPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdIntToPos", rdram, ctx, runtime); + uint32_t lsn = getRegU32(ctx, 4); + uint32_t posAddr = getRegU32(ctx, 5); + uint8_t *pos = getMemPtr(rdram, posAddr); + if (!pos) + { + setReturnS32(ctx, 0); + return; + } + + uint32_t adjusted = lsn + 150; + const uint32_t minutes = adjusted / (60 * 75); + adjusted %= (60 * 75); + const uint32_t seconds = adjusted / 75; + const uint32_t sectors = adjusted % 75; + + pos[0] = toBcd(minutes); + pos[1] = toBcd(seconds); + pos[2] = toBcd(sectors); + pos[3] = 0; + setReturnS32(ctx, 1); } void sceCdMmode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdMmode", rdram, ctx, runtime); + g_cdMode = getRegU32(ctx, 4); + setReturnS32(ctx, 1); } void sceCdNcmdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdNcmdDiskReady", rdram, ctx, runtime); + setReturnS32(ctx, 2); } void sceCdPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdPause", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdPosToInt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdPosToInt", rdram, ctx, runtime); + uint32_t posAddr = getRegU32(ctx, 4); + const uint8_t *pos = getConstMemPtr(rdram, posAddr); + if (!pos) + { + setReturnS32(ctx, -1); + return; + } + + const uint32_t minutes = fromBcd(pos[0]); + const uint32_t seconds = fromBcd(pos[1]); + const uint32_t sectors = fromBcd(pos[2]); + const uint32_t absolute = (minutes * 60 * 75) + (seconds * 75) + sectors; + const int32_t lsn = static_cast(absolute) - 150; + setReturnS32(ctx, lsn); } void sceCdReadChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdReadChain", rdram, ctx, runtime); + uint32_t chainAddr = getRegU32(ctx, 4); + bool ok = true; + + for (int i = 0; i < 64; ++i) + { + uint32_t *entry = reinterpret_cast(getMemPtr(rdram, chainAddr + (i * 16))); + if (!entry) + { + ok = false; + break; + } + + const uint32_t lbn = entry[0]; + const uint32_t sectors = entry[1]; + const uint32_t buf = entry[2]; + if (lbn == 0xFFFFFFFFu || sectors == 0) + { + break; + } + + uint32_t offset = buf & PS2_RAM_MASK; + size_t bytes = static_cast(sectors) * kCdSectorSize; + const size_t maxBytes = PS2_RAM_SIZE - offset; + if (bytes > maxBytes) + { + bytes = maxBytes; + } + + if (!readCdSectors(lbn, sectors, rdram + offset, bytes)) + { + ok = false; + break; + } + + g_cdStreamingLbn = lbn + sectors; + } + + setReturnS32(ctx, ok ? 1 : 0); } void sceCdReadClock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdReadClock", rdram, ctx, runtime); + uint32_t clockAddr = getRegU32(ctx, 4); + uint8_t *clockData = getMemPtr(rdram, clockAddr); + if (!clockData) + { + setReturnS32(ctx, 0); + return; + } + + std::time_t now = std::time(nullptr); + std::tm localTm{}; +#ifdef _WIN32 + localtime_s(&localTm, &now); +#else + localtime_r(&now, &localTm); +#endif + + // sceCdCLOCK format (BCD fields). + clockData[0] = 0; + clockData[1] = toBcd(static_cast(localTm.tm_sec)); + clockData[2] = toBcd(static_cast(localTm.tm_min)); + clockData[3] = toBcd(static_cast(localTm.tm_hour)); + clockData[4] = 0; + clockData[5] = toBcd(static_cast(localTm.tm_mday)); + clockData[6] = toBcd(static_cast(localTm.tm_mon + 1)); + clockData[7] = toBcd(static_cast((localTm.tm_year + 1900) % 100)); + setReturnS32(ctx, 1); } void sceCdReadIOPm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdReadIOPm", rdram, ctx, runtime); + sceCdRead(rdram, ctx, runtime); } void sceCdSearchFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdSearchFile", rdram, ctx, runtime); + uint32_t fileAddr = getRegU32(ctx, 4); + uint32_t pathAddr = getRegU32(ctx, 5); + const std::string path = readPs2CStringBounded(rdram, pathAddr, 260); + const std::string normalizedPath = normalizeCdPathNoPrefix(path); + static uint32_t traceCount = 0; + const uint32_t callerRa = getRegU32(ctx, 31); + const bool shouldTrace = (traceCount < 128u) || ((traceCount % 512u) == 0u); + if (shouldTrace) + { + std::cout << "[sceCdSearchFile] pc=0x" << std::hex << ctx->pc + << " ra=0x" << callerRa + << " file=0x" << fileAddr + << " pathAddr=0x" << pathAddr + << " path=\"" << sanitizeForLog(path) << "\"" + << std::dec << std::endl; + } + ++traceCount; + + if (path.empty()) + { + static uint32_t emptyPathCount = 0; + if (emptyPathCount < 64 || (emptyPathCount % 512u) == 0u) + { + std::ostringstream preview; + preview << std::hex; + for (uint32_t i = 0; i < 16; ++i) + { + const uint8_t byte = *getConstMemPtr(rdram, pathAddr + i); + preview << (i == 0 ? "" : " ") << static_cast(byte); + } + std::cerr << "[sceCdSearchFile] empty path at 0x" << std::hex << pathAddr + << " preview=" << preview.str() + << " ra=0x" << callerRa << std::dec << std::endl; + } + ++emptyPathCount; + g_lastCdError = -1; + setReturnS32(ctx, 0); + return; + } + + if (normalizedPath.empty()) + { + static uint32_t emptyNormalizedCount = 0; + if (emptyNormalizedCount < 64u || (emptyNormalizedCount % 512u) == 0u) + { + std::cerr << "sceCdSearchFile failed: " << sanitizeForLog(path) + << " (normalized path is empty, root: " << getCdRootPath().string() << ")" + << std::endl; + } + ++emptyNormalizedCount; + g_lastCdError = -1; + setReturnS32(ctx, 0); + return; + } + + CdFileEntry entry; + bool found = registerCdFile(path, entry); + CdFileEntry resolvedEntry = entry; + std::string resolvedPath; + bool usedRemapFallback = false; + + // Remap is fallback-only: if the requested .IDX exists, keep it. + // This avoids feeding AFS payload sectors to code that expects IDX metadata. + if (!found) + { + const CdFileEntry missingEntry{}; + if (tryRemapGdInitSearchToAfs(path, callerRa, missingEntry, resolvedEntry, resolvedPath)) + { + found = true; + usedRemapFallback = true; + } + } + + if (!found) + { + static std::string lastFailedPath; + static uint32_t samePathFailCount = 0; + if (path == lastFailedPath) + { + ++samePathFailCount; + } + else + { + lastFailedPath = path; + samePathFailCount = 1; + } + + if (samePathFailCount <= 16u || (samePathFailCount % 512u) == 0u) + { + std::cerr << "sceCdSearchFile failed: " << sanitizeForLog(path) + << " (root: " << getCdRootPath().string() + << ", repeat=" << samePathFailCount << ")" << std::endl; + } + setReturnS32(ctx, 0); + return; + } + + if (usedRemapFallback) + { + std::cout << "[sceCdSearchFile] remap gd-init search \"" << sanitizeForLog(path) + << "\" -> \"" << sanitizeForLog(resolvedPath) << "\"" << std::endl; + } + + if (!writeCdSearchResult(rdram, fileAddr, path, resolvedEntry)) + { + g_lastCdError = -1; + setReturnS32(ctx, 0); + return; + } + + g_cdStreamingLbn = resolvedEntry.baseLbn; + if (shouldTrace) + { + std::cout << "[sceCdSearchFile:ok] path=\"" << sanitizeForLog(path) + << "\" lsn=0x" << std::hex << resolvedEntry.baseLbn + << " size=0x" << resolvedEntry.sizeBytes + << " sectors=0x" << resolvedEntry.sectors + << std::dec << std::endl; + } + setReturnS32(ctx, 1); } void sceCdSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdSeek", rdram, ctx, runtime); + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); } void sceCdStandby(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStandby", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStatus", rdram, ctx, runtime); + setReturnS32(ctx, g_cdInitialized ? 6 : 0); } void sceCdStInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStInit", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStop", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdStPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStPause", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdStRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStRead", rdram, ctx, runtime); + uint32_t sectors = getRegU32(ctx, 4); + uint32_t buf = getRegU32(ctx, 5); + uint32_t errAddr = getRegU32(ctx, 7); + + uint32_t offset = buf & PS2_RAM_MASK; + size_t bytes = static_cast(sectors) * kCdSectorSize; + const size_t maxBytes = PS2_RAM_SIZE - offset; + if (bytes > maxBytes) + { + bytes = maxBytes; + } + + const bool ok = readCdSectors(g_cdStreamingLbn, sectors, rdram + offset, bytes); + if (ok) + { + g_cdStreamingLbn += sectors; + } + + if (int32_t *err = reinterpret_cast(getMemPtr(rdram, errAddr)); err) + { + *err = ok ? 0 : g_lastCdError; + } + + setReturnS32(ctx, ok ? static_cast(sectors) : 0); } void sceCdStream(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStream", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdStResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStResume", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdStSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStSeek", rdram, ctx, runtime); + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); } void sceCdStSeekF(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStSeekF", rdram, ctx, runtime); + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); } void sceCdStStart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStStart", rdram, ctx, runtime); + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); } void sceCdStStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStStat", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceCdStStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdStStop", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceCdSyncS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdSyncS", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceCdTrayReq(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceCdTrayReq", rdram, ctx, runtime); + uint32_t statusPtr = getRegU32(ctx, 5); + if (uint32_t *status = reinterpret_cast(getMemPtr(rdram, statusPtr)); status) + { + *status = 0; + } + setReturnS32(ctx, 1); } void sceClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceClose", rdram, ctx, runtime); + ps2_syscalls::fioClose(rdram, ctx, runtime); } void sceDeci2Close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2163,7 +3977,9 @@ namespace ps2_stubs void sceDmaGetChan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaGetChan", rdram, ctx, runtime); + const uint32_t chanArg = getRegU32(ctx, 4); + const uint32_t channelBase = resolveDmaChannelBase(rdram, chanArg); + setReturnU32(ctx, channelBase); } void sceDmaGetEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2208,7 +4024,7 @@ namespace ps2_stubs void sceDmaReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaReset", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceDmaRestart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2218,32 +4034,32 @@ namespace ps2_stubs void sceDmaSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaSend", rdram, ctx, runtime); + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); } void sceDmaSendI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaSendI", rdram, ctx, runtime); + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); } void sceDmaSendM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaSendM", rdram, ctx, runtime); + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); } void sceDmaSendN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaSendN", rdram, ctx, runtime); + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, true)); } void sceDmaSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaSync", rdram, ctx, runtime); + setReturnS32(ctx, submitDmaSync(rdram, ctx, runtime)); } void sceDmaSyncN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceDmaSyncN", rdram, ctx, runtime); + setReturnS32(ctx, submitDmaSync(rdram, ctx, runtime)); } void sceDmaWatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2258,42 +4074,232 @@ namespace ps2_stubs void sceFsReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceFsReset", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsExecLoadImage", rdram, ctx, runtime); + uint32_t imgAddr = getRegU32(ctx, 4); + uint32_t srcAddr = getRegU32(ctx, 5); + + GsImageMem img{}; + if (!runtime || !readGsImage(rdram, imgAddr, img)) + { + setReturnS32(ctx, -1); + return; + } + + const uint32_t rowBytes = bytesForPixels(img.psm, static_cast(img.width)); + if (rowBytes == 0) + { + setReturnS32(ctx, -1); + return; + } + + uint32_t fbw = img.vram_width ? img.vram_width : std::max(1, (img.width + 63) / 64); + uint32_t base = static_cast(img.vram_addr) * 2048u; + uint32_t stride = bytesForPixels(img.psm, fbw * 64u); + if (stride == 0) + { + setReturnS32(ctx, -1); + return; + } + + uint8_t *gsvram = runtime->memory().getGSVRAM(); + uint8_t *src = getMemPtr(rdram, srcAddr); + if (!gsvram || !src) + { + setReturnS32(ctx, -1); + return; + } + + static int logCount = 0; + if (logCount < 8) + { + std::cout << "ps2_stub sceGsExecLoadImage: x=" << img.x + << " y=" << img.y + << " w=" << img.width + << " h=" << img.height + << " vram=0x" << std::hex << img.vram_addr + << " fbw=" << std::dec << static_cast(fbw) + << " psm=" << static_cast(img.psm) + << " src=0x" << std::hex << srcAddr << std::dec << std::endl; + ++logCount; + } + + for (uint32_t row = 0; row < img.height; ++row) + { + uint32_t dstOff = base + (static_cast(img.y) + row) * stride + bytesForPixels(img.psm, static_cast(img.x)); + uint32_t srcOff = row * rowBytes; + if (dstOff >= PS2_GS_VRAM_SIZE) + break; + uint32_t copyBytes = rowBytes; + if (dstOff + copyBytes > PS2_GS_VRAM_SIZE) + copyBytes = PS2_GS_VRAM_SIZE - dstOff; + std::memcpy(gsvram + dstOff, src + srcOff, copyBytes); + } + + if (img.width >= 320 && img.height >= 200) + { + auto &gs = runtime->memory().gs(); + gs.dispfb1 = makeDispFb(img.vram_addr, fbw, img.psm, 0, 0); + gs.display1 = makeDisplay(0, 0, 0, 0, img.width - 1, img.height - 1); + } + + setReturnS32(ctx, 0); } void sceGsExecStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsExecStoreImage", rdram, ctx, runtime); + uint32_t imgAddr = getRegU32(ctx, 4); + uint32_t dstAddr = getRegU32(ctx, 5); + + GsImageMem img{}; + if (!runtime || !readGsImage(rdram, imgAddr, img)) + { + setReturnS32(ctx, -1); + return; + } + + const uint32_t rowBytes = bytesForPixels(img.psm, static_cast(img.width)); + if (rowBytes == 0) + { + setReturnS32(ctx, -1); + return; + } + + uint32_t fbw = img.vram_width ? img.vram_width : std::max(1, (img.width + 63) / 64); + uint32_t base = static_cast(img.vram_addr) * 2048u; + uint32_t stride = bytesForPixels(img.psm, fbw * 64u); + if (stride == 0) + { + setReturnS32(ctx, -1); + return; + } + + uint8_t *gsvram = runtime->memory().getGSVRAM(); + uint8_t *dst = getMemPtr(rdram, dstAddr); + if (!gsvram || !dst) + { + setReturnS32(ctx, -1); + return; + } + + static int logCount = 0; + if (logCount < 8) + { + std::cout << "ps2_stub sceGsExecStoreImage: x=" << img.x + << " y=" << img.y + << " w=" << img.width + << " h=" << img.height + << " vram=0x" << std::hex << img.vram_addr + << " fbw=" << std::dec << static_cast(fbw) + << " psm=" << static_cast(img.psm) + << " dst=0x" << std::hex << dstAddr << std::dec << std::endl; + ++logCount; + } + + for (uint32_t row = 0; row < img.height; ++row) + { + uint32_t srcOff = base + (static_cast(img.y) + row) * stride + bytesForPixels(img.psm, static_cast(img.x)); + uint32_t dstOff = row * rowBytes; + if (srcOff >= PS2_GS_VRAM_SIZE) + break; + uint32_t copyBytes = rowBytes; + if (srcOff + copyBytes > PS2_GS_VRAM_SIZE) + copyBytes = PS2_GS_VRAM_SIZE - srcOff; + std::memcpy(dst + dstOff, gsvram + srcOff, copyBytes); + } + + setReturnS32(ctx, 0); } void sceGsGetGParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsGetGParam", rdram, ctx, runtime); + uint32_t addr = writeGsGParamToScratch(runtime); + setReturnU32(ctx, addr); } void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsPutDispEnv", rdram, ctx, runtime); + uint32_t envAddr = getRegU32(ctx, 4); + GsDispEnvMem env{}; + if (readGsDispEnv(rdram, envAddr, env)) + { + auto &gs = runtime->memory().gs(); + gs.display1 = env.display; + gs.dispfb1 = env.dispfb; + } + setReturnS32(ctx, 0); } void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsPutDrawEnv", rdram, ctx, runtime); + uint32_t envAddr = getRegU32(ctx, 4); + uint32_t psm = getRegU32(ctx, 5); + uint32_t w = getRegU32(ctx, 6); + uint32_t h = getRegU32(ctx, 7); + + if (w == 0) + w = 640; + if (h == 0) + h = 448; + + GsDrawEnvMem env{}; + env.offset_x = static_cast(2048 - (w / 2)); + env.offset_y = static_cast(2048 - (h / 2)); + env.clip_x = 0; + env.clip_y = 0; + env.clip_w = static_cast(w); + env.clip_h = static_cast(h); + env.vram_addr = 0; + env.fbw = static_cast((w + 63) / 64); + env.psm = static_cast(psm); + env.vram_x = 0; + env.vram_y = 0; + env.draw_mask = 0; + env.auto_clear = 1; + env.bg_r = 1; + env.bg_g = 1; + env.bg_b = 1; + env.bg_a = 0x80; + env.bg_q = 0.0f; + + uint8_t *ptr = getMemPtr(rdram, envAddr); + if (ptr) + { + std::memcpy(ptr, &env, sizeof(env)); + } + setReturnS32(ctx, 0); } void sceGsResetGraph(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsResetGraph", rdram, ctx, runtime); + uint32_t mode = getRegU32(ctx, 4); + uint32_t interlace = getRegU32(ctx, 5); + uint32_t omode = getRegU32(ctx, 6); + uint32_t ffmode = getRegU32(ctx, 7); + + if (mode == 0) + { + g_gparam.interlace = static_cast(interlace & 0x1); + g_gparam.omode = static_cast(omode & 0xFF); + g_gparam.ffmode = static_cast(ffmode & 0x1); + writeGsGParamToScratch(runtime); + + auto &gs = runtime->memory().gs(); + gs.pmode = makePmode(1, 0, 0, 0, 0, 0x80); + gs.smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1); + gs.dispfb1 = makeDispFb(0, 10, 0, 0, 0); + gs.display1 = makeDisplay(0, 0, 0, 0, 639, 447); + } + + setReturnS32(ctx, 0); } void sceGsResetPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsResetPath", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2303,12 +4309,29 @@ namespace ps2_stubs void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSetDefDBuffDc", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceGsSetDefDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSetDefDispEnv", rdram, ctx, runtime); + uint32_t envAddr = getRegU32(ctx, 4); + uint32_t psm = getRegU32(ctx, 5); + uint32_t w = getRegU32(ctx, 6); + uint32_t h = getRegU32(ctx, 7); + uint32_t dx = readStackU32(rdram, ctx, 16); + uint32_t dy = readStackU32(rdram, ctx, 20); + + if (w == 0) + w = 640; + if (h == 0) + h = 448; + + uint32_t fbw = (w + 63) / 64; + uint64_t dispfb = makeDispFb(0, fbw, psm, 0, 0); + uint64_t display = makeDisplay(dx, dy, 0, 0, w - 1, h - 1); + + writeGsDispEnv(rdram, envAddr, display, dispfb); + setReturnS32(ctx, 0); } void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2323,32 +4346,48 @@ namespace ps2_stubs void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSetDefLoadImage", rdram, ctx, runtime); + uint32_t imgAddr = getRegU32(ctx, 4); + const GsSetDefImageArgs args = decodeGsSetDefImageArgs(rdram, ctx); + + GsImageMem img{}; + img.x = static_cast(args.x); + img.y = static_cast(args.y); + img.width = static_cast(args.width); + img.height = static_cast(args.height); + img.vram_addr = static_cast(args.vramAddr); + img.vram_width = static_cast(args.vramWidth); + img.psm = static_cast(args.psm); + + writeGsImage(rdram, imgAddr, img); + setReturnS32(ctx, 0); } void sceGsSetDefStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSetDefStoreImage", rdram, ctx, runtime); + sceGsSetDefLoadImage(rdram, ctx, runtime); } void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSwapDBuffDc", rdram, ctx, runtime); + // can we get away with that ? kkkk + static int cur = 0; + cur ^= 1; + setReturnS32(ctx, cur); } void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSyncPath", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSyncV", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceGsSyncVCallback", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceGszbufaddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2383,7 +4422,7 @@ namespace ps2_stubs void sceLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceLseek", rdram, ctx, runtime); + ps2_syscalls::fioLseek(rdram, ctx, runtime); } void sceMcChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2438,7 +4477,13 @@ namespace ps2_stubs void sceMcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceMcInit", rdram, ctx, runtime); + static uint32_t logCount = 0; + if (logCount < 8) + { + std::cout << "ps2_stub sceMcInit -> 0" << std::endl; + ++logCount; + } + setReturnS32(ctx, 0); } void sceMcMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2608,7 +4653,7 @@ namespace ps2_stubs void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceOpen", rdram, ctx, runtime); + ps2_syscalls::fioOpen(rdram, ctx, runtime); } void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2643,27 +4688,41 @@ namespace ps2_stubs void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetModVersion", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // Arbitrary non-zero module version. + setReturnS32(ctx, 0x0200); } void scePadGetPortMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetPortMax", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 2); } void scePadGetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetReqState", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // 0 = completed/no pending request. + setReturnS32(ctx, 0); } void scePadGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetSlotMax", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // Most games use one slot unless multitap is active. + setReturnS32(ctx, 1); } void scePadGetState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadGetState", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // Pad state constants used by libpad: 6 means stable and ready. + setReturnS32(ctx, 6); } void scePadInfoAct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2678,37 +4737,104 @@ namespace ps2_stubs void scePadInfoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInfoMode", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + + const int32_t infoMode = static_cast(getRegU32(ctx, 6)); // a2 + const int32_t index = static_cast(getRegU32(ctx, 7)); // a3 + + // Minimal DualShock-like capabilities to keep game-side pad setup paths alive. + constexpr int32_t kPadTypeDualShock = 7; + switch (infoMode) + { + case 1: // PAD_MODECURID + setReturnS32(ctx, kPadTypeDualShock); + return; + case 2: // PAD_MODECUREXID + setReturnS32(ctx, kPadTypeDualShock); + return; + case 3: // PAD_MODECUROFFS + setReturnS32(ctx, 0); + return; + case 4: // PAD_MODETABLE + if (index == -1) + { + setReturnS32(ctx, 1); // one available mode + } + else if (index == 0) + { + setReturnS32(ctx, kPadTypeDualShock); + } + else + { + setReturnS32(ctx, 0); + } + return; + default: + setReturnS32(ctx, 0); + return; + } } void scePadInfoPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInfoPressMode", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + // Pressure mode is disabled in this minimal implementation. + setReturnS32(ctx, 0); } void scePadInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInit", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadInit2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadInit2", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadPortClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadPortClose", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadPortOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadPortOpen", rdram, ctx, runtime); + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); } void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("scePadRead", rdram, ctx, runtime); + (void)runtime; + + const uint32_t dataAddr = getRegU32(ctx, 6); // a2 + uint8_t *data = getMemPtr(rdram, dataAddr); + if (!data) + { + setReturnS32(ctx, 0); + return; + } + + // struct padButtonStatus (32 bytes): neutral state, no buttons pressed. + std::memset(data, 0, 32); + data[1] = 0x73; // analog/dualshock mode marker + data[2] = 0xFF; // btns low (active-low) + data[3] = 0xFF; // btns high + data[4] = 0x80; // rjoy_h + data[5] = 0x80; // rjoy_v + data[6] = 0x80; // ljoy_h + data[7] = 0x80; // ljoy_v + + setReturnS32(ctx, 1); } void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2763,7 +4889,7 @@ namespace ps2_stubs void sceRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceRead", rdram, ctx, runtime); + ps2_syscalls::fioRead(rdram, ctx, runtime); } void sceResetttyinit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2808,17 +4934,27 @@ namespace ps2_stubs void sceSifAllocIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifAllocIopHeap", rdram, ctx, runtime); + const uint32_t reqSize = getRegU32(ctx, 4); + const uint32_t alignedSize = (reqSize + (kIopHeapAlign - 1)) & ~(kIopHeapAlign - 1); + if (alignedSize == 0 || g_iopHeapNext + alignedSize > kIopHeapLimit) + { + setReturnS32(ctx, 0); + return; + } + + const uint32_t allocAddr = g_iopHeapNext; + g_iopHeapNext += alignedSize; + setReturnS32(ctx, static_cast(allocAddr)); } void sceSifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifBindRpc", rdram, ctx, runtime); + ps2_syscalls::SifBindRpc(rdram, ctx, runtime); } void sceSifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifCheckStatRpc", rdram, ctx, runtime); + ps2_syscalls::SifCheckStatRpc(rdram, ctx, runtime); } void sceSifDmaStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2828,7 +4964,7 @@ namespace ps2_stubs void sceSifExecRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifExecRequest", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifExitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2838,12 +4974,12 @@ namespace ps2_stubs void sceSifExitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifExitRpc", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifFreeIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifFreeIopHeap", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifGetDataTable(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2858,12 +4994,12 @@ namespace ps2_stubs void sceSifGetNextRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifGetNextRequest", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifGetOtherData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifGetOtherData", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifGetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2883,12 +5019,13 @@ namespace ps2_stubs void sceSifInitIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifInitIopHeap", rdram, ctx, runtime); + g_iopHeapNext = kIopHeapBase; + setReturnS32(ctx, 0); } void sceSifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifInitRpc", rdram, ctx, runtime); + ps2_syscalls::SifInitRpc(rdram, ctx, runtime); } void sceSifIsAliveIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2898,12 +5035,12 @@ namespace ps2_stubs void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifLoadElf", rdram, ctx, runtime); + ps2_syscalls::sceSifLoadElf(rdram, ctx, runtime); } void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifLoadElfPart", rdram, ctx, runtime); + ps2_syscalls::sceSifLoadElfPart(rdram, ctx, runtime); } void sceSifLoadFileReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2913,22 +5050,22 @@ namespace ps2_stubs void sceSifLoadIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifLoadIopHeap", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifLoadModuleBuffer", rdram, ctx, runtime); + ps2_syscalls::sceSifLoadModuleBuffer(rdram, ctx, runtime); } void sceSifRebootIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifRebootIop", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceSifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifRegisterRpc", rdram, ctx, runtime); + ps2_syscalls::SifRegisterRpc(rdram, ctx, runtime); } void sceSifRemoveCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2938,12 +5075,12 @@ namespace ps2_stubs void sceSifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifRemoveRpc", rdram, ctx, runtime); + ps2_syscalls::SifRemoveRpc(rdram, ctx, runtime); } void sceSifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifRemoveRpcQueue", rdram, ctx, runtime); + ps2_syscalls::SifRemoveRpcQueue(rdram, ctx, runtime); } void sceSifResetIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2953,7 +5090,7 @@ namespace ps2_stubs void sceSifRpcLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifRpcLoop", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSifSetCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -2983,7 +5120,7 @@ namespace ps2_stubs void sceSifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSetRpcQueue", rdram, ctx, runtime); + ps2_syscalls::SifSetRpcQueue(rdram, ctx, runtime); } void sceSifSetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -3003,7 +5140,7 @@ namespace ps2_stubs void sceSifSyncIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSifSyncIop", rdram, ctx, runtime); + setReturnS32(ctx, 1); } void sceSifWriteBackDCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -3063,7 +5200,7 @@ namespace ps2_stubs void sceSSyn_SetOutputMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceSSyn_SetOutputMode", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceSSyn_SetPortMaxPoly(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -3478,7 +5615,7 @@ namespace ps2_stubs void sceVpu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVpu0Reset", rdram, ctx, runtime); + setReturnS32(ctx, 0); } void sceVu0AddVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -3678,7 +5815,25 @@ namespace ps2_stubs void sceVu0UnitMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceVu0UnitMatrix", rdram, ctx, runtime); + const uint32_t dstAddr = getRegU32(ctx, 4); // sceVu0FMATRIX dst + alignas(16) const float identity[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + + if (!writeGuestBytes(rdram, runtime, dstAddr, reinterpret_cast(identity), sizeof(identity))) + { + static uint32_t warnCount = 0; + if (warnCount < 8) + { + std::cerr << "sceVu0UnitMatrix: failed to write matrix at 0x" + << std::hex << dstAddr << std::dec << std::endl; + ++warnCount; + } + } + + setReturnS32(ctx, 0); } void sceVu0ViewScreenMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -3688,7 +5843,7 @@ namespace ps2_stubs void sceWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("sceWrite", rdram, ctx, runtime); + ps2_syscalls::fioWrite(rdram, ctx, runtime); } void srand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -3708,21 +5863,66 @@ namespace ps2_stubs void vfprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("vfprintf", rdram, ctx, runtime); + uint32_t file_handle = getRegU32(ctx, 4); // $a0 + uint32_t format_addr = getRegU32(ctx, 5); // $a1 + uint32_t va_list_addr = getRegU32(ctx, 6); // $a2 + FILE *fp = get_file_ptr(file_handle); + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); + int ret = -1; + + if (fp && format_addr != 0) + { + std::string rendered = formatPs2StringWithVaList(rdram, runtime, formatOwned.c_str(), va_list_addr); + ret = std::fprintf(fp, "%s", rendered.c_str()); + } + else + { + std::cerr << "vfprintf error: Invalid file handle or format address." + << " Handle: 0x" << std::hex << file_handle << " (file valid: " << (fp != nullptr) << ")" + << ", Format: 0x" << format_addr << std::dec + << std::endl; + } + + setReturnS32(ctx, ret); } void vsprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("vsprintf", rdram, ctx, runtime); + uint32_t str_addr = getRegU32(ctx, 4); // $a0 + uint32_t format_addr = getRegU32(ctx, 5); // $a1 + uint32_t va_list_addr = getRegU32(ctx, 6); // $a2 + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); + int ret = -1; + + if (format_addr != 0) + { + std::string rendered = formatPs2StringWithVaList(rdram, runtime, formatOwned.c_str(), va_list_addr); + if (writeGuestBytes(rdram, runtime, str_addr, reinterpret_cast(rendered.c_str()), rendered.size() + 1u)) + { + ret = static_cast(rendered.size()); + } + else + { + std::cerr << "vsprintf error: Failed to write destination buffer at 0x" + << std::hex << str_addr << std::dec << std::endl; + } + } + else + { + std::cerr << "vsprintf error: Invalid address provided." + << " Dest: 0x" << std::hex << str_addr + << ", Format: 0x" << format_addr << std::dec + << std::endl; + } + + setReturnS32(ctx, ret); } void write(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - TODO_NAMED("write", rdram, ctx, runtime); + ps2_syscalls::fioWrite(rdram, ctx, runtime); } - // END AUTO-GENERATED FALLBACK STUBS - void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { TODO_NAMED("unknown", rdram, ctx, runtime); @@ -3730,10 +5930,28 @@ namespace ps2_stubs void TODO_NAMED(const char *name, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + const std::string stubName = name ? name : "unknown"; + uint32_t callCount = 0; + { + std::lock_guard lock(g_stubWarningMutex); + callCount = ++g_stubWarningCount[stubName]; + } + + if (callCount > kMaxStubWarningsPerName) + { + if (callCount == (kMaxStubWarningsPerName + 1)) + { + std::cerr << "Warning: Further calls to PS2 stub '" << stubName + << "' are suppressed after " << kMaxStubWarningsPerName << " warnings" << std::endl; + } + setReturnS32(ctx, -1); + return; + } + uint32_t stub_num = getRegU32(ctx, 2); // $v0 uint32_t caller_ra = getRegU32(ctx, 31); // $ra - std::cerr << "Warning: Unimplemented PS2 stub called. name=" << (name ? name : "unknown") + std::cerr << "Warning: Unimplemented PS2 stub called. name=" << stubName << " PC=0x" << std::hex << ctx->pc << ", RA=0x" << caller_ra << ", Stub# guess (from $v0)=0x" << stub_num << std::dec << std::endl; diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/ps2_syscalls.cpp index 4d85cb8..6975c17 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/ps2_syscalls.cpp @@ -3,22 +3,111 @@ #include "ps2_runtime_macros.h" #include "ps2_stubs.h" #include +#include +#include #include #include -#include #include +#include #include #include #include #include #include #include +#include +#include +#include +#include #ifndef _WIN32 -#include // for unlink,rmdir,chdir +#include // for unlink,rmdir,chdir #include // for mkdir #endif +#include +std::string translatePs2Path(const char *ps2Path); + +namespace +{ + std::string toLowerAscii(std::string value) + { + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char c) + { return static_cast(std::tolower(c)); }); + return value; + } + + std::string stripIsoVersionSuffix(std::string value) + { + const std::size_t semicolon = value.find(';'); + if (semicolon == std::string::npos) + { + return value; + } + + bool numericSuffix = semicolon + 1 < value.size(); + for (std::size_t i = semicolon + 1; i < value.size(); ++i) + { + if (!std::isdigit(static_cast(value[i]))) + { + numericSuffix = false; + break; + } + } + + if (numericSuffix) + { + value.erase(semicolon); + } + return value; + } + + std::string normalizePs2PathSuffix(std::string suffix) + { + std::replace(suffix.begin(), suffix.end(), '\\', '/'); + suffix = stripIsoVersionSuffix(std::move(suffix)); + while (!suffix.empty() && (suffix.front() == '/' || suffix.front() == '\\')) + { + suffix.erase(suffix.begin()); + } + return suffix; + } + + std::filesystem::path getConfiguredHostRoot() + { + const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); + if (!paths.hostRoot.empty()) + { + return paths.hostRoot; + } + if (!paths.elfDirectory.empty()) + { + return paths.elfDirectory; + } + + std::error_code ec; + const std::filesystem::path cwd = std::filesystem::current_path(ec); + return ec ? std::filesystem::path(".") : cwd.lexically_normal(); + } + + std::filesystem::path getConfiguredCdRoot() + { + const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); + if (!paths.cdRoot.empty()) + { + return paths.cdRoot; + } + if (!paths.elfDirectory.empty()) + { + return paths.elfDirectory; + } + + std::error_code ec; + const std::filesystem::path cwd = std::filesystem::current_path(ec); + return ec ? std::filesystem::path(".") : cwd.lexically_normal(); + } +} std::unordered_map g_fileDescriptors; int g_nextFd = 3; // Start after stdin, stdout, stderr @@ -34,8 +123,1191 @@ struct ThreadInfo uint32_t option = 0; uint32_t arg = 0; bool started = false; + uint32_t tlsBase = 0; + + // Thread Status + int status = 0x10; // THS_DORMANT + int waitType = 0; // TSW_NONE + int waitId = 0; + int wakeupCount = 0; + int currentPriority = 0; + int suspendCount = 0; + + std::mutex m; + std::condition_variable cv; + std::atomic forceRelease{false}; + std::atomic terminated{false}; }; +// Thread status +#define THS_RUN 0x01 +#define THS_READY 0x02 +#define THS_WAIT 0x04 +#define THS_SUSPEND 0x08 +#define THS_WAITSUSPEND 0x0c +#define THS_DORMANT 0x10 + +// Thread WAIT Status +#define TSW_NONE 0 +#define TSW_SLEEP 1 +#define TSW_SEMA 2 +#define TSW_EVENT 3 + +// Common kernel-like error codes used by thread/event/alarm syscalls. +constexpr int KE_OK = 0; +constexpr int KE_ERROR = -1; +constexpr int KE_ILLEGAL_MODE = -405; +constexpr int KE_ILLEGAL_THID = -406; +constexpr int KE_UNKNOWN_THID = -407; +constexpr int KE_UNKNOWN_SEMID = -408; +constexpr int KE_UNKNOWN_EVFID = -409; +constexpr int KE_DORMANT = -413; +constexpr int KE_NOT_WAIT = -416; +constexpr int KE_RELEASE_WAIT = -418; +constexpr int KE_SEMA_ZERO = -419; +constexpr int KE_EVF_COND = -421; +constexpr int KE_EVF_MULTI = -422; +constexpr int KE_EVF_ILPAT = -423; +constexpr int KE_WAIT_DELETE = -425; + +// SIF RPC Structures +struct t_SifRpcHeader +{ + uint32_t pkt_addr; // void* + uint32_t rpc_id; + int sema_id; + uint32_t mode; +}; + +struct t_SifRpcClientData +{ + t_SifRpcHeader hdr; + uint32_t command; + uint32_t buf; // void* + uint32_t cbuf; // void* + uint32_t end_function; // func ptr + uint32_t end_param; // void* + uint32_t server; // t_SifRpcServerData* +}; + +struct t_SifRpcServerData +{ + int sid; + uint32_t func; // func ptr + uint32_t buf; // void* + int size; + uint32_t cfunc; // func ptr + uint32_t cbuf; // void* + int size2; + uint32_t client; // t_SifRpcClientData* + uint32_t pkt_addr; // void* + int rpc_number; + uint32_t recvbuf; // void* + int rsize; + int rmode; + int rid; + uint32_t link; // t_SifRpcServerData* + uint32_t next; // t_SifRpcServerData* + uint32_t base; // t_SifRpcDataQueue* +}; + +struct t_SifRpcDataQueue +{ + int thread_id; + int active; + uint32_t link; // t_SifRpcServerData* + uint32_t start; // t_SifRpcServerData* + uint32_t end; // t_SifRpcServerData* + uint32_t next; // t_SifRpcDataQueue* +}; + +struct ee_thread_status_t +{ + int status; // 0x00 + uint32_t func; // 0x04 + uint32_t stack; // 0x08 + int stack_size; // 0x0C + uint32_t gp_reg; // 0x10 + int initial_priority; // 0x14 + int current_priority; // 0x18 + uint32_t attr; // 0x1C + uint32_t option; // 0x20 + uint32_t waitType; // 0x24 + uint32_t waitId; // 0x28 + uint32_t wakeupCount; // 0x2C +}; + +struct ee_sema_t +{ + int count; + int max_count; + int init_count; + int wait_threads; + uint32_t attr; + uint32_t option; +}; + +struct SemaInfo +{ + int count = 0; + int maxCount = 0; + int initCount = 0; + uint32_t attr = 0; + uint32_t option = 0; + int waiters = 0; + std::mutex m; + std::condition_variable cv; +}; + +struct EventFlagInfo +{ + uint32_t attr = 0; + uint32_t option = 0; + uint32_t initBits = 0; + uint32_t bits = 0; + int waiters = 0; + bool deleted = false; + std::mutex m; + std::condition_variable cv; +}; + +struct AlarmInfo +{ + int id = 0; + uint16_t ticks = 0; + uint32_t handler = 0; + uint32_t commonArg = 0; + uint32_t gp = 0; + uint32_t sp = 0; + uint8_t *rdram = nullptr; + PS2Runtime *runtime = nullptr; + std::chrono::steady_clock::time_point dueAt; +}; + +struct io_stat_t +{ + uint32_t mode; + uint32_t attr; + uint32_t size; + uint8_t ctime[8]; + uint8_t atime[8]; + uint8_t mtime[8]; + uint32_t hisize; +}; + +static constexpr uint32_t kFioSoIfLnk = 0x0008; +static constexpr uint32_t kFioSoIfReg = 0x0010; +static constexpr uint32_t kFioSoIfDir = 0x0020; +static constexpr uint32_t kFioSoIROth = 0x0004; +static constexpr uint32_t kFioSoIWOth = 0x0002; +static constexpr uint32_t kFioSoIXOth = 0x0001; + +static std::unordered_map> g_threads; +static int g_nextThreadId = 2; // Reserve 1 for the main thread +static thread_local int g_currentThreadId = 1; +static std::mutex g_thread_map_mutex; + +static std::unordered_map> g_semas; +static int g_nextSemaId = 1; +static std::mutex g_sema_map_mutex; +static std::unordered_map> g_eventFlags; +static int g_nextEventFlagId = 1; +static std::mutex g_event_flag_map_mutex; +static std::unordered_map> g_alarms; +static int g_nextAlarmId = 1; +static std::mutex g_alarm_mutex; +static std::condition_variable g_alarm_cv; +static std::once_flag g_alarm_worker_once; +std::atomic g_activeThreads{0}; + +struct RpcServerState +{ + uint32_t sid = 0; + uint32_t sd_ptr = 0; // PS2 address +}; + +struct RpcClientState +{ + bool busy = false; + uint32_t last_rpc = 0; + uint32_t sid = 0; +}; + +static std::unordered_map g_rpc_servers; +static std::unordered_map g_rpc_clients; +static std::mutex g_rpc_mutex; +static bool g_rpc_initialized = false; +static uint32_t g_rpc_next_id = 1; +static uint32_t g_rpc_packet_index = 0; +static uint32_t g_rpc_server_index = 0; +static uint32_t g_rpc_active_queue = 0; +static constexpr uint32_t kDtxRpcSid = 0x7D000000u; +static constexpr uint32_t kDtxUrpcObjBase = 0x01F18000u; +static constexpr uint32_t kDtxUrpcObjLimit = 0x01F1FF00u; +static constexpr uint32_t kDtxUrpcFnTableBase = 0x0034FED0u; +static constexpr uint32_t kDtxUrpcObjTableBase = 0x0034FFD0u; +static std::mutex g_dtx_rpc_mutex; +static std::unordered_map g_dtx_remote_by_id; +static uint32_t g_dtx_next_urpc_obj = kDtxUrpcObjBase; + +struct DtxSjrmtState +{ + uint32_t handle = 0; + uint32_t mode = 0; + uint32_t wkAddr = 0; + uint32_t wkSize = 0; + uint32_t readPos = 0; + uint32_t writePos = 0; + uint32_t roomBytes = 0; + uint32_t dataBytes = 0; + uint32_t uuid0 = 0; + uint32_t uuid1 = 0; + uint32_t uuid2 = 0; + uint32_t uuid3 = 0; +}; + +static std::unordered_map g_dtx_sjrmt_by_handle; + +static uint32_t dtxNormalizeSjrmtCapacity(uint32_t requestedBytes) +{ + if (requestedBytes == 0u || requestedBytes > 0x01000000u) + { + return 0x4000u; + } + return requestedBytes; +} + +static uint32_t dtxAllocUrpcHandleLocked() +{ + for (uint32_t i = 0; i < 4096u; ++i) + { + uint32_t candidate = g_dtx_next_urpc_obj; + g_dtx_next_urpc_obj += 0x20u; + if (g_dtx_next_urpc_obj < kDtxUrpcObjBase || g_dtx_next_urpc_obj >= kDtxUrpcObjLimit) + { + g_dtx_next_urpc_obj = kDtxUrpcObjBase; + } + + if (candidate < kDtxUrpcObjBase || candidate >= kDtxUrpcObjLimit) + { + continue; + } + + if (g_dtx_sjrmt_by_handle.find(candidate) != g_dtx_sjrmt_by_handle.end()) + { + continue; + } + + bool inUseByDtxRemote = false; + for (const auto &entry : g_dtx_remote_by_id) + { + if (entry.second == candidate) + { + inUseByDtxRemote = true; + break; + } + } + + if (!inUseByDtxRemote) + { + return candidate; + } + } + + return kDtxUrpcObjBase; +} + +struct ExitHandlerEntry +{ + uint32_t func = 0; + uint32_t arg = 0; +}; + +static std::mutex g_exit_handler_mutex; +static std::unordered_map> g_exit_handlers; + +static std::mutex g_bootmode_mutex; +static bool g_bootmode_initialized = false; +static uint32_t g_bootmode_pool_offset = 0; +static std::unordered_map g_bootmode_addresses; + +static std::mutex g_tls_mutex; +static uint32_t g_tls_index = 0; + +static std::mutex g_osd_mutex; +static bool g_osd_config_initialized = false; +static uint32_t g_osd_config_raw = 0; + +static std::mutex g_ps2_path_mutex; +static bool g_ps2_paths_initialized = false; +static std::filesystem::path g_host_base; +static std::filesystem::path g_cdrom_base; +static std::filesystem::path g_host_cwd; +static std::filesystem::path g_cdrom_cwd; +static std::string g_ps2_cwd_device = "host0"; + +static constexpr uint32_t kRpcPacketSize = 64; +static constexpr uint32_t kRpcPacketPoolBase = 0x01F00000; +static constexpr uint32_t kRpcPacketPoolBytes = 0x00010000; +static constexpr uint32_t kRpcPacketPoolCount = kRpcPacketPoolBytes / kRpcPacketSize; +static constexpr uint32_t kRpcServerPoolBase = 0x01F10000; +static constexpr uint32_t kRpcServerPoolBytes = 0x00010000; +static constexpr uint32_t kRpcServerStride = 0x80; +static constexpr uint32_t kRpcServerPoolCount = kRpcServerPoolBytes / kRpcServerStride; + +static constexpr uint32_t kTlsPoolBase = 0x01F20000; +static constexpr uint32_t kTlsPoolBytes = 0x00010000; +static constexpr uint32_t kTlsBlockSize = 0x100; +static constexpr uint32_t kTlsPoolCount = kTlsPoolBytes / kTlsBlockSize; + +static constexpr uint32_t kBootModePoolBase = 0x01F30000; +static constexpr uint32_t kBootModePoolBytes = 0x00001000; + +static constexpr uint32_t kSifRpcModeNowait = 0x01; +static constexpr uint32_t kSifRpcModeNoWbDc = 0x02; +static constexpr size_t kMaxSifModulePathBytes = 260; +static constexpr uint32_t kMaxSifModuleLogs = 24; +static constexpr size_t kSifModuleBufferProbeBytes = 2048; +static constexpr size_t kLoadfilePathMaxBytes = 252; +static constexpr size_t kLoadfileArgMaxBytes = 252; +static constexpr uint32_t kElfMagic = 0x464C457Fu; +static constexpr uint16_t kElfMachineMips = 8u; +static constexpr uint16_t kElfTypeExec = 2u; +static constexpr uint32_t kElfPtLoad = 1u; +static constexpr uint32_t kElfPtMipsRegInfo = 0x70000000u; +static constexpr uint32_t kElfShtMipsRegInfo = 0x70000006u; + +#pragma pack(push, 1) +struct Elf32Header +{ + uint32_t magic; + uint8_t elfClass; + uint8_t endianness; + uint8_t version; + uint8_t osAbi; + uint8_t abiVersion; + uint8_t pad[7]; + uint16_t type; + uint16_t machine; + uint32_t version2; + uint32_t entry; + uint32_t phoff; + uint32_t shoff; + uint32_t flags; + uint16_t ehsize; + uint16_t phentsize; + uint16_t phnum; + uint16_t shentsize; + uint16_t shnum; + uint16_t shstrndx; +}; + +struct Elf32ProgramHeader +{ + uint32_t type; + uint32_t offset; + uint32_t vaddr; + uint32_t paddr; + uint32_t filesz; + uint32_t memsz; + uint32_t flags; + uint32_t align; +}; + +struct Elf32SectionHeader +{ + uint32_t name; + uint32_t type; + uint32_t flags; + uint32_t addr; + uint32_t offset; + uint32_t size; + uint32_t link; + uint32_t info; + uint32_t addralign; + uint32_t entsize; +}; + +struct GuestExecData +{ + uint32_t epc; + uint32_t gp; + uint32_t sp; + uint32_t dummy; +}; +#pragma pack(pop) + +static_assert(sizeof(Elf32Header) == 52u, "Unexpected ELF32 header layout."); +static_assert(sizeof(Elf32ProgramHeader) == 32u, "Unexpected ELF32 program header layout."); +static_assert(sizeof(Elf32SectionHeader) == 40u, "Unexpected ELF32 section header layout."); +static_assert(sizeof(GuestExecData) == 16u, "Unexpected GuestExecData layout."); + +struct SifModuleRecord +{ + int32_t id = 0; + std::string path; + std::string pathKey; + uint32_t refCount = 0; + bool loaded = false; +}; + +static std::mutex g_sif_module_mutex; +static std::unordered_map g_sif_modules_by_id; +static std::unordered_map g_sif_module_id_by_path; +static int32_t g_next_sif_module_id = 1; +static uint32_t g_sif_module_log_count = 0; + +namespace +{ + std::string readGuestCStringBounded(const uint8_t *rdram, uint32_t guestAddr, size_t maxBytes) + { + std::string out; + if (!rdram || guestAddr == 0 || maxBytes == 0) + { + return out; + } + + out.reserve(maxBytes); + for (size_t i = 0; i < maxBytes; ++i) + { + const char ch = static_cast(rdram[(guestAddr + static_cast(i)) & PS2_RAM_MASK]); + if (ch == '\0') + { + break; + } + out.push_back(ch); + } + return out; + } + + std::string normalizeSifModulePathKey(const std::string &path) + { + return toLowerAscii(normalizePs2PathSuffix(path)); + } + + uint64_t hashGuestBytesFnv1a64(const uint8_t *rdram, uint32_t guestAddr, size_t byteCount) + { + constexpr uint64_t kOffset = 1469598103934665603ull; + constexpr uint64_t kPrime = 1099511628211ull; + + if (!rdram || guestAddr == 0 || byteCount == 0) + { + return 0ull; + } + + uint64_t hash = kOffset; + for (size_t i = 0; i < byteCount; ++i) + { + const uint8_t b = rdram[(guestAddr + static_cast(i)) & PS2_RAM_MASK]; + hash ^= static_cast(b); + hash *= kPrime; + } + return hash; + } + + std::string makeSifModuleBufferTag(const uint8_t *rdram, uint32_t bufferAddr) + { + char key[96] = {}; + const uint64_t hash = hashGuestBytesFnv1a64(rdram, bufferAddr, kSifModuleBufferProbeBytes); + std::snprintf(key, sizeof(key), "iopbuf:fnv64:%016llx", static_cast(hash)); + return std::string(key); + } + + void logSifModuleAction(const char *op, int32_t moduleId, const std::string &path, uint32_t refCount) + { + if (!op) + { + return; + } + + std::lock_guard lock(g_sif_module_mutex); + if (g_sif_module_log_count >= kMaxSifModuleLogs) + { + return; + } + + std::cout << "[SIF module] " << op + << " id=" << moduleId + << " ref=" << refCount + << " path=\"" << path << "\"" + << std::endl; + ++g_sif_module_log_count; + } + + int32_t trackSifModuleLoad(const std::string &path) + { + if (path.empty()) + { + return -1; + } + + const std::string pathKey = normalizeSifModulePathKey(path); + if (pathKey.empty()) + { + return -1; + } + + std::lock_guard lock(g_sif_module_mutex); + + auto byPathIt = g_sif_module_id_by_path.find(pathKey); + if (byPathIt != g_sif_module_id_by_path.end()) + { + auto byIdIt = g_sif_modules_by_id.find(byPathIt->second); + if (byIdIt != g_sif_modules_by_id.end()) + { + SifModuleRecord &record = byIdIt->second; + record.loaded = true; + ++record.refCount; + return record.id; + } + } + + if (g_next_sif_module_id <= 0) + { + g_next_sif_module_id = 1; + } + + const int32_t moduleId = g_next_sif_module_id++; + SifModuleRecord record; + record.id = moduleId; + record.path = path; + record.pathKey = pathKey; + record.refCount = 1; + record.loaded = true; + + g_sif_module_id_by_path[pathKey] = moduleId; + g_sif_modules_by_id[moduleId] = record; + return moduleId; + } + + bool trackSifModuleStop(int32_t moduleId, uint32_t *remainingRefs = nullptr) + { + if (moduleId <= 0) + { + if (remainingRefs) + { + *remainingRefs = 0; + } + return false; + } + + std::lock_guard lock(g_sif_module_mutex); + auto it = g_sif_modules_by_id.find(moduleId); + if (it == g_sif_modules_by_id.end()) + { + if (remainingRefs) + { + *remainingRefs = 0; + } + return false; + } + + SifModuleRecord &record = it->second; + if (record.refCount > 0) + { + --record.refCount; + } + record.loaded = (record.refCount != 0); + + if (remainingRefs) + { + *remainingRefs = record.refCount; + } + return true; + } + + bool readFileBlockAt(std::ifstream &file, uint64_t offset, void *dst, size_t byteCount) + { + if (!dst || byteCount == 0) + { + return false; + } + + file.seekg(static_cast(offset), std::ios::beg); + if (!file) + { + return false; + } + + file.read(reinterpret_cast(dst), static_cast(byteCount)); + return file.gcount() == static_cast(byteCount); + } + + bool tryExtractElfGpValue(std::ifstream &file, const Elf32Header &header, uint32_t &gpOut) + { + uint8_t regInfo[24] = {}; + + for (uint32_t i = 0; i < header.phnum; ++i) + { + Elf32ProgramHeader ph{}; + const uint64_t phOffset = static_cast(header.phoff) + static_cast(i) * header.phentsize; + if (!readFileBlockAt(file, phOffset, &ph, sizeof(ph))) + { + return false; + } + + if (ph.type == kElfPtMipsRegInfo && ph.filesz >= sizeof(regInfo)) + { + if (!readFileBlockAt(file, ph.offset, regInfo, sizeof(regInfo))) + { + return false; + } + std::memcpy(&gpOut, regInfo + 20u, sizeof(gpOut)); + return true; + } + } + + for (uint32_t i = 0; i < header.shnum; ++i) + { + Elf32SectionHeader sh{}; + const uint64_t shOffset = static_cast(header.shoff) + static_cast(i) * header.shentsize; + if (!readFileBlockAt(file, shOffset, &sh, sizeof(sh))) + { + return false; + } + + if (sh.type == kElfShtMipsRegInfo && sh.size >= sizeof(regInfo)) + { + if (!readFileBlockAt(file, sh.offset, regInfo, sizeof(regInfo))) + { + return false; + } + std::memcpy(&gpOut, regInfo + 20u, sizeof(gpOut)); + return true; + } + } + + return false; + } + + bool loadElfIntoGuestMemory(const std::string &hostPath, + uint8_t *rdram, + PS2Runtime *runtime, + const std::string §ionName, + GuestExecData &execDataOut, + std::string &errorOut) + { + if (!rdram || hostPath.empty()) + { + errorOut = "invalid path or RDRAM pointer"; + return false; + } + + std::ifstream file(hostPath, std::ios::binary); + if (!file) + { + errorOut = "failed to open ELF"; + return false; + } + + Elf32Header header{}; + if (!readFileBlockAt(file, 0, &header, sizeof(header))) + { + errorOut = "failed to read ELF header"; + return false; + } + + if (header.magic != kElfMagic || header.machine != kElfMachineMips || header.type != kElfTypeExec) + { + errorOut = "not a MIPS executable ELF"; + return false; + } + + bool loadedAny = false; + const bool loadAll = sectionName.empty() || toLowerAscii(sectionName) == "all"; + static uint32_t secFilterLogCount = 0; + if (!loadAll && secFilterLogCount < 8u) + { + std::cout << "[SifLoadElfPart] section filter \"" << sectionName + << "\" requested; loading PT_LOAD segments only." << std::endl; + ++secFilterLogCount; + } + + for (uint32_t i = 0; i < header.phnum; ++i) + { + Elf32ProgramHeader ph{}; + const uint64_t phOffset = static_cast(header.phoff) + static_cast(i) * header.phentsize; + if (!readFileBlockAt(file, phOffset, &ph, sizeof(ph))) + { + errorOut = "failed to read ELF program headers"; + return false; + } + + if (ph.type != kElfPtLoad || ph.memsz == 0u) + { + continue; + } + if (ph.filesz > ph.memsz) + { + errorOut = "ELF segment filesz > memsz"; + return false; + } + + const uint64_t memSize64 = static_cast(ph.memsz); + if (runtime && ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) + { + const uint32_t scratchOffset = runtime->memory().translateAddress(ph.vaddr); + if (static_cast(scratchOffset) + memSize64 > PS2_SCRATCHPAD_SIZE) + { + errorOut = "ELF scratchpad segment out of range"; + return false; + } + + uint8_t *dest = runtime->memory().getScratchpad() + scratchOffset; + if (ph.filesz > 0u) + { + if (!readFileBlockAt(file, ph.offset, dest, ph.filesz)) + { + errorOut = "failed to read ELF segment payload"; + return false; + } + } + if (ph.memsz > ph.filesz) + { + std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); + } + } + else + { + const uint32_t physAddr = runtime ? runtime->memory().translateAddress(ph.vaddr) : (ph.vaddr & PS2_RAM_MASK); + if (static_cast(physAddr) + memSize64 > PS2_RAM_SIZE) + { + errorOut = "ELF RDRAM segment out of range"; + return false; + } + + uint8_t *dest = rdram + physAddr; + if (ph.filesz > 0u) + { + if (!readFileBlockAt(file, ph.offset, dest, ph.filesz)) + { + errorOut = "failed to read ELF segment payload"; + return false; + } + } + if (ph.memsz > ph.filesz) + { + std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); + } + } + + loadedAny = true; + } + + if (!loadedAny) + { + errorOut = "ELF has no loadable segments"; + return false; + } + + execDataOut.epc = header.entry; + execDataOut.gp = 0u; + execDataOut.sp = 0u; + execDataOut.dummy = 0u; + + uint32_t gpValue = 0u; + if (tryExtractElfGpValue(file, header, gpValue)) + { + execDataOut.gp = gpValue; + } + + return true; + } + + int32_t runSifLoadElfPart(uint8_t *rdram, + R5900Context *ctx, + PS2Runtime *runtime, + uint32_t pathAddr, + const std::string §ionName, + uint32_t execDataAddr) + { + if (!rdram || !ctx) + { + return -1; + } + + const std::string ps2Path = readGuestCStringBounded(rdram, pathAddr, kLoadfilePathMaxBytes); + if (ps2Path.empty()) + { + return -1; + } + + const std::string hostPath = translatePs2Path(ps2Path.c_str()); + if (hostPath.empty()) + { + return -1; + } + + GuestExecData execData{}; + std::string loadError; + if (!loadElfIntoGuestMemory(hostPath, rdram, runtime, sectionName, execData, loadError)) + { + static uint32_t logCount = 0; + if (logCount < 16u) + { + std::cerr << "[SifLoadElfPart] failed path=\"" << ps2Path << "\" host=\"" << hostPath + << "\" reason=" << loadError << std::endl; + ++logCount; + } + return -1; + } + + if (execData.gp == 0u) + { + execData.gp = getRegU32(ctx, 28); + } + execData.sp = getRegU32(ctx, 29); + + if (execDataAddr != 0u) + { + GuestExecData *guestExec = reinterpret_cast(getMemPtr(rdram, execDataAddr)); + if (!guestExec) + { + return -1; + } + std::memcpy(guestExec, &execData, sizeof(execData)); + } + + static uint32_t successLogs = 0; + if (successLogs < 16u) + { + std::cout << "[SifLoadElfPart] loaded \"" << ps2Path << "\" epc=0x" + << std::hex << execData.epc << " gp=0x" << execData.gp << std::dec << std::endl; + ++successLogs; + } + + return 0; + } +} + +namespace +{ + struct ThreadExitException final : public std::exception + { + const char *what() const noexcept override + { + return "PS2 Thread Exit"; + } + }; +} + +static void applySuspendStatusLocked(ThreadInfo &info) +{ + if (info.waitType != TSW_NONE) + { + info.status = THS_WAITSUSPEND; + } + else + { + info.status = THS_SUSPEND; + } +} + +static void throwIfTerminated(const std::shared_ptr &info) +{ + if (info && info->terminated.load()) + { + throw ThreadExitException(); + } +} + +static void waitWhileSuspended(const std::shared_ptr &info) +{ + if (!info) + return; + + std::unique_lock lock(info->m); + if (info->suspendCount > 0) + { + info->status = THS_SUSPEND; + info->waitType = TSW_NONE; + info->waitId = 0; + info->cv.wait(lock, [&]() + { return info->suspendCount == 0 || info->terminated.load(); }); + if (info->terminated.load()) + { + throw ThreadExitException(); + } + info->status = THS_RUN; + } +} + +static std::shared_ptr lookupThreadInfo(int tid) +{ + std::lock_guard lock(g_thread_map_mutex); + auto it = g_threads.find(tid); + if (it != g_threads.end()) + { + return it->second; + } + return nullptr; +} + +static std::shared_ptr ensureCurrentThreadInfo(R5900Context *ctx) +{ + const int tid = g_currentThreadId; + std::lock_guard lock(g_thread_map_mutex); + auto it = g_threads.find(tid); + if (it != g_threads.end()) + { + return it->second; + } + + auto info = std::make_shared(); + info->started = true; + info->status = THS_RUN; + info->currentPriority = info->priority; + info->suspendCount = 0; + if (ctx) + { + info->entry = ctx->pc; + info->stack = getRegU32(ctx, 29); + info->gp = getRegU32(ctx, 28); + } + info->waitType = TSW_NONE; + info->waitId = 0; + + g_threads.emplace(tid, info); + return info; +} + +static std::shared_ptr lookupSemaInfo(int sid) +{ + std::lock_guard lock(g_sema_map_mutex); + auto it = g_semas.find(sid); + if (it != g_semas.end()) + { + return it->second; + } + return nullptr; +} + +static std::shared_ptr lookupEventFlagInfo(int eid) +{ + std::lock_guard lock(g_event_flag_map_mutex); + auto it = g_eventFlags.find(eid); + if (it != g_eventFlags.end()) + { + return it->second; + } + return nullptr; +} + +static void setRegU32(R5900Context *ctx, int reg, uint32_t value) +{ + if (reg < 0 || reg > 31) + return; + ctx->r[reg] = _mm_set_epi32(0, 0, 0, value); +} + +static std::chrono::microseconds alarmTicksToDuration(uint16_t ticks) +{ + constexpr uint64_t kAlarmTickUsec = 64u; // Approximate EE H-SYNC tick period. + const uint64_t clampedTicks = (ticks == 0u) ? 1u : static_cast(ticks); + return std::chrono::microseconds(clampedTicks * kAlarmTickUsec); +} + +static void ensureAlarmWorkerRunning() +{ + std::call_once(g_alarm_worker_once, []() + { + std::thread([]() + { + for (;;) + { + std::shared_ptr readyAlarm; + { + std::unique_lock lock(g_alarm_mutex); + while (!readyAlarm) + { + if (g_alarms.empty()) + { + g_alarm_cv.wait(lock); + continue; + } + + auto nextIt = std::min_element(g_alarms.begin(), g_alarms.end(), + [](const auto &a, const auto &b) + { + return a.second->dueAt < b.second->dueAt; + }); + if (nextIt == g_alarms.end()) + { + g_alarm_cv.wait(lock); + continue; + } + + const auto now = std::chrono::steady_clock::now(); + if (nextIt->second->dueAt > now) + { + g_alarm_cv.wait_until(lock, nextIt->second->dueAt); + continue; + } + + readyAlarm = nextIt->second; + g_alarms.erase(nextIt); + } + } + + if (!readyAlarm || !readyAlarm->runtime || !readyAlarm->rdram || !readyAlarm->handler) + { + continue; + } + if (!readyAlarm->runtime->hasFunction(readyAlarm->handler)) + { + continue; + } + + try + { + R5900Context callbackCtx{}; + setRegU32(&callbackCtx, 28, readyAlarm->gp); + setRegU32(&callbackCtx, 29, readyAlarm->sp); + setRegU32(&callbackCtx, 31, 0); + setRegU32(&callbackCtx, 4, static_cast(readyAlarm->id)); + setRegU32(&callbackCtx, 5, static_cast(readyAlarm->ticks)); + setRegU32(&callbackCtx, 6, readyAlarm->commonArg); + setRegU32(&callbackCtx, 7, 0); + callbackCtx.pc = readyAlarm->handler; + + PS2Runtime::RecompiledFunction func = readyAlarm->runtime->lookupFunction(readyAlarm->handler); + func(readyAlarm->rdram, &callbackCtx, readyAlarm->runtime); + } + catch (const ThreadExitException &) + { + } + catch (const std::exception &e) + { + static int alarmExceptionLogs = 0; + if (alarmExceptionLogs < 8) + { + std::cerr << "[SetAlarm] callback exception: " << e.what() << std::endl; + ++alarmExceptionLogs; + } + } + } }) + .detach(); }); +} + +static void rpcCopyToRdram(uint8_t *rdram, uint32_t dst, uint32_t src, size_t size) +{ + if (!rdram || size == 0) + return; + + constexpr size_t kMaxRpcTransferBytes = 1u * 1024u * 1024u; + const size_t clampedSize = std::min(size, kMaxRpcTransferBytes); + if (clampedSize != size) + { + static uint32_t warnCount = 0; + if (warnCount < 8) + { + std::cerr << "[SifCallRpc] clamping copy size from " << size + << " to " << clampedSize + << " bytes (dst=0x" << std::hex << dst + << " src=0x" << src << std::dec << ")" << std::endl; + ++warnCount; + } + } + + for (size_t i = 0; i < clampedSize; ++i) + { + const uint32_t dstAddr = dst + static_cast(i); + const uint32_t srcAddr = src + static_cast(i); + uint8_t *dstPtr = getMemPtr(rdram, dstAddr); + const uint8_t *srcPtr = getConstMemPtr(rdram, srcAddr); + if (!dstPtr || !srcPtr) + { + break; + } + *dstPtr = *srcPtr; + } +} + +static void rpcZeroRdram(uint8_t *rdram, uint32_t dst, size_t size) +{ + if (!rdram || size == 0) + return; + + constexpr size_t kMaxRpcTransferBytes = 1u * 1024u * 1024u; + const size_t clampedSize = std::min(size, kMaxRpcTransferBytes); + if (clampedSize != size) + { + static uint32_t warnCount = 0; + if (warnCount < 8) + { + std::cerr << "[SifCallRpc] clamping zero size from " << size + << " to " << clampedSize + << " bytes (dst=0x" << std::hex << dst << std::dec << ")" << std::endl; + ++warnCount; + } + } + + for (size_t i = 0; i < clampedSize; ++i) + { + const uint32_t dstAddr = dst + static_cast(i); + uint8_t *dstPtr = getMemPtr(rdram, dstAddr); + if (!dstPtr) + { + break; + } + *dstPtr = 0; + } +} + +static bool readStackU32(uint8_t *rdram, uint32_t sp, uint32_t offset, uint32_t &out) +{ + uint8_t *ptr = getMemPtr(rdram, sp + offset); + if (!ptr) + return false; + out = *reinterpret_cast(ptr); + return true; +} + +static bool rpcInvokeFunction(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, + uint32_t funcAddr, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t *outV0) +{ + if (!runtime || !funcAddr || !runtime->hasFunction(funcAddr)) + return false; + + R5900Context tmp = *ctx; + setRegU32(&tmp, 4, a0); + setRegU32(&tmp, 5, a1); + setRegU32(&tmp, 6, a2); + setRegU32(&tmp, 7, a3); + tmp.pc = funcAddr; + + PS2Runtime::RecompiledFunction func = runtime->lookupFunction(funcAddr); + func(rdram, &tmp, runtime); + + if (outV0) + { + *outV0 = getRegU32(&tmp, 2); + } + return true; +} + +static uint32_t rpcAllocPacketAddr(uint8_t *rdram) +{ + if (kRpcPacketPoolCount == 0) + return 0; + + uint32_t slot = g_rpc_packet_index++ % kRpcPacketPoolCount; + uint32_t addr = kRpcPacketPoolBase + (slot * kRpcPacketSize); + rpcZeroRdram(rdram, addr, kRpcPacketSize); + return addr; +} + +static uint32_t rpcAllocServerAddr(uint8_t *rdram) +{ + if (kRpcServerPoolCount == 0) + return 0; + + uint32_t slot = g_rpc_server_index++ % kRpcServerPoolCount; + uint32_t addr = kRpcServerPoolBase + (slot * kRpcServerStride); + rpcZeroRdram(rdram, addr, kRpcServerStride); + return addr; +} +/* struct SemaInfo { int count = 0; @@ -51,6 +1323,20 @@ static thread_local int g_currentThreadId = 1; static std::unordered_map> g_semas; static int g_nextSemaId = 1; std::atomic g_activeThreads{0}; +*/ + +struct IrqHandlerInfo +{ + uint32_t cause = 0; + uint32_t handler = 0; + uint32_t arg = 0; + bool enabled = true; +}; + +static std::unordered_map g_intcHandlers; +static std::unordered_map g_dmacHandlers; +static int g_nextIntcHandlerId = 1; +static int g_nextDmacHandlerId = 1; int allocatePs2Fd(FILE *file) { @@ -111,34 +1397,539 @@ const char *translateFioMode(int ps2Flags) std::string translatePs2Path(const char *ps2Path) { + if (!ps2Path || !*ps2Path) + { + return {}; + } + std::string pathStr(ps2Path); - if (pathStr.rfind("host0:", 0) == 0) + std::string lower = toLowerAscii(pathStr); + + auto resolveWithBase = [&](const std::filesystem::path &base, const std::string &suffix) -> std::string { - // Map host0: to ./host_fs/ relative to executable - std::filesystem::path hostBasePath = std::filesystem::current_path() / "host_fs"; - std::filesystem::create_directories(hostBasePath); // Ensure it exists - return (hostBasePath / pathStr.substr(6)).string(); - } - else if (pathStr.rfind("cdrom0:", 0) == 0) + const std::string normalizedSuffix = normalizePs2PathSuffix(suffix); + std::filesystem::path resolved = base; + if (!normalizedSuffix.empty()) + { + resolved /= std::filesystem::path(normalizedSuffix); + } + return resolved.lexically_normal().string(); + }; + + if (lower.rfind("host0:", 0) == 0 || lower.rfind("host:", 0) == 0) { - // Map cdrom0: to ./cd_fs/ relative to executable (for example) - std::filesystem::path cdBasePath = std::filesystem::current_path() / "cd_fs"; - std::filesystem::create_directories(cdBasePath); // Ensure it exists - return (cdBasePath / pathStr.substr(7)).string(); + const std::size_t prefixLength = (lower.rfind("host0:", 0) == 0) ? 6 : 5; + return resolveWithBase(getConfiguredHostRoot(), pathStr.substr(prefixLength)); } - std::cerr << "Warning: Unsupported PS2 path prefix: " << pathStr << std::endl; - return ""; + + if (lower.rfind("cdrom0:", 0) == 0 || lower.rfind("cdrom:", 0) == 0) + { + const std::size_t prefixLength = (lower.rfind("cdrom0:", 0) == 0) ? 7 : 6; + return resolveWithBase(getConfiguredCdRoot(), pathStr.substr(prefixLength)); + } + + if (!pathStr.empty() && (pathStr.front() == '/' || pathStr.front() == '\\')) + { + return resolveWithBase(getConfiguredCdRoot(), pathStr); + } + + if (pathStr.size() > 1 && pathStr[1] == ':') + { + return pathStr; + } + + return resolveWithBase(getConfiguredCdRoot(), pathStr); } -#include "ps2_syscalls.h" +static bool localtimeSafe(const std::time_t *t, std::tm *out) +{ +#ifdef _WIN32 + return localtime_s(out, t) == 0; +#else + return localtime_r(t, out) != nullptr; +#endif +} + +static void encodePs2Time(std::time_t t, uint8_t out[8]) +{ + std::tm tm{}; + if (!localtimeSafe(&t, &tm)) + { + std::memset(out, 0, 8); + return; + } + + uint16_t year = static_cast(tm.tm_year + 1900); + out[0] = 0; + out[1] = static_cast(tm.tm_sec); + out[2] = static_cast(tm.tm_min); + out[3] = static_cast(tm.tm_hour); + out[4] = static_cast(tm.tm_mday); + out[5] = static_cast(tm.tm_mon + 1); + out[6] = static_cast(year & 0xFF); + out[7] = static_cast((year >> 8) & 0xFF); +} + +static std::time_t fileTimeToTimeT(std::filesystem::file_time_type ft) +{ + auto sctp = std::chrono::time_point_cast( + ft - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()); + return std::chrono::system_clock::to_time_t(sctp); +} + +static bool gmtimeSafe(const std::time_t *t, std::tm *out) +{ +#ifdef _WIN32 + return gmtime_s(out, t) == 0; +#else + return gmtime_r(t, out) != nullptr; +#endif +} + +static int getTimezoneOffsetMinutes() +{ + std::time_t now = std::time(nullptr); + std::tm local{}; + std::tm gmt{}; + if (!localtimeSafe(&now, &local) || !gmtimeSafe(&now, &gmt)) + return 0; + + std::time_t localTime = std::mktime(&local); + std::time_t gmtTime = std::mktime(&gmt); + if (localTime == static_cast(-1) || gmtTime == static_cast(-1)) + return 0; + + double diff = std::difftime(localTime, gmtTime); + return static_cast(diff / 60.0); +} + +static uint32_t packOsdConfig(uint32_t spdifMode, uint32_t screenType, uint32_t videoOutput, + uint32_t japLanguage, uint32_t ps1drvConfig, uint32_t version, + uint32_t language, int timezoneOffset) +{ + uint32_t raw = 0; + raw |= (spdifMode & 0x1) << 0; + raw |= (screenType & 0x3) << 1; + raw |= (videoOutput & 0x1) << 3; + raw |= (japLanguage & 0x1) << 4; + raw |= (ps1drvConfig & 0xFF) << 5; + raw |= (version & 0x7) << 13; + raw |= (language & 0x1F) << 16; + raw |= (static_cast(timezoneOffset) & 0x7FF) << 21; + return raw; +} + +static int decodeTimezoneOffset(uint32_t raw) +{ + int tz = static_cast((raw >> 21) & 0x7FF); + if (tz & 0x400) + tz |= ~0x7FF; + return tz; +} + +static int clampTimezoneOffset(int tz) +{ + if (tz < -1024) + return -1024; + if (tz > 1023) + return 1023; + return tz; +} + +static uint32_t sanitizeOsdConfigRaw(uint32_t raw) +{ + uint32_t spdifMode = raw & 0x1; + uint32_t screenType = (raw >> 1) & 0x3; + if (screenType > 2) + screenType = 0; + uint32_t videoOutput = (raw >> 3) & 0x1; + uint32_t japLanguage = (raw >> 4) & 0x1; + uint32_t ps1drvConfig = (raw >> 5) & 0xFF; + uint32_t version = (raw >> 13) & 0x7; + if (version > 2) + version = 1; + uint32_t language = (raw >> 16) & 0x1F; + int tz = clampTimezoneOffset(decodeTimezoneOffset(raw)); + return packOsdConfig(spdifMode, screenType, videoOutput, japLanguage, ps1drvConfig, version, language, tz); +} + +static void ensureOsdConfigInitialized() +{ + std::lock_guard lock(g_osd_mutex); + if (g_osd_config_initialized) + return; + + int tz = clampTimezoneOffset(getTimezoneOffsetMinutes()); + uint32_t spdifMode = 1; // disabled + uint32_t screenType = 0; // 4:3 + uint32_t videoOutput = 0; // RGB + uint32_t japLanguage = 1; // non-japanese + uint32_t ps1drvConfig = 0; + uint32_t version = 1; // OSD2 + uint32_t language = 1; // English + g_osd_config_raw = packOsdConfig(spdifMode, screenType, videoOutput, japLanguage, ps1drvConfig, version, language, tz); + g_osd_config_initialized = true; +} + +static uint32_t allocTlsAddr(uint8_t *rdram) +{ + if (!rdram || kTlsPoolCount == 0) + return 0; + + std::lock_guard lock(g_tls_mutex); + uint32_t slot = g_tls_index++ % kTlsPoolCount; + uint32_t addr = kTlsPoolBase + (slot * kTlsBlockSize); + rpcZeroRdram(rdram, addr, kTlsBlockSize); + return addr; +} + +static uint32_t allocBootModeAddr(uint8_t *rdram, size_t bytes) +{ + if (!rdram) + return 0; + + size_t aligned = (bytes + 15u) & ~15u; + if (g_bootmode_pool_offset + aligned > kBootModePoolBytes) + return 0; + + uint32_t addr = kBootModePoolBase + g_bootmode_pool_offset; + g_bootmode_pool_offset += static_cast(aligned); + rpcZeroRdram(rdram, addr, aligned); + return addr; +} + +static uint32_t createBootModeEntry(uint8_t *rdram, uint8_t id, uint16_t value, uint8_t lenField, const uint32_t *data, uint8_t dataCount) +{ + uint8_t allocCount = (dataCount == 0) ? 1 : dataCount; + size_t bytes = static_cast(1 + allocCount) * sizeof(uint32_t); + uint32_t addr = allocBootModeAddr(rdram, bytes); + if (!addr) + return 0; + + uint32_t header = (static_cast(lenField) << 24) | + (static_cast(id) << 16) | + (static_cast(value) & 0xFFFFu); + + uint32_t *dst = reinterpret_cast(getMemPtr(rdram, addr)); + if (!dst) + return 0; + + dst[0] = header; + for (uint8_t i = 0; i < allocCount; ++i) + { + dst[1 + i] = (data && i < dataCount) ? data[i] : 0; + } + + return addr; +} + +static void ensureBootModeTable(uint8_t *rdram) +{ + std::lock_guard lock(g_bootmode_mutex); + if (g_bootmode_initialized) + return; + + g_bootmode_pool_offset = 0; + g_bootmode_addresses.clear(); + + const uint32_t boot3Data[1] = {0}; + const uint32_t boot5Data[1] = {0}; + + g_bootmode_addresses[1] = createBootModeEntry(rdram, 1, 0, 0, nullptr, 0); + g_bootmode_addresses[3] = createBootModeEntry(rdram, 3, 0, 1, boot3Data, 1); + g_bootmode_addresses[4] = createBootModeEntry(rdram, 4, 0, 0, nullptr, 0); + g_bootmode_addresses[5] = createBootModeEntry(rdram, 5, 0, 1, boot5Data, 1); + g_bootmode_addresses[6] = createBootModeEntry(rdram, 6, 0, 0, nullptr, 0); + g_bootmode_addresses[7] = createBootModeEntry(rdram, 7, 0, 0, nullptr, 0); + + g_bootmode_initialized = true; +} + +static void runExitHandlersForThread(int tid, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + if (!runtime || !ctx) + return; + + std::vector handlers; + { + std::lock_guard lock(g_exit_handler_mutex); + auto it = g_exit_handlers.find(tid); + if (it == g_exit_handlers.end()) + return; + handlers = std::move(it->second); + g_exit_handlers.erase(it); + } + + for (const auto &handler : handlers) + { + if (!handler.func) + continue; + try + { + rpcInvokeFunction(rdram, ctx, runtime, handler.func, handler.arg, 0, 0, 0, nullptr); + } + catch (const ThreadExitException &) + { + // ignore + } + catch (const std::exception &) + { + } + } +} namespace ps2_syscalls { + // for some bizarre case I have to duplicate this here + void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetupHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void EndOfHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + + bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + switch (syscallNumber) + { + case 0x01: + ResetEE(rdram, ctx, runtime); + return true; + case 0x02: + GsSetCrt(rdram, ctx, runtime); + return true; + case 0x04: + ExitThread(rdram, ctx, runtime); + return true; + case 0x10: + AddIntcHandler(rdram, ctx, runtime); + return true; + case 0x11: + RemoveIntcHandler(rdram, ctx, runtime); + return true; + case 0x12: + AddDmacHandler(rdram, ctx, runtime); + return true; + case 0x13: + RemoveDmacHandler(rdram, ctx, runtime); + return true; + case 0x14: + EnableIntc(rdram, ctx, runtime); + return true; + case 0x15: + DisableIntc(rdram, ctx, runtime); + return true; + case 0x16: + EnableDmac(rdram, ctx, runtime); + return true; + case 0x17: + DisableDmac(rdram, ctx, runtime); + return true; + case 0x18: + case 0xFC: + SetAlarm(rdram, ctx, runtime); + return true; + case 0x19: + case 0xFE: + CancelAlarm(rdram, ctx, runtime); + return true; + case static_cast(-0x1E): + case static_cast(-0xFD): + iSetAlarm(rdram, ctx, runtime); + return true; + case static_cast(-0x1F): + case static_cast(-0xFF): + iCancelAlarm(rdram, ctx, runtime); + return true; + case 0x20: + CreateThread(rdram, ctx, runtime); + return true; + case 0x21: + DeleteThread(rdram, ctx, runtime); + return true; + case 0x22: + StartThread(rdram, ctx, runtime); + return true; + case 0x23: + ExitThread(rdram, ctx, runtime); + return true; + case 0x24: + ExitDeleteThread(rdram, ctx, runtime); + return true; + case 0x25: + TerminateThread(rdram, ctx, runtime); + return true; + case 0x29: + case static_cast(-0x2A): + ChangeThreadPriority(rdram, ctx, runtime); + return true; + case 0x2B: + case static_cast(-0x2C): + RotateThreadReadyQueue(rdram, ctx, runtime); + return true; + case 0x2D: + ReleaseWaitThread(rdram, ctx, runtime); + return true; + case static_cast(-0x2E): + iReleaseWaitThread(rdram, ctx, runtime); + return true; + case 0x2F: + case static_cast(-0x2F): + GetThreadId(rdram, ctx, runtime); + return true; + case 0x30: + case static_cast(-0x31): + ReferThreadStatus(rdram, ctx, runtime); + return true; + case 0x32: + SleepThread(rdram, ctx, runtime); + return true; + case 0x33: + WakeupThread(rdram, ctx, runtime); + return true; + case static_cast(-0x34): + iWakeupThread(rdram, ctx, runtime); + return true; + case 0x35: + CancelWakeupThread(rdram, ctx, runtime); + return true; + case static_cast(-0x36): + iCancelWakeupThread(rdram, ctx, runtime); + return true; + case 0x37: + case static_cast(-0x38): + SuspendThread(rdram, ctx, runtime); + return true; + case 0x39: + case static_cast(-0x3A): + ResumeThread(rdram, ctx, runtime); + return true; + case 0x3C: + SetupThread(rdram, ctx, runtime); + return true; + case 0x3D: + SetupHeap(rdram, ctx, runtime); + return true; + case 0x3E: + EndOfHeap(rdram, ctx, runtime); + return true; + case 0x40: + CreateSema(rdram, ctx, runtime); + return true; + case 0x41: + case static_cast(-0x49): + DeleteSema(rdram, ctx, runtime); + return true; + case 0x42: + SignalSema(rdram, ctx, runtime); + return true; + case static_cast(-0x43): + iSignalSema(rdram, ctx, runtime); + return true; + case 0x44: + WaitSema(rdram, ctx, runtime); + return true; + case 0x45: + PollSema(rdram, ctx, runtime); + return true; + case static_cast(-0x46): + iPollSema(rdram, ctx, runtime); + return true; + case 0x47: + ReferSemaStatus(rdram, ctx, runtime); + return true; + case static_cast(-0x48): + iReferSemaStatus(rdram, ctx, runtime); + return true; + case 0x4A: + SetOsdConfigParam(rdram, ctx, runtime); + return true; + case 0x4B: + GetOsdConfigParam(rdram, ctx, runtime); + return true; + case 0x50: + CreateEventFlag(rdram, ctx, runtime); + return true; + case 0x51: + DeleteEventFlag(rdram, ctx, runtime); + return true; + case 0x52: + SetEventFlag(rdram, ctx, runtime); + return true; + case 0x53: + iSetEventFlag(rdram, ctx, runtime); + return true; + case 0x54: + ClearEventFlag(rdram, ctx, runtime); + return true; + case static_cast(-0x55): + iClearEventFlag(rdram, ctx, runtime); + return true; + case 0x56: + WaitEventFlag(rdram, ctx, runtime); + return true; + case 0x57: + PollEventFlag(rdram, ctx, runtime); + return true; + case static_cast(-0x58): + iPollEventFlag(rdram, ctx, runtime); + return true; + case 0x59: + ReferEventFlagStatus(rdram, ctx, runtime); + return true; + case static_cast(-0x5A): + iReferEventFlagStatus(rdram, ctx, runtime); + return true; + case 0x5A: + QueryBootMode(rdram, ctx, runtime); + return true; + case 0x5B: + GetThreadTLS(rdram, ctx, runtime); + return true; + case 0x5C: + case static_cast(-0x5C): + EnableIntcHandler(rdram, ctx, runtime); + return true; + case 0x5D: + case static_cast(-0x5D): + DisableIntcHandler(rdram, ctx, runtime); + return true; + case 0x5E: + case static_cast(-0x5E): + EnableDmacHandler(rdram, ctx, runtime); + return true; + case 0x5F: + case static_cast(-0x5F): + DisableDmacHandler(rdram, ctx, runtime); + return true; + case 0x64: + FlushCache(rdram, ctx, runtime); + return true; + case 0x70: + case static_cast(-0x70): + GsGetIMR(rdram, ctx, runtime); + return true; + case 0x71: + case static_cast(-0x71): + GsPutIMR(rdram, ctx, runtime); + return true; + case 0x74: + RegisterExitHandler(rdram, ctx, runtime); + return true; + case 0x85: + SetMemoryMode(rdram, ctx, runtime); + return true; + default: + return false; + } + } void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - std::cout << "Syscall: FlushCache (No-op)" << std::endl; - // No-op for now setReturnS32(ctx, 0); } @@ -150,9 +1941,7 @@ namespace ps2_syscalls void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // Affects memory mapping / TLB behavior. - // std::cout << "Syscall: SetMemoryMode (No-op)" << std::endl; - setReturnS32(ctx, 0); // Success + setReturnS32(ctx, 0); } void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -167,24 +1956,67 @@ namespace ps2_syscalls return; } - ThreadInfo info{}; - info.attr = param[0]; - info.entry = param[1]; - info.stack = param[2]; - info.stackSize = param[3]; - info.gp = param[5]; // Often gp is at offset 20 - info.priority = param[4]; // Commonly priority/init attr slot - info.option = param[6]; + auto info = std::make_shared(); + info->attr = param[0]; + info->entry = param[1]; + info->stack = param[2]; + info->stackSize = param[3]; - int id = g_nextThreadId++; - g_threads[id] = info; + auto looksLikeGuestPtr = [](uint32_t v) -> bool + { + if (v == 0) + { + return true; + } + const uint32_t norm = v & 0x1FFFFFFFu; + return norm < PS2_RAM_SIZE && norm >= 0x10000u; + }; + + auto looksLikePriority = [](uint32_t v) -> bool + { + // Typical EE priorities are very small integers (1..127). + return v <= 0x400u; + }; + + const uint32_t gpA = param[4]; + const uint32_t prioA = param[5]; + const uint32_t gpB = param[5]; + const uint32_t prioB = param[4]; + + // Prefer the standard EE layout (gp at +0x10, priority at +0x14), + // but keep a fallback for callsites that used the swapped decode. + if (looksLikeGuestPtr(gpA) && looksLikePriority(prioA)) + { + info->gp = gpA; + info->priority = prioA; + } + else if (looksLikeGuestPtr(gpB) && looksLikePriority(prioB)) + { + info->gp = gpB; + info->priority = prioB; + } + else + { + info->gp = gpA; + info->priority = prioA; + } + + info->option = param[6]; + info->currentPriority = static_cast(info->priority); + + int id = 0; + { + std::lock_guard lock(g_thread_map_mutex); + id = g_nextThreadId++; + g_threads[id] = info; + } std::cout << "[CreateThread] id=" << id - << " entry=0x" << std::hex << info.entry - << " stack=0x" << info.stack - << " size=0x" << info.stackSize - << " gp=0x" << info.gp - << " prio=" << std::dec << info.priority << std::endl; + << " entry=0x" << std::hex << info->entry + << " stack=0x" << info->stack + << " size=0x" << info->stackSize + << " gp=0x" << info->gp + << " prio=" << std::dec << info->priority << std::endl; setReturnS32(ctx, id); } @@ -192,6 +2024,7 @@ namespace ps2_syscalls void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); // $a0 + std::lock_guard lock(g_thread_map_mutex); g_threads.erase(tid); setReturnS32(ctx, 0); } @@ -201,122 +2034,285 @@ namespace ps2_syscalls int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg - auto it = g_threads.find(tid); - if (it == g_threads.end()) + auto info = lookupThreadInfo(tid); + if (!info) { std::cerr << "StartThread error: unknown thread id " << tid << std::endl; setReturnS32(ctx, -1); return; } - ThreadInfo &info = it->second; - if (info.started) { - setReturnS32(ctx, 0); - return; + std::lock_guard lock(info->m); + if (info->started) + { + setReturnS32(ctx, tid); // Already started + return; + } + + info->started = true; + info->status = THS_RUN; + info->arg = arg; } - info.started = true; - info.arg = arg; - - if (!runtime->hasFunction(info.entry)) + if (!runtime->hasFunction(info->entry)) { - std::cerr << "[StartThread] entry 0x" << std::hex << info.entry << std::dec << " is not registered" << std::endl; + std::cerr << "[StartThread] entry 0x" << std::hex << info->entry << std::dec << " is not registered" << std::endl; setReturnS32(ctx, -1); return; } - // TODO check later skip audio threads to avoid runaway recursion/stack overflows. - if (info.entry == 0x2f42a0 || info.entry == 0x2f4258) + const uint32_t callerSp = getRegU32(ctx, 29); + const uint32_t callerGp = getRegU32(ctx, 28); + { - std::cout << "[StartThread] id=" << tid - << " entry=0x" << std::hex << info.entry << std::dec - << " skipped (audio thread stub)" << std::endl; - setReturnS32(ctx, 0); - return; + std::lock_guard lock(info->m); + if (info->stack == 0 && info->stackSize != 0) + { + const uint32_t autoStack = runtime->guestMalloc(info->stackSize, 16u); + if (autoStack != 0) + { + info->stack = autoStack; + std::cout << "[StartThread] id=" << tid + << " auto-stack=0x" << std::hex << autoStack + << " size=0x" << info->stackSize << std::dec << std::endl; + } + } + + if (info->stack != 0 && info->stackSize == 0) + { + // Some games leave size zero in the thread param even though a stack + // buffer is supplied; use a conservative default instead of caller SP. + info->stackSize = 0x800u; + } } - // Spawn a host thread to simulate PS2 thread execution. g_activeThreads.fetch_add(1, std::memory_order_relaxed); std::thread([=]() mutable { - R5900Context threadCtxCopy = *ctx; // Copy current CPU state to simulate a new thread context + { + std::string name = "PS2Thread_" + std::to_string(tid); + ThreadNaming::SetCurrentThreadName(name); + } + R5900Context threadCtxCopy{}; R5900Context *threadCtx = &threadCtxCopy; - if (info.stack && info.stackSize) + uint32_t threadSp = callerSp; + if (info->stack) { - SET_GPR_U32(threadCtx, 29, info.stack + info.stackSize); // SP at top of stack + const uint32_t stackSize = (info->stackSize != 0) ? info->stackSize : 0x800u; + threadSp = (info->stack + stackSize) & ~0xFu; } - if (info.gp) + uint32_t threadGp = info->gp; + const uint32_t normalizedGp = threadGp & 0x1FFFFFFFu; + if (threadGp == 0 || normalizedGp < 0x10000u || normalizedGp >= PS2_RAM_SIZE) { - SET_GPR_U32(threadCtx, 28, info.gp); + threadGp = callerGp; } - SET_GPR_U32(threadCtx, 4, info.arg); - threadCtx->pc = info.entry; + SET_GPR_U32(threadCtx, 29, threadSp); + SET_GPR_U32(threadCtx, 28, threadGp); + SET_GPR_U32(threadCtx, 4, info->arg); + SET_GPR_U32(threadCtx, 31, 0); + threadCtx->pc = info->entry; - PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info.entry); + PS2Runtime::RecompiledFunction func = runtime->lookupFunction(info->entry); g_currentThreadId = tid; std::cout << "[StartThread] id=" << tid - << " entry=0x" << std::hex << info.entry + << " entry=0x" << std::hex << info->entry << " sp=0x" << GPR_U32(threadCtx, 29) << " gp=0x" << GPR_U32(threadCtx, 28) - << " arg=0x" << info.arg << std::dec << std::endl; + << " arg=0x" << info->arg << std::dec << std::endl; + bool exited = false; try { func(rdram, threadCtx, runtime); } + catch (const ThreadExitException &) + { + exited = true; + } catch (const std::exception &e) { std::cerr << "[StartThread] id=" << tid << " exception: " << e.what() << std::endl; } - std::cout << "[StartThread] id=" << tid << " returned (pc=0x" - << std::hex << threadCtx->pc << std::dec << ")" << std::endl; + if (!exited) + { + std::cout << "[StartThread] id=" << tid << " returned (pc=0x" + << std::hex << threadCtx->pc << std::dec << ")" << std::endl; + } + + runExitHandlersForThread(tid, rdram, threadCtx, runtime); + + { + std::lock_guard lock(info->m); + info->started = false; + info->status = THS_DORMANT; + } g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }) .detach(); // for now report success to the caller. - setReturnS32(ctx, 0); + setReturnS32(ctx, tid); } void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - std::cout << "PS2 ExitThread: Thread is exiting (PC=0x" << std::hex << ctx->pc << std::dec << ")" << std::endl; - setReturnS32(ctx, 0); + runExitHandlersForThread(g_currentThreadId, rdram, ctx, runtime); + auto info = ensureCurrentThreadInfo(ctx); + if (info) + { + std::lock_guard lock(info->m); + info->terminated = true; + info->forceRelease = true; + info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + } + if (info) + { + info->cv.notify_all(); + } + throw ThreadExitException(); } void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - int tid = static_cast(getRegU32(ctx, 4)); - g_threads.erase(tid); - setReturnS32(ctx, 0); + int tid = g_currentThreadId; + runExitHandlersForThread(tid, rdram, ctx, runtime); + auto info = ensureCurrentThreadInfo(ctx); + if (info) + { + std::lock_guard lock(info->m); + info->terminated = true; + info->forceRelease = true; + info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + } + if (info) + { + info->cv.notify_all(); + } + { + std::lock_guard lock(g_thread_map_mutex); + g_threads.erase(tid); + } + throw ThreadExitException(); } void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); - g_threads.erase(tid); + if (tid == 0) + tid = g_currentThreadId; + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, -1); + return; + } + + { + std::lock_guard lock(info->m); + info->terminated = true; + info->forceRelease = true; + info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + } + info->cv.notify_all(); + + if (tid == g_currentThreadId) + { + runExitHandlersForThread(tid, rdram, ctx, runtime); + throw ThreadExitException(); + } setReturnS32(ctx, 0); } void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; int tid = static_cast(getRegU32(ctx, 4)); - if (logCount < 16) + if (tid == 0) + tid = g_currentThreadId; + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (!info) { - std::cout << "[SuspendThread] tid=" << tid << std::endl; - ++logCount; + setReturnS32(ctx, -1); + return; } + + { + std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, -1); + return; + } + info->suspendCount++; + applySuspendStatusLocked(*info); + } + info->cv.notify_all(); + + if (tid == g_currentThreadId) + { + std::unique_lock lock(info->m); + info->cv.wait(lock, [&]() + { return info->suspendCount == 0 || info->terminated.load(); }); + if (info->terminated.load()) + { + throw ThreadExitException(); + } + info->status = THS_RUN; + } + setReturnS32(ctx, 0); } void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + int tid = static_cast(getRegU32(ctx, 4)); + if (tid == 0) + tid = g_currentThreadId; + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, -1); + return; + } + + { + std::lock_guard lock(info->m); + if (info->suspendCount <= 0) + { + setReturnS32(ctx, -1); + return; + } + info->suspendCount--; + if (info->suspendCount == 0) + { + if (info->waitType != TSW_NONE) + { + info->status = THS_WAIT; + } + else + { + info->status = (tid == g_currentThreadId) ? THS_RUN : THS_READY; + } + } + } + info->cv.notify_all(); setReturnS32(ctx, 0); } @@ -327,76 +2323,220 @@ namespace ps2_syscalls void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + int tid = static_cast(getRegU32(ctx, 4)); + uint32_t statusAddr = getRegU32(ctx, 5); + + if (tid == 0) // TH_SELF + { + tid = g_currentThreadId; + } + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, -1); + return; + } + + ee_thread_status_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); + if (!status) + { + setReturnS32(ctx, -1); + return; + } + + std::lock_guard lock(info->m); + status->status = info->status; + status->func = info->entry; + status->stack = info->stack; + status->stack_size = info->stackSize; + status->gp_reg = info->gp; + status->initial_priority = info->priority; + status->current_priority = info->currentPriority; + status->attr = info->attr; + status->option = info->option; + status->waitType = info->waitType; + status->waitId = info->waitId; + status->wakeupCount = info->wakeupCount; setReturnS32(ctx, 0); } void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; - if (logCount < 16) + auto info = ensureCurrentThreadInfo(ctx); + if (!info) { - std::cout << "[SleepThread] tid=" << g_currentThreadId << std::endl; - ++logCount; + setReturnS32(ctx, KE_UNKNOWN_THID); + return; } - setReturnS32(ctx, 0); + + throwIfTerminated(info); + + int ret = 0; + std::unique_lock lock(info->m); + + if (info->wakeupCount > 0) + { + info->wakeupCount--; + info->status = THS_RUN; + info->waitType = TSW_NONE; + info->waitId = 0; + ret = 0; + } + else + { + info->status = THS_WAIT; + info->waitType = TSW_SLEEP; + info->waitId = 0; + info->forceRelease = false; + + info->cv.wait(lock, [&]() + { return info->wakeupCount > 0 || info->forceRelease.load() || info->terminated.load(); }); + + if (info->terminated.load()) + { + throw ThreadExitException(); + } + + info->status = THS_RUN; + info->waitType = TSW_NONE; + info->waitId = 0; + + if (info->forceRelease.load()) + { + info->forceRelease = false; + ret = KE_RELEASE_WAIT; + } + else + { + if (info->wakeupCount > 0) + info->wakeupCount--; + ret = 0; + } + } + + lock.unlock(); + waitWhileSuspended(info); + setReturnS32(ctx, ret); } void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; int tid = static_cast(getRegU32(ctx, 4)); - if (logCount < 32) + if (tid == 0) { - std::cout << "[WakeupThread] tid=" << tid << std::endl; - ++logCount; + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + { + std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, KE_DORMANT); + return; + } + if (info->status == THS_WAIT && info->waitType == TSW_SLEEP) + { + if (info->suspendCount > 0) + { + info->status = THS_SUSPEND; + } + else + { + info->status = THS_READY; + } + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount++; + info->cv.notify_one(); + } + else + { + info->wakeupCount++; + } } setReturnS32(ctx, 0); } void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; - int tid = static_cast(getRegU32(ctx, 4)); - if (logCount < 32) - { - std::cout << "[iWakeupThread] tid=" << tid << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); + WakeupThread(rdram, ctx, runtime); } void CancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; - if (logCount < 32) + int tid = static_cast(getRegU32(ctx, 4)); + if (tid == 0) + tid = g_currentThreadId; + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (!info) { - std::cout << "[CancelWakeupThread]" << std::endl; - ++logCount; + setReturnS32(ctx, -1); + return; } - setReturnS32(ctx, 0); + + int previous = 0; + { + std::lock_guard lock(info->m); + previous = info->wakeupCount; + info->wakeupCount = 0; + } + setReturnS32(ctx, previous); } void iCancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - static int logCount = 0; - if (logCount < 32) + int tid = static_cast(getRegU32(ctx, 4)); + if (tid == 0) { - std::cout << "[iCancelWakeupThread]" << std::endl; - ++logCount; + setReturnS32(ctx, KE_ILLEGAL_THID); + return; } - setReturnS32(ctx, 0); + + auto info = lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + int previous = 0; + { + std::lock_guard lock(info->m); + previous = info->wakeupCount; + info->wakeupCount = 0; + } + setReturnS32(ctx, previous); } void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int tid = static_cast(getRegU32(ctx, 4)); int newPrio = static_cast(getRegU32(ctx, 5)); - auto it = g_threads.find(tid); - if (it != g_threads.end()) + + if (tid == 0) + tid = g_currentThreadId; + + auto info = (tid == g_currentThreadId) ? ensureCurrentThreadInfo(ctx) : lookupThreadInfo(tid); + if (info) { - it->second.priority = newPrio; + int oldPrio = info->currentPriority; + info->currentPriority = newPrio; + setReturnS32(ctx, oldPrio); // Return old priority? + } + else + { + setReturnS32(ctx, -1); } - setReturnS32(ctx, 0); } void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -418,12 +2558,75 @@ namespace ps2_syscalls void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + int tid = static_cast(getRegU32(ctx, 4)); + if (tid == 0) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + + auto info = lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + bool wasWaiting = false; + int waitType = 0; + int waitId = 0; + + { + std::lock_guard lock(info->m); + if (info->status == THS_WAIT) + { + wasWaiting = true; + waitType = info->waitType; + waitId = info->waitId; + info->forceRelease = true; + info->waitType = TSW_NONE; + info->waitId = 0; + if (info->suspendCount > 0) + { + info->status = THS_SUSPEND; + } + else + { + info->status = THS_READY; + } + } + } + + if (!wasWaiting) + { + setReturnS32(ctx, KE_NOT_WAIT); + return; + } + + info->cv.notify_all(); + + if (waitType == TSW_SEMA) + { + auto sema = lookupSemaInfo(waitId); + if (sema) + { + sema->cv.notify_all(); + } + } + else if (waitType == TSW_EVENT) + { + auto eventFlag = lookupEventFlagInfo(waitId); + if (eventFlag) + { + eventFlag->cv.notify_all(); + } + } setReturnS32(ctx, 0); } void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + ReleaseWaitThread(rdram, ctx, runtime); } void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -432,26 +2635,39 @@ namespace ps2_syscalls const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); int init = 0; int max = 1; + uint32_t attr = 0; + uint32_t option = 0; + if (param) { // sceSemaParam layout commonly: attr(0), option(1), initCount(2), maxCount(3) + attr = param[0]; + option = param[1]; init = static_cast(param[2]); max = static_cast(param[3]); } if (max <= 0) { - max = 1; // avoid dead semaphores, but maybe not good ideia + max = 1; } if (init > max) { init = max; } - int id = g_nextSemaId++; + int id = 0; auto info = std::make_shared(); info->count = init; info->maxCount = max; - g_semas.emplace(id, info); + info->initCount = init; + info->attr = attr; + info->option = option; + + { + std::lock_guard lock(g_sema_map_mutex); + id = g_nextSemaId++; + g_semas.emplace(id, info); + } std::cout << "[CreateSema] id=" << id << " init=" << init << " max=" << max << std::endl; setReturnS32(ctx, id); } @@ -459,6 +2675,7 @@ namespace ps2_syscalls void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); + std::lock_guard lock(g_sema_map_mutex); g_semas.erase(sid); setReturnS32(ctx, 0); } @@ -466,10 +2683,9 @@ namespace ps2_syscalls void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); - auto it = g_semas.find(sid); - if (it != g_semas.end()) + auto sema = lookupSemaInfo(sid); + if (sema) { - auto sema = it->second; std::lock_guard lock(sema->m); if (sema->count < sema->maxCount) { @@ -488,52 +2704,84 @@ namespace ps2_syscalls void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); - auto it = g_semas.find(sid); - if (it != g_semas.end()) + auto sema = lookupSemaInfo(sid); + if (!sema) { - auto sema = it->second; - std::unique_lock lock(sema->m); - static int globalLog = 0; - if (globalLog < 5) + setReturnS32(ctx, KE_UNKNOWN_SEMID); + return; + } + + auto info = ensureCurrentThreadInfo(ctx); + throwIfTerminated(info); + std::unique_lock lock(sema->m); + int ret = 0; + + if (sema->count == 0) + { + if (info) { - std::cout << "[WaitSema] sid=" << sid << " count=" << sema->count << std::endl; - ++globalLog; + std::lock_guard tLock(info->m); + info->status = THS_WAIT; + info->waitType = TSW_SEMA; + info->waitId = sid; + info->forceRelease = false; } - if (sema->count == 0) + + sema->waiters++; + sema->cv.wait(lock, [&]() + { + bool forced = info ? info->forceRelease.load() : false; + bool terminated = info ? info->terminated.load() : false; + return sema->count > 0 || forced || terminated; }); + sema->waiters--; + + if (info) { - static thread_local int logCount = 0; - if (logCount < 3) + std::lock_guard tLock(info->m); + info->status = THS_RUN; + info->waitType = TSW_NONE; + info->waitId = 0; + if (info->forceRelease) { - std::cout << "[WaitSema] sid=" << sid << " blocking until signaled" << std::endl; - ++logCount; + info->forceRelease = false; + ret = KE_RELEASE_WAIT; } - sema->cv.wait(lock, [&]() - { return sema->count > 0; }); } - if (sema->count > 0) + + if (info && info->terminated.load()) { - sema->count--; + throw ThreadExitException(); } } - setReturnS32(ctx, 0); + + if (ret == 0 && sema->count > 0) + { + sema->count--; + } + lock.unlock(); + waitWhileSuspended(info); + setReturnS32(ctx, ret); } void PollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { int sid = static_cast(getRegU32(ctx, 4)); - auto it = g_semas.find(sid); - if (it != g_semas.end()) + auto sema = lookupSemaInfo(sid); + if (!sema) { - auto sema = it->second; - std::lock_guard lock(sema->m); - if (sema->count > 0) - { - sema->count--; - setReturnS32(ctx, 0); - return; - } + setReturnS32(ctx, KE_UNKNOWN_SEMID); + return; } - setReturnS32(ctx, 0); + + std::lock_guard lock(sema->m); + if (sema->count > 0) + { + sema->count--; + setReturnS32(ctx, KE_OK); + return; + } + + setReturnS32(ctx, KE_SEMA_ZERO); } void iPollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -543,6 +2791,30 @@ namespace ps2_syscalls void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + int sid = static_cast(getRegU32(ctx, 4)); + uint32_t statusAddr = getRegU32(ctx, 5); + + auto sema = lookupSemaInfo(sid); + if (!sema) + { + setReturnS32(ctx, -1); + return; + } + + ee_sema_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); + if (!status) + { + setReturnS32(ctx, -1); + return; + } + + std::lock_guard lock(sema->m); + status->count = sema->count; + status->max_count = sema->maxCount; + status->init_count = sema->initCount; + status->wait_threads = sema->waiters; + status->attr = sema->attr; + status->option = sema->option; setReturnS32(ctx, 0); } @@ -551,87 +2823,390 @@ namespace ps2_syscalls ReferSemaStatus(rdram, ctx, runtime); } + constexpr uint32_t WEF_OR = 1; + constexpr uint32_t WEF_CLEAR = 0x10; + constexpr uint32_t WEF_CLEAR_ALL = 0x20; + constexpr uint32_t WEF_MODE_MASK = WEF_OR | WEF_CLEAR | WEF_CLEAR_ALL; + constexpr uint32_t EA_MULTI = 0x2; + void CreateEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 + const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); + + auto info = std::make_shared(); + if (param) + { + info->attr = param[0]; + info->option = param[1]; + info->initBits = param[2]; + info->bits = info->initBits; + } + + int id = 0; + { + std::lock_guard mapLock(g_event_flag_map_mutex); + id = g_nextEventFlagId++; + g_eventFlags[id] = info; + } + setReturnS32(ctx, id); } void DeleteEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + int eid = static_cast(getRegU32(ctx, 4)); + std::shared_ptr info; + { + std::lock_guard mapLock(g_event_flag_map_mutex); + auto it = g_eventFlags.find(eid); + if (it == g_eventFlags.end()) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + info = it->second; + g_eventFlags.erase(it); + } + + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + + { + std::lock_guard lock(info->m); + info->deleted = true; + } + info->cv.notify_all(); + setReturnS32(ctx, 0); } void SetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + int eid = static_cast(getRegU32(ctx, 4)); + uint32_t bits = getRegU32(ctx, 5); + auto info = lookupEventFlagInfo(eid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + + if (bits == 0) + { + setReturnS32(ctx, KE_OK); + return; + } + + { + std::lock_guard lock(info->m); + info->bits |= bits; + } + info->cv.notify_all(); + setReturnS32(ctx, 0); } void iSetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + SetEventFlag(rdram, ctx, runtime); } void ClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + int eid = static_cast(getRegU32(ctx, 4)); + uint32_t bits = getRegU32(ctx, 5); + auto info = lookupEventFlagInfo(eid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + + { + std::lock_guard lock(info->m); + info->bits &= bits; + } + info->cv.notify_all(); + setReturnS32(ctx, KE_OK); } void iClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + ClearEventFlag(rdram, ctx, runtime); } void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + int eid = static_cast(getRegU32(ctx, 4)); + uint32_t waitBits = getRegU32(ctx, 5); + uint32_t mode = getRegU32(ctx, 6); + uint32_t resBitsAddr = getRegU32(ctx, 7); + + if ((mode & ~WEF_MODE_MASK) != 0) + { + setReturnS32(ctx, KE_ILLEGAL_MODE); + return; + } + + if (waitBits == 0) + { + setReturnS32(ctx, KE_EVF_ILPAT); + return; + } + + auto info = lookupEventFlagInfo(eid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + + uint32_t *resBitsPtr = resBitsAddr ? reinterpret_cast(getMemPtr(rdram, resBitsAddr)) : nullptr; + + std::unique_lock lock(info->m); + if ((info->attr & EA_MULTI) == 0 && info->waiters > 0) + { + setReturnS32(ctx, KE_EVF_MULTI); + return; + } + + auto tInfo = ensureCurrentThreadInfo(ctx); + throwIfTerminated(tInfo); + int ret = KE_OK; + + auto satisfied = [&]() + { + if (tInfo && tInfo->forceRelease.load()) + return true; + if (tInfo && tInfo->terminated.load()) + return true; + if (info->deleted) + { + return true; + } + if (mode & WEF_OR) + { + return (info->bits & waitBits) != 0; + } + return (info->bits & waitBits) == waitBits; + }; + + if (!satisfied()) + { + if (tInfo) + { + std::lock_guard tLock(tInfo->m); + tInfo->status = THS_WAIT; + tInfo->waitType = TSW_EVENT; + tInfo->waitId = eid; + tInfo->forceRelease = false; + } + + info->waiters++; + info->cv.wait(lock, satisfied); + info->waiters--; + + if (tInfo) + { + std::lock_guard tLock(tInfo->m); + tInfo->status = THS_RUN; + tInfo->waitType = TSW_NONE; + tInfo->waitId = 0; + if (tInfo->forceRelease) + { + tInfo->forceRelease = false; + ret = KE_RELEASE_WAIT; + } + } + + if (tInfo && tInfo->terminated.load()) + { + throw ThreadExitException(); + } + } + + if (ret == KE_OK && info->deleted) + { + ret = KE_WAIT_DELETE; + } + + if (ret == KE_OK && resBitsPtr) + { + *resBitsPtr = info->bits; + } + + if (ret == KE_OK && (mode & (WEF_CLEAR | WEF_CLEAR_ALL))) + { + info->bits = 0; + } + + lock.unlock(); + waitWhileSuspended(tInfo); + setReturnS32(ctx, ret); } void PollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + int eid = static_cast(getRegU32(ctx, 4)); + uint32_t waitBits = getRegU32(ctx, 5); + uint32_t mode = getRegU32(ctx, 6); + uint32_t resBitsAddr = getRegU32(ctx, 7); + + if ((mode & ~WEF_MODE_MASK) != 0) + { + setReturnS32(ctx, KE_ILLEGAL_MODE); + return; + } + + if (waitBits == 0) + { + setReturnS32(ctx, KE_EVF_ILPAT); + return; + } + + auto info = lookupEventFlagInfo(eid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + + uint32_t *resBitsPtr = resBitsAddr ? reinterpret_cast(getMemPtr(rdram, resBitsAddr)) : nullptr; + + std::lock_guard lock(info->m); + if ((info->attr & EA_MULTI) == 0 && info->waiters > 0) + { + setReturnS32(ctx, KE_EVF_MULTI); + return; + } + + bool ok = false; + if (mode & WEF_OR) + { + ok = (info->bits & waitBits) != 0; + } + else + { + ok = (info->bits & waitBits) == waitBits; + } + + if (!ok) + { + setReturnS32(ctx, KE_EVF_COND); + return; + } + + if (resBitsPtr) + { + *resBitsPtr = info->bits; + } + + if (mode & (WEF_CLEAR | WEF_CLEAR_ALL)) + { + info->bits = 0; + } + + setReturnS32(ctx, KE_OK); } void iPollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + PollEventFlag(rdram, ctx, runtime); } void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + int eid = static_cast(getRegU32(ctx, 4)); + uint32_t infoAddr = getRegU32(ctx, 5); + + struct Ps2EventFlagInfo + { + uint32_t attr; + uint32_t option; + uint32_t initBits; + uint32_t currBits; + int32_t numThreads; + int32_t reserved1; + int32_t reserved2; + }; + + auto info = lookupEventFlagInfo(eid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_EVFID); + return; + } + + Ps2EventFlagInfo *out = infoAddr ? reinterpret_cast(getMemPtr(rdram, infoAddr)) : nullptr; + if (!out) + { + setReturnS32(ctx, -1); + return; + } + + std::lock_guard lock(info->m); + out->attr = info->attr; + out->option = info->option; + out->initBits = info->initBits; + out->currBits = info->bits; + out->numThreads = info->waiters; + out->reserved1 = 0; + out->reserved2 = 0; + setReturnS32(ctx, 0); } void iReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO + ReferEventFlagStatus(rdram, ctx, runtime); } - // According to GPT the real PS2 uses a timer interrupt to invoke a callback. For now, fire the callback immediately void SetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t usec = getRegU32(ctx, 4); + uint16_t ticks = static_cast(getRegU32(ctx, 4) & 0xFFFFu); uint32_t handler = getRegU32(ctx, 5); uint32_t arg = getRegU32(ctx, 6); static int logCount = 0; if (logCount < 5) { - std::cout << "[SetAlarm] usec=" << usec + std::cout << "[SetAlarm] ticks=" << ticks << " handler=0x" << std::hex << handler << " arg=0x" << arg << std::dec << std::endl; ++logCount; } - // If the handler looks like a semaphore id, just kick it now. - if (arg) + if (!runtime || !handler || !runtime->hasFunction(handler)) { - R5900Context localCtx = *ctx; - R5900Context *ctxPtr = &localCtx; - SET_GPR_U32(ctxPtr, 4, arg); - SignalSema(rdram, ctxPtr, runtime); + setReturnS32(ctx, KE_ERROR); + return; } - setReturnS32(ctx, 0); + auto info = std::make_shared(); + info->ticks = ticks; + info->handler = handler; + info->commonArg = arg; + info->gp = getRegU32(ctx, 28); + info->sp = getRegU32(ctx, 29); + info->rdram = rdram; + info->runtime = runtime; + info->dueAt = std::chrono::steady_clock::now() + alarmTicksToDuration(ticks); + + int alarmId = 0; + { + std::lock_guard lock(g_alarm_mutex); + alarmId = g_nextAlarmId++; + if (g_nextAlarmId <= 0) + { + g_nextAlarmId = 1; + } + info->id = alarmId; + g_alarms[alarmId] = info; + } + + ensureAlarmWorkerRunning(); + g_alarm_cv.notify_all(); + setReturnS32(ctx, alarmId); } void iSetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -641,7 +3216,27 @@ namespace ps2_syscalls void CancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + int alarmId = static_cast(getRegU32(ctx, 4)); + if (alarmId <= 0) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + bool removed = false; + { + std::lock_guard lock(g_alarm_mutex); + removed = g_alarms.erase(alarmId) != 0; + } + + if (removed) + { + g_alarm_cv.notify_all(); + setReturnS32(ctx, KE_OK); + return; + } + + setReturnS32(ctx, KE_ERROR); } void iCancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -659,6 +3254,92 @@ namespace ps2_syscalls setReturnS32(ctx, 0); } + void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + IrqHandlerInfo info{}; + info.cause = getRegU32(ctx, 4); + info.handler = getRegU32(ctx, 5); + info.arg = getRegU32(ctx, 6); + info.enabled = true; + + const int handlerId = g_nextIntcHandlerId++; + g_intcHandlers[handlerId] = info; + setReturnS32(ctx, handlerId); + } + + void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (handlerId > 0) + { + g_intcHandlers.erase(handlerId); + } + setReturnS32(ctx, 0); + } + + void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + IrqHandlerInfo info{}; + info.cause = getRegU32(ctx, 4); + info.handler = getRegU32(ctx, 5); + info.arg = getRegU32(ctx, 6); + info.enabled = true; + + const int handlerId = g_nextDmacHandlerId++; + g_dmacHandlers[handlerId] = info; + setReturnS32(ctx, handlerId); + } + + void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (handlerId > 0) + { + g_dmacHandlers.erase(handlerId); + } + setReturnS32(ctx, 0); + } + + void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) + { + it->second.enabled = true; + } + setReturnS32(ctx, 0); + } + + void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) + { + it->second.enabled = false; + } + setReturnS32(ctx, 0); + } + + void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) + { + it->second.enabled = true; + } + setReturnS32(ctx, 0); + } + + void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) + { + it->second.enabled = false; + } + setReturnS32(ctx, 0); + } + void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { setReturnS32(ctx, 0); @@ -671,25 +3352,89 @@ namespace ps2_syscalls void SifStopModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + const int32_t moduleId = static_cast(getRegU32(ctx, 4)); // $a0 + const uint32_t resultAddr = getRegU32(ctx, 7); // $a3 (int* result, optional) + + uint32_t refsLeft = 0; + const bool knownModule = trackSifModuleStop(moduleId, &refsLeft); + const int32_t ret = knownModule ? 0 : -1; + + if (resultAddr != 0) + { + int32_t *hostResult = reinterpret_cast(getMemPtr(rdram, resultAddr)); + if (hostResult) + { + *hostResult = knownModule ? 0 : -1; + } + } + + if (knownModule) + { + std::string modulePath; + { + std::lock_guard lock(g_sif_module_mutex); + auto it = g_sif_modules_by_id.find(moduleId); + if (it != g_sif_modules_by_id.end()) + { + modulePath = it->second.path; + } + } + logSifModuleAction("stop", moduleId, modulePath, refsLeft); + } + + setReturnS32(ctx, ret); } void SifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t pathAddr = getRegU32(ctx, 4); - const char *path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - static int logCount = 0; - if (logCount < 3) + const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + const std::string modulePath = readGuestCStringBounded(rdram, pathAddr, kMaxSifModulePathBytes); + if (modulePath.empty()) { - std::cout << "[SifLoadModule] path=" << (path ? path : "") << std::endl; - ++logCount; + setReturnS32(ctx, -1); + return; } - // Return a fake module id > 0 to indicate success. - setReturnS32(ctx, 1); + + const int32_t moduleId = trackSifModuleLoad(modulePath); + if (moduleId <= 0) + { + setReturnS32(ctx, -1); + return; + } + + uint32_t refs = 0; + { + std::lock_guard lock(g_sif_module_mutex); + auto it = g_sif_modules_by_id.find(moduleId); + if (it != g_sif_modules_by_id.end()) + { + refs = it->second.refCount; + } + } + logSifModuleAction("load", moduleId, modulePath, refs); + + setReturnS32(ctx, moduleId); } void SifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + std::lock_guard lock(g_rpc_mutex); + if (!g_rpc_initialized) + { + g_rpc_servers.clear(); + g_rpc_clients.clear(); + g_rpc_next_id = 1; + g_rpc_packet_index = 0; + g_rpc_server_index = 0; + g_rpc_active_queue = 0; + { + std::lock_guard dtxLock(g_dtx_rpc_mutex); + g_dtx_remote_by_id.clear(); + g_dtx_next_urpc_obj = kDtxUrpcObjBase; + } + g_rpc_initialized = true; + std::cout << "[SifInitRpc] Initialized" << std::endl; + } setReturnS32(ctx, 0); } @@ -699,26 +3444,66 @@ namespace ps2_syscalls uint32_t rpcId = getRegU32(ctx, 5); uint32_t mode = getRegU32(ctx, 6); - uint32_t *p = reinterpret_cast(getMemPtr(rdram, clientPtr)); - if (p) + t_SifRpcClientData *client = reinterpret_cast(getMemPtr(rdram, clientPtr)); + + if (!client) { - // server cookie/non-null marker - p[0] = clientPtr ? clientPtr : 1; - // rpc number (typical offset 12) - p[3] = rpcId; - // mode (offset 32) - p[8] = mode; - // some callers read a word at +36 to test readiness - p[9] = 1; + setReturnS32(ctx, -1); + return; } - static int logCount = 0; - if (logCount < 5) + client->command = 0; + client->buf = 0; + client->cbuf = 0; + client->end_function = 0; + client->end_param = 0; + client->server = 0; + client->hdr.pkt_addr = 0; + client->hdr.sema_id = -1; + client->hdr.mode = mode; + + uint32_t serverPtr = 0; { - std::cout << "[SifBindRpc] client=0x" << std::hex << clientPtr - << " rpcId=0x" << rpcId - << " mode=0x" << mode << std::dec << std::endl; - ++logCount; + std::lock_guard lock(g_rpc_mutex); + client->hdr.rpc_id = g_rpc_next_id++; + auto it = g_rpc_servers.find(rpcId); + if (it != g_rpc_servers.end()) + { + serverPtr = it->second.sd_ptr; + } + g_rpc_clients[clientPtr] = {}; + g_rpc_clients[clientPtr].sid = rpcId; + } + + if (!serverPtr) + { + // Allocate a dummy server so bind loops can proceed. + serverPtr = rpcAllocServerAddr(rdram); + if (serverPtr) + { + t_SifRpcServerData *dummy = reinterpret_cast(getMemPtr(rdram, serverPtr)); + if (dummy) + { + std::memset(dummy, 0, sizeof(*dummy)); + dummy->sid = static_cast(rpcId); + } + std::lock_guard lock(g_rpc_mutex); + g_rpc_servers[rpcId] = {rpcId, serverPtr}; + } + } + + if (serverPtr) + { + t_SifRpcServerData *sd = reinterpret_cast(getMemPtr(rdram, serverPtr)); + client->server = serverPtr; + client->buf = sd ? sd->buf : 0; + client->cbuf = sd ? sd->cbuf : 0; + } + else + { + client->server = 0; + client->buf = 0; + client->cbuf = 0; } setReturnS32(ctx, 0); @@ -727,53 +3512,888 @@ namespace ps2_syscalls void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t clientPtr = getRegU32(ctx, 4); - uint32_t rpcId = getRegU32(ctx, 5); + uint32_t rpcNum = getRegU32(ctx, 5); uint32_t mode = getRegU32(ctx, 6); uint32_t sendBuf = getRegU32(ctx, 7); + uint32_t sendSize = 0; + uint32_t recvBuf = 0; + uint32_t recvSize = 0; + uint32_t endFunc = 0; + uint32_t endParam = 0; - uint32_t *p = reinterpret_cast(getMemPtr(rdram, clientPtr)); - if (p) + // EE-side calls use extended arg registers: + // a0-a3 => r4-r7, arg5-arg8 => r8-r11, arg9 => stack + 0x0. + // Keep O32 stack-layout fallback for compatibility with other call sites. + uint32_t sp = getRegU32(ctx, 29); + sendSize = getRegU32(ctx, 8); + recvBuf = getRegU32(ctx, 9); + recvSize = getRegU32(ctx, 10); + endFunc = getRegU32(ctx, 11); + (void)readStackU32(rdram, sp, 0x0, endParam); + + if (sendSize == 0 && recvBuf == 0 && recvSize == 0 && endFunc == 0) { - // Mark completion flag at +36. - p[9] = 1; + readStackU32(rdram, sp, 0x10, sendSize); + readStackU32(rdram, sp, 0x14, recvBuf); + readStackU32(rdram, sp, 0x18, recvSize); + readStackU32(rdram, sp, 0x1C, endFunc); + readStackU32(rdram, sp, 0x20, endParam); + } + + t_SifRpcClientData *client = reinterpret_cast(getMemPtr(rdram, clientPtr)); + + if (!client) + { + setReturnS32(ctx, -1); + return; + } + + client->command = rpcNum; + client->end_function = endFunc; + client->end_param = endParam; + client->hdr.mode = mode; + + { + std::lock_guard lock(g_rpc_mutex); + g_rpc_clients[clientPtr].busy = true; + g_rpc_clients[clientPtr].last_rpc = rpcNum; + uint32_t sid = g_rpc_clients[clientPtr].sid; + if (sid) + { + auto it = g_rpc_servers.find(sid); + if (it != g_rpc_servers.end()) + { + uint32_t mappedServer = it->second.sd_ptr; + if (mappedServer && client->server != mappedServer) + { + client->server = mappedServer; + } + } + } + } + + uint32_t sid = 0; + { + std::lock_guard lock(g_rpc_mutex); + auto it = g_rpc_clients.find(clientPtr); + if (it != g_rpc_clients.end()) + { + sid = it->second.sid; + } + } + + uint32_t serverPtr = client->server; + t_SifRpcServerData *sd = serverPtr ? reinterpret_cast(getMemPtr(rdram, serverPtr)) : nullptr; + + if (sd) + { + sd->client = clientPtr; + sd->pkt_addr = client->hdr.pkt_addr; + sd->rpc_number = rpcNum; + sd->size = static_cast(sendSize); + sd->recvbuf = recvBuf; + sd->rsize = static_cast(recvSize); + sd->rmode = ((mode & kSifRpcModeNowait) && endFunc == 0) ? 0 : 1; + sd->rid = 0; + } + + if (sd && sd->buf && sendBuf && sendSize > 0) + { + rpcCopyToRdram(rdram, sd->buf, sendBuf, sendSize); + } + + uint32_t resultPtr = 0; + bool handled = false; + + auto readRpcU32 = [&](uint32_t addr, uint32_t &out) -> bool + { + if (!addr) + { + return false; + } + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(&out, ptr, sizeof(out)); + return true; + }; + + auto writeRpcU32 = [&](uint32_t addr, uint32_t value) -> bool + { + if (!addr) + { + return false; + } + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, &value, sizeof(value)); + return true; + }; + + const bool isDtxUrpc = (sid == kDtxRpcSid) && (rpcNum >= 0x400u) && (rpcNum < 0x500u); + uint32_t dtxUrpcCommand = isDtxUrpc ? (rpcNum & 0xFFu) : 0u; + uint32_t dtxUrpcFn = 0; + uint32_t dtxUrpcObj = 0; + uint32_t dtxUrpcSend0 = 0; + bool dtxUrpcDispatchAttempted = false; + bool dtxUrpcFallbackEmulated = false; + bool dtxUrpcFallbackCreate34 = false; + bool hasUrpcHandler = false; + if (isDtxUrpc) + { + if (sendBuf && sendSize >= sizeof(uint32_t)) + { + (void)readRpcU32(sendBuf, dtxUrpcSend0); + } + if (dtxUrpcCommand < 64u) + { + (void)readRpcU32(kDtxUrpcFnTableBase + (dtxUrpcCommand * 4u), dtxUrpcFn); + (void)readRpcU32(kDtxUrpcObjTableBase + (dtxUrpcCommand * 4u), dtxUrpcObj); + } + hasUrpcHandler = (dtxUrpcCommand < 64u) && (dtxUrpcFn != 0u); + } + const bool allowServerDispatch = !isDtxUrpc || hasUrpcHandler; + + if (sd && sd->func && (sid != kDtxRpcSid || isDtxUrpc) && allowServerDispatch) + { + dtxUrpcDispatchAttempted = dtxUrpcDispatchAttempted || isDtxUrpc; + handled = rpcInvokeFunction(rdram, ctx, runtime, sd->func, rpcNum, sd->buf, sendSize, 0, &resultPtr); + if (handled && resultPtr == 0 && sd->buf) + { + resultPtr = sd->buf; + } + if (handled && resultPtr == 0 && recvBuf) + { + resultPtr = recvBuf; + } + } + + if (!handled && isDtxUrpc && sendBuf && sendSize > 0) + { + // Only dispatch through dtx_rpc_func when a URPC handler is registered in the table. + // If the slot is empty, defer to the fallback emulation below. + if (hasUrpcHandler) + { + dtxUrpcDispatchAttempted = true; + handled = rpcInvokeFunction(rdram, ctx, runtime, 0x2fabc0u, rpcNum, sendBuf, sendSize, 0, &resultPtr); + if (handled && resultPtr == 0) + { + resultPtr = sendBuf; + } + } + } + + if (!handled && sid == kDtxRpcSid) + { + if (rpcNum == 2 && recvBuf && recvSize >= sizeof(uint32_t)) + { + uint32_t dtxId = 0; + if (sendBuf && sendSize >= sizeof(uint32_t)) + { + (void)readRpcU32(sendBuf, dtxId); + } + + uint32_t remoteHandle = 0; + { + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_remote_by_id.find(dtxId); + if (it != g_dtx_remote_by_id.end()) + { + remoteHandle = it->second; + } + if (!remoteHandle) + { + remoteHandle = rpcAllocServerAddr(rdram); + if (!remoteHandle) + { + remoteHandle = rpcAllocPacketAddr(rdram); + } + if (!remoteHandle) + { + remoteHandle = kRpcServerPoolBase + ((dtxId & 0xFFu) * kRpcServerStride); + } + g_dtx_remote_by_id[dtxId] = remoteHandle; + } + } + + (void)writeRpcU32(recvBuf, remoteHandle); + if (recvSize > sizeof(uint32_t)) + { + rpcZeroRdram(rdram, recvBuf + sizeof(uint32_t), recvSize - sizeof(uint32_t)); + } + handled = true; + resultPtr = recvBuf; + } + else if (rpcNum == 3) + { + uint32_t remoteHandle = 0; + if (sendBuf && sendSize >= sizeof(uint32_t) && readRpcU32(sendBuf, remoteHandle) && remoteHandle) + { + std::lock_guard lock(g_dtx_rpc_mutex); + for (auto it = g_dtx_remote_by_id.begin(); it != g_dtx_remote_by_id.end(); ++it) + { + if (it->second == remoteHandle) + { + g_dtx_remote_by_id.erase(it); + break; + } + } + } + if (recvBuf && recvSize > 0) + { + rpcZeroRdram(rdram, recvBuf, recvSize); + } + handled = true; + resultPtr = recvBuf; + } + else if (rpcNum >= 0x400 && rpcNum < 0x500) + { + dtxUrpcFallbackEmulated = true; + const uint32_t urpcCommand = rpcNum & 0xFFu; + uint32_t outWords[4] = {1u, 0u, 0u, 0u}; + uint32_t outWordCount = 1u; + + auto readSendWord = [&](uint32_t index, uint32_t &out) -> bool + { + const uint64_t byteOffset = static_cast(index) * sizeof(uint32_t); + if (!sendBuf || sendSize < (byteOffset + sizeof(uint32_t))) + { + return false; + } + return readRpcU32(sendBuf + static_cast(byteOffset), out); + }; + + switch (urpcCommand) + { + case 32u: // SJRMT_RBF_CREATE + case 33u: // SJRMT_MEM_CREATE + case 34u: // SJRMT_UNI_CREATE + { + uint32_t arg0 = 0; + uint32_t arg1 = 0; + uint32_t arg2 = 0; + (void)readSendWord(0u, arg0); + (void)readSendWord(1u, arg1); + (void)readSendWord(2u, arg2); + + uint32_t mode = 0; + uint32_t wkAddr = 0; + uint32_t wkSize = 0; + if (urpcCommand == 34u) + { + mode = arg0; + wkAddr = arg1; + wkSize = arg2; + dtxUrpcFallbackCreate34 = true; + } + else if (urpcCommand == 33u) + { + wkAddr = arg0; + wkSize = arg1; + } + else + { + wkAddr = arg0; + wkSize = (arg1 != 0u) ? arg1 : arg2; + } + + wkSize = dtxNormalizeSjrmtCapacity(wkSize); + + std::lock_guard lock(g_dtx_rpc_mutex); + const uint32_t handle = dtxAllocUrpcHandleLocked(); + DtxSjrmtState state{}; + state.handle = handle; + state.mode = mode; + state.wkAddr = wkAddr; + state.wkSize = wkSize; + state.readPos = 0u; + state.writePos = 0u; + state.roomBytes = wkSize; + state.dataBytes = 0u; + state.uuid0 = 0x53524D54u; // "SRMT" + state.uuid1 = handle; + state.uuid2 = wkAddr; + state.uuid3 = wkSize; + g_dtx_sjrmt_by_handle[handle] = state; + + outWords[0] = handle ? handle : 1u; + outWordCount = 1u; + break; + } + case 35u: // SJRMT_DESTROY + { + uint32_t handle = 0; + (void)readSendWord(0u, handle); + std::lock_guard lock(g_dtx_rpc_mutex); + g_dtx_sjrmt_by_handle.erase(handle); + outWords[0] = 1u; + outWordCount = 1u; + break; + } + case 36u: // SJRMT_GET_UUID + { + uint32_t handle = 0; + (void)readSendWord(0u, handle); + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + outWords[0] = it->second.uuid0; + outWords[1] = it->second.uuid1; + outWords[2] = it->second.uuid2; + outWords[3] = it->second.uuid3; + } + else + { + outWords[0] = 0u; + outWords[1] = 0u; + outWords[2] = 0u; + outWords[3] = 0u; + } + outWordCount = 4u; + break; + } + case 37u: // SJRMT_RESET + { + uint32_t handle = 0; + (void)readSendWord(0u, handle); + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + const uint32_t cap = (it->second.wkSize == 0u) ? 0x4000u : it->second.wkSize; + it->second.readPos = 0u; + it->second.writePos = 0u; + it->second.roomBytes = cap; + it->second.dataBytes = 0u; + } + outWords[0] = 1u; + outWordCount = 1u; + break; + } + case 38u: // SJRMT_GET_CHUNK + { + uint32_t handle = 0; + uint32_t streamId = 0; + uint32_t nbyte = 0; + (void)readSendWord(0u, handle); + (void)readSendWord(1u, streamId); + (void)readSendWord(2u, nbyte); + + uint32_t ptr = 0u; + uint32_t len = 0u; + + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + DtxSjrmtState &state = it->second; + const uint32_t cap = (state.wkSize == 0u) ? 0x4000u : state.wkSize; + + if (streamId == 0u) + { + len = std::min(nbyte, state.roomBytes); + ptr = state.wkAddr + (cap ? (state.writePos % cap) : 0u); + if (cap != 0u) + { + state.writePos = (state.writePos + len) % cap; + } + state.roomBytes -= len; + } + else if (streamId == 1u) + { + len = std::min(nbyte, state.dataBytes); + ptr = state.wkAddr + (cap ? (state.readPos % cap) : 0u); + if (cap != 0u) + { + state.readPos = (state.readPos + len) % cap; + } + state.dataBytes -= len; + } + } + + outWords[0] = ptr; + outWords[1] = len; + outWordCount = 2u; + break; + } + case 39u: // SJRMT_UNGET_CHUNK + { + uint32_t handle = 0; + uint32_t streamId = 0; + uint32_t len = 0; + (void)readSendWord(0u, handle); + (void)readSendWord(1u, streamId); + (void)readSendWord(3u, len); + + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + DtxSjrmtState &state = it->second; + const uint32_t cap = (state.wkSize == 0u) ? 0x4000u : state.wkSize; + if (streamId == 0u) + { + const uint32_t delta = (cap == 0u) ? 0u : (len % cap); + if (cap != 0u) + { + state.writePos = (state.writePos + cap - delta) % cap; + } + state.roomBytes = std::min(cap, state.roomBytes + len); + } + else if (streamId == 1u) + { + const uint32_t delta = (cap == 0u) ? 0u : (len % cap); + if (cap != 0u) + { + state.readPos = (state.readPos + cap - delta) % cap; + } + state.dataBytes = std::min(cap, state.dataBytes + len); + } + } + + outWords[0] = 1u; + outWordCount = 1u; + break; + } + case 40u: // SJRMT_PUT_CHUNK + { + uint32_t handle = 0; + uint32_t streamId = 0; + uint32_t len = 0; + (void)readSendWord(0u, handle); + (void)readSendWord(1u, streamId); + (void)readSendWord(3u, len); + + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + DtxSjrmtState &state = it->second; + const uint32_t cap = (state.wkSize == 0u) ? 0x4000u : state.wkSize; + if (streamId == 0u) + { + state.roomBytes = std::min(cap, state.roomBytes + len); + } + else if (streamId == 1u) + { + state.dataBytes = std::min(cap, state.dataBytes + len); + } + } + + outWords[0] = 1u; + outWordCount = 1u; + break; + } + case 41u: // SJRMT_GET_NUM_DATA + { + uint32_t handle = 0; + uint32_t streamId = 0; + (void)readSendWord(0u, handle); + (void)readSendWord(1u, streamId); + + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + outWords[0] = (streamId == 0u) ? it->second.roomBytes : it->second.dataBytes; + } + else + { + outWords[0] = 0u; + } + outWordCount = 1u; + break; + } + case 42u: // SJRMT_IS_GET_CHUNK + { + uint32_t handle = 0; + uint32_t streamId = 0; + uint32_t nbyte = 0; + (void)readSendWord(0u, handle); + (void)readSendWord(1u, streamId); + (void)readSendWord(2u, nbyte); + + uint32_t available = 0u; + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(handle); + if (it != g_dtx_sjrmt_by_handle.end()) + { + available = (streamId == 0u) ? it->second.roomBytes : it->second.dataBytes; + } + outWords[0] = (available >= nbyte) ? 1u : 0u; + outWords[1] = available; + outWordCount = 2u; + break; + } + case 43u: // SJRMT_INIT + case 44u: // SJRMT_FINISH + { + outWords[0] = 1u; + outWordCount = 1u; + break; + } + default: + { + uint32_t urpcRet = 1u; + if (sendBuf && sendSize >= sizeof(uint32_t)) + { + (void)readRpcU32(sendBuf, urpcRet); + } + if (urpcCommand == 0u) + { + std::lock_guard lock(g_dtx_rpc_mutex); + urpcRet = dtxAllocUrpcHandleLocked(); + } + if (urpcRet == 0u) + { + urpcRet = 1u; + } + outWords[0] = urpcRet; + outWordCount = 1u; + break; + } + } + + if (recvBuf && recvSize > 0u) + { + const uint32_t recvWordCapacity = static_cast(recvSize / sizeof(uint32_t)); + const uint32_t wordsToWrite = std::min(outWordCount, recvWordCapacity); + for (uint32_t i = 0; i < wordsToWrite; ++i) + { + (void)writeRpcU32(recvBuf + (i * sizeof(uint32_t)), outWords[i]); + } + + // SJRMT_IsGetChunk callers read rbuf[1] even when nout==1. + if (urpcCommand == 42u && outWordCount > 1u) + { + (void)writeRpcU32(recvBuf + sizeof(uint32_t), outWords[1]); + } + + if (recvSize > (wordsToWrite * sizeof(uint32_t))) + { + rpcZeroRdram(rdram, recvBuf + (wordsToWrite * sizeof(uint32_t)), + recvSize - (wordsToWrite * sizeof(uint32_t))); + } + } + + handled = true; + resultPtr = recvBuf; + } + } + + if (recvBuf && recvSize > 0) + { + if (handled && resultPtr) + { + rpcCopyToRdram(rdram, recvBuf, resultPtr, recvSize); + } + else if (!handled && sendBuf && sendSize > 0) + { + size_t copySize = (sendSize < recvSize) ? sendSize : recvSize; + rpcCopyToRdram(rdram, recvBuf, sendBuf, copySize); + } + else if (!handled) + { + rpcZeroRdram(rdram, recvBuf, recvSize); + } + } + + if (isDtxUrpc) + { + static int dtxUrpcLogCount = 0; + if (dtxUrpcLogCount < 64) + { + uint32_t dtxUrpcRecv0 = 0; + if (recvBuf && recvSize >= sizeof(uint32_t)) + { + (void)readRpcU32(recvBuf, dtxUrpcRecv0); + } + std::cout << "[SifCallRpc:DTX] rpcNum=0x" << std::hex << rpcNum + << " cmd=0x" << dtxUrpcCommand + << " fn=0x" << dtxUrpcFn + << " obj=0x" << dtxUrpcObj + << " send0=0x" << dtxUrpcSend0 + << " recv0=0x" << dtxUrpcRecv0 + << " resultPtr=0x" << resultPtr + << " handled=" << std::dec << (handled ? 1 : 0) + << " dispatch=" << (dtxUrpcDispatchAttempted ? 1 : 0) + << " emu=" << (dtxUrpcFallbackEmulated ? 1 : 0) + << " emu34=" << (dtxUrpcFallbackCreate34 ? 1 : 0) + << std::endl; + ++dtxUrpcLogCount; + } + } + + if (endFunc) + { + rpcInvokeFunction(rdram, ctx, runtime, endFunc, endParam, 0, 0, 0, nullptr); } static int logCount = 0; - if (logCount < 5) + if (logCount < 10) { std::cout << "[SifCallRpc] client=0x" << std::hex << clientPtr - << " rpcId=0x" << rpcId + << " sid=0x" << sid + << " rpcNum=0x" << rpcNum << " mode=0x" << mode - << " sendBuf=0x" << sendBuf << std::dec << std::endl; + << " sendBuf=0x" << sendBuf + << " recvBuf=0x" << recvBuf + << " recvSize=0x" << recvSize + << " size=" << std::dec << sendSize << std::endl; ++logCount; } + { + std::lock_guard lock(g_rpc_mutex); + g_rpc_clients[clientPtr].busy = false; + } + setReturnS32(ctx, 0); } void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + uint32_t sdPtr = getRegU32(ctx, 4); + uint32_t sid = getRegU32(ctx, 5); + uint32_t func = getRegU32(ctx, 6); + uint32_t buf = getRegU32(ctx, 7); + // stack args: cfunc, cbuf, qd... + uint32_t sp = getRegU32(ctx, 29); + uint32_t cfunc = 0; + uint32_t cbuf = 0; + uint32_t qd = 0; + readStackU32(rdram, sp, 0x10, cfunc); + readStackU32(rdram, sp, 0x14, cbuf); + readStackU32(rdram, sp, 0x18, qd); + + t_SifRpcServerData *sd = reinterpret_cast(getMemPtr(rdram, sdPtr)); + if (!sd) + { + setReturnS32(ctx, -1); + return; + } + + sd->sid = static_cast(sid); + sd->func = func; + sd->buf = buf; + sd->size = 0; + sd->cfunc = cfunc; + sd->cbuf = cbuf; + sd->size2 = 0; + sd->client = 0; + sd->pkt_addr = 0; + sd->rpc_number = 0; + sd->recvbuf = 0; + sd->rsize = 0; + sd->rmode = 0; + sd->rid = 0; + sd->base = qd; + sd->link = 0; + sd->next = 0; + + if (qd) + { + t_SifRpcDataQueue *queue = reinterpret_cast(getMemPtr(rdram, qd)); + if (queue) + { + if (!queue->link) + { + queue->link = sdPtr; + } + else + { + uint32_t curPtr = queue->link; + for (int guard = 0; guard < 1024 && curPtr; ++guard) + { + t_SifRpcServerData *cur = reinterpret_cast(getMemPtr(rdram, curPtr)); + if (!cur) + break; + if (!cur->link) + { + cur->link = sdPtr; + break; + } + if (cur->link == sdPtr) + break; + curPtr = cur->link; + } + } + } + } + + { + std::lock_guard lock(g_rpc_mutex); + g_rpc_servers[sid] = {sid, sdPtr}; + for (auto &entry : g_rpc_clients) + { + if (entry.second.sid == sid) + { + t_SifRpcClientData *cd = reinterpret_cast(getMemPtr(rdram, entry.first)); + if (cd) + { + cd->server = sdPtr; + cd->buf = sd->buf; + cd->cbuf = sd->cbuf; + } + } + } + } + + std::cout << "[SifRegisterRpc] sid=0x" << std::hex << sid << " sd=0x" << sdPtr << std::dec << std::endl; setReturnS32(ctx, 0); } void SifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 1); + uint32_t clientPtr = getRegU32(ctx, 4); + std::lock_guard lock(g_rpc_mutex); + auto it = g_rpc_clients.find(clientPtr); + if (it == g_rpc_clients.end()) + { + setReturnS32(ctx, 0); + return; + } + setReturnS32(ctx, it->second.busy ? 1 : 0); } void SifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + uint32_t qdPtr = getRegU32(ctx, 4); + int threadId = static_cast(getRegU32(ctx, 5)); + + t_SifRpcDataQueue *qd = reinterpret_cast(getMemPtr(rdram, qdPtr)); + if (!qd) + { + setReturnS32(ctx, -1); + return; + } + + qd->thread_id = threadId; + qd->active = 0; + qd->link = 0; + qd->start = 0; + qd->end = 0; + qd->next = 0; + + { + std::lock_guard lock(g_rpc_mutex); + if (!g_rpc_active_queue) + { + g_rpc_active_queue = qdPtr; + } + else + { + uint32_t curPtr = g_rpc_active_queue; + for (int guard = 0; guard < 1024 && curPtr; ++guard) + { + if (curPtr == qdPtr) + break; + t_SifRpcDataQueue *cur = reinterpret_cast(getMemPtr(rdram, curPtr)); + if (!cur) + break; + if (!cur->next) + { + cur->next = qdPtr; + break; + } + curPtr = cur->next; + } + } + } + setReturnS32(ctx, 0); } void SifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + uint32_t qdPtr = getRegU32(ctx, 4); + if (!qdPtr) + { + setReturnU32(ctx, 0); + return; + } + + std::lock_guard lock(g_rpc_mutex); + if (!g_rpc_active_queue) + { + setReturnU32(ctx, 0); + return; + } + + if (g_rpc_active_queue == qdPtr) + { + t_SifRpcDataQueue *qd = reinterpret_cast(getMemPtr(rdram, qdPtr)); + g_rpc_active_queue = qd ? qd->next : 0; + setReturnU32(ctx, qdPtr); + return; + } + + uint32_t curPtr = g_rpc_active_queue; + for (int guard = 0; guard < 1024 && curPtr; ++guard) + { + t_SifRpcDataQueue *cur = reinterpret_cast(getMemPtr(rdram, curPtr)); + if (!cur) + break; + if (cur->next == qdPtr) + { + t_SifRpcDataQueue *rem = reinterpret_cast(getMemPtr(rdram, qdPtr)); + cur->next = rem ? rem->next : 0; + setReturnU32(ctx, qdPtr); + return; + } + curPtr = cur->next; + } + + setReturnU32(ctx, 0); } void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + uint32_t sdPtr = getRegU32(ctx, 4); + uint32_t qdPtr = getRegU32(ctx, 5); + + t_SifRpcDataQueue *qd = reinterpret_cast(getMemPtr(rdram, qdPtr)); + if (!qd || !sdPtr) + { + setReturnU32(ctx, 0); + return; + } + + if (qd->link == sdPtr) + { + t_SifRpcServerData *sd = reinterpret_cast(getMemPtr(rdram, sdPtr)); + qd->link = sd ? sd->link : 0; + if (sd) + sd->link = 0; + setReturnU32(ctx, sdPtr); + return; + } + + uint32_t curPtr = qd->link; + for (int guard = 0; guard < 1024 && curPtr; ++guard) + { + t_SifRpcServerData *cur = reinterpret_cast(getMemPtr(rdram, curPtr)); + if (!cur) + break; + if (cur->link == sdPtr) + { + t_SifRpcServerData *sd = reinterpret_cast(getMemPtr(rdram, sdPtr)); + cur->link = sd ? sd->link : 0; + if (sd) + sd->link = 0; + setReturnU32(ctx, sdPtr); + return; + } + curPtr = cur->link; + } + + setReturnU32(ctx, 0); } void sceSifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -783,16 +4403,34 @@ namespace ps2_syscalls void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + uint32_t cid = getRegU32(ctx, 4); + uint32_t packetAddr = getRegU32(ctx, 5); + uint32_t packetSize = getRegU32(ctx, 6); + uint32_t srcExtra = getRegU32(ctx, 7); + + uint32_t sp = getRegU32(ctx, 29); + uint32_t destExtra = 0; + uint32_t sizeExtra = 0; + readStackU32(rdram, sp, 0x10, destExtra); + readStackU32(rdram, sp, 0x14, sizeExtra); + + if (sizeExtra > 0 && srcExtra && destExtra) + { + rpcCopyToRdram(rdram, destExtra, srcExtra, sizeExtra); + } + static int logCount = 0; if (logCount < 5) { - std::cout << "[sceSifSendCmd] cmd=0x" << std::hex << getRegU32(ctx, 4) - << " packet=0x" << getRegU32(ctx, 5) - << " size=0x" << getRegU32(ctx, 6) - << " dest=0x" << getRegU32(ctx, 7) << std::dec << std::endl; + std::cout << "[sceSifSendCmd] cid=0x" << std::hex << cid + << " packet=0x" << packetAddr + << " psize=0x" << packetSize + << " extra=0x" << destExtra << std::dec << std::endl; ++logCount; } - setReturnS32(ctx, 0); + + // Return non-zero on success. + setReturnS32(ctx, 1); } void _sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1007,7 +4645,6 @@ namespace ps2_syscalls } else { - // maybe we dont need this check. if position fits in 32 bits if (newPos > 0xFFFFFFFFL) { std::cerr << "fioLseek warning: New position exceeds 32-bit for fd " << ps2Fd << std::endl; @@ -1022,7 +4659,6 @@ namespace ps2_syscalls void fioMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO maybe we dont need this. uint32_t pathAddr = getRegU32(ctx, 4); // $a0 // int mode = (int)getRegU32(ctx, 5); @@ -1060,7 +4696,6 @@ namespace ps2_syscalls void fioChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO maybe we dont need this as well. uint32_t pathAddr = getRegU32(ctx, 4); // $a0 const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); if (!ps2Path) @@ -1214,9 +4849,11 @@ namespace ps2_syscalls void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - // TODO return IMR value from the Gs hardware this is just a stub. - // The IMR (Interrupt Mask Register) is a 64-bit register that controls which interrupts are enabled. - uint64_t imr = 0x0000000000000000ULL; + uint64_t imr = 0; + if (runtime) + { + imr = runtime->memory().gs().imr; + } std::cout << "PS2 GsGetIMR: Returning IMR=0x" << std::hex << imr << std::dec << std::endl; @@ -1225,9 +4862,15 @@ namespace ps2_syscalls void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint64_t imr = getRegU32(ctx, 4) | ((uint64_t)getRegU32(ctx, 5) << 32); // $a0 = lower 32 bits, $a1 = upper 32 bits - std::cout << "PS2 GsPutIMR: Setting IMR=0x" << std::hex << imr << std::dec << std::endl; - // Do nothing for now. + uint64_t newImr = getRegU32(ctx, 4) | ((uint64_t)getRegU32(ctx, 5) << 32); // $a0 = lower 32 bits, $a1 = upper 32 bits + uint64_t oldImr = 0; + if (runtime) + { + oldImr = runtime->memory().gs().imr; + runtime->memory().gs().imr = newImr; + } + std::cout << "PS2 GsPutIMR: Setting IMR=0x" << std::hex << newImr << std::dec << std::endl; + setReturnU64(ctx, oldImr); } void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1253,10 +4896,14 @@ namespace ps2_syscalls uint32_t *param = reinterpret_cast(getMemPtr(rdram, paramAddr)); - // Default to English language, USA region - *param = 0x00000000; + ensureOsdConfigInitialized(); + uint32_t raw; + { + std::lock_guard lock(g_osd_mutex); + raw = g_osd_config_raw; + } - std::cout << "PS2 GetOsdConfigParam: Retrieved OSD parameters" << std::endl; + *param = raw; setReturnS32(ctx, 0); } @@ -1273,8 +4920,14 @@ namespace ps2_syscalls return; } - // TODO save user preferences - std::cout << "PS2 SetOsdConfigParam: Set OSD parameters" << std::endl; + const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); + uint32_t raw = param ? *param : 0; + raw = sanitizeOsdConfigRaw(raw); + { + std::lock_guard lock(g_osd_mutex); + g_osd_config_raw = raw; + g_osd_config_initialized = true; + } setReturnS32(ctx, 0); } @@ -1307,53 +4960,119 @@ namespace ps2_syscalls void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - pointer to ELF path + const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path + const uint32_t secNameAddr = getRegU32(ctx, 5); // $a1 - section name ("all" typically) + const uint32_t execDataAddr = getRegU32(ctx, 6); // $a2 - t_ExecData* - const char *elfPath = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + std::string secName = readGuestCStringBounded(rdram, secNameAddr, kLoadfileArgMaxBytes); + if (secName.empty()) + { + secName = "all"; + } - std::cout << "PS2 SifLoadElfPart: Would load ELF from " << elfPath << std::endl; - setReturnS32(ctx, 1); // dummy return value for success + const int32_t ret = runSifLoadElfPart(rdram, ctx, runtime, pathAddr, secName, execDataAddr); + setReturnS32(ctx, ret); + } + + void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path + const uint32_t execDataAddr = getRegU32(ctx, 5); // $a1 - t_ExecData* + const int32_t ret = runSifLoadElfPart(rdram, ctx, runtime, pathAddr, "all", execDataAddr); + setReturnS32(ctx, ret); + } + + void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + SifLoadElfPart(rdram, ctx, runtime); } void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t moduePath = getRegU32(ctx, 4); // $a0 - pointer to module path - - // Extract path - const char *modulePath = reinterpret_cast(getConstMemPtr(rdram, moduePath)); - - std::cout << "PS2 SifLoadModule: Would load module from " << moduePath << std::endl; - - setReturnS32(ctx, 1); + // Use the same tracker as SifLoadModule so both APIs return the same module IDs. + SifLoadModule(rdram, ctx, runtime); } - void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - uint32_t syscall_num = getRegU32(ctx, 3); // Syscall number usually in $v1 ($r3) for SYSCALL instr - uint32_t caller_ra = getRegU32(ctx, 31); // $ra + const uint32_t bufferAddr = getRegU32(ctx, 4); // $a0 + if (!rdram || bufferAddr == 0u) + { + setReturnS32(ctx, -1); + return; + } + + // Match buffer-based module loads to stable synthetic tags so module ID lookup remains deterministic. + const std::string moduleTag = makeSifModuleBufferTag(rdram, bufferAddr); + const int32_t moduleId = trackSifModuleLoad(moduleTag); + if (moduleId <= 0) + { + setReturnS32(ctx, -1); + return; + } + + uint32_t refs = 0; + { + std::lock_guard lock(g_sif_module_mutex); + auto it = g_sif_modules_by_id.find(moduleId); + if (it != g_sif_modules_by_id.end()) + { + refs = it->second.refCount; + } + } + logSifModuleAction("load-buffer", moduleId, moduleTag, refs); + setReturnS32(ctx, moduleId); + } + + void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId) + { + const uint32_t v0 = getRegU32(ctx, 2); + const uint32_t v1 = getRegU32(ctx, 3); + const uint32_t caller_ra = getRegU32(ctx, 31); + uint32_t syscallId = encodedSyscallId; + if (syscallId == 0u) + { + syscallId = (v0 != 0u) ? v0 : v1; + } std::cerr << "Warning: Unimplemented PS2 syscall called. PC=0x" << std::hex << ctx->pc - << ", RA=0x" << caller_ra - << ", Syscall # (from $v1)=0x" << syscall_num << std::dec << std::endl; + << ", RA=0x" << caller_ra + << ", Encoded=0x" << encodedSyscallId + << ", v0=0x" << v0 + << ", v1=0x" << v1 + << ", Chosen=0x" << syscallId + << std::dec << std::endl; std::cerr << " Args: $a0=0x" << std::hex << getRegU32(ctx, 4) - << ", $a1=0x" << getRegU32(ctx, 5) - << ", $a2=0x" << getRegU32(ctx, 6) - << ", $a3=0x" << getRegU32(ctx, 7) << std::dec << std::endl; + << ", $a1=0x" << getRegU32(ctx, 5) + << ", $a2=0x" << getRegU32(ctx, 6) + << ", $a3=0x" << getRegU32(ctx, 7) << std::dec << std::endl; // Common syscalls: // 0x04: Exit // 0x06: LoadExecPS2 // 0x07: ExecPS2 - if (syscall_num == 0x04) + if (syscallId == 0x04u) { std::cerr << " -> Syscall is Exit(), calling ExitThread stub." << std::endl; ExitThread(rdram, ctx, runtime); return; } - // Return generic error for unimplemented ones - setReturnS32(ctx, -1); // Return -ENOSYS or similar? Use -1 for simplicity. + static std::mutex s_unknownMutex; + static std::unordered_map s_unknownCounts; + { + std::lock_guard lock(s_unknownMutex); + const uint64_t count = ++s_unknownCounts[syscallId]; + if (count == 1 || (count % 5000u) == 0u) + { + std::cerr << " -> Unknown syscallId=0x" << std::hex << syscallId + << " hits=" << std::dec << count << std::endl; + } + } + + // Bootstrap default: avoid hard-failing loops that probe syscall availability. + setReturnS32(ctx, 0); } // 0x3C SetupThread: returns stack pointer (stack + stack_size) @@ -1366,21 +5085,90 @@ namespace ps2_syscalls setReturnS32(ctx, sp); } + // 0x3D SetupHeap: returns heap base/start pointer + void SetupHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t heapBase = getRegU32(ctx, 4); // $a0 + const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (optional size) + + if (runtime) + { + uint32_t heapLimit = PS2_RAM_SIZE; + if (heapSize != 0u && heapBase < PS2_RAM_SIZE) + { + const uint64_t candidateLimit = static_cast(heapBase) + static_cast(heapSize); + heapLimit = static_cast(std::min(candidateLimit, PS2_RAM_SIZE)); + } + runtime->configureGuestHeap(heapBase, heapLimit); + setReturnU32(ctx, runtime->guestHeapBase()); + return; + } + + setReturnU32(ctx, heapBase); + } + + // 0x3E EndOfHeap: commonly returns current heap end; keep it stable for now. + void EndOfHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + if (runtime) + { + setReturnU32(ctx, runtime->guestHeapEnd()); + return; + } + + setReturnU32(ctx, getRegU32(ctx, 4)); + } + // 0x5A QueryBootMode (stub): return 0 for now void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + uint32_t mode = getRegU32(ctx, 4); + ensureBootModeTable(rdram); + uint32_t addr = 0; + { + std::lock_guard lock(g_bootmode_mutex); + auto it = g_bootmode_addresses.find(static_cast(mode)); + if (it != g_bootmode_addresses.end()) + addr = it->second; + } + setReturnU32(ctx, addr); } // 0x5B GetThreadTLS (stub): return 0 void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { - setReturnS32(ctx, 0); + auto info = ensureCurrentThreadInfo(ctx); + if (!info) + { + setReturnU32(ctx, 0); + return; + } + + if (info->tlsBase == 0) + { + info->tlsBase = allocTlsAddr(rdram); + } + + setReturnU32(ctx, info->tlsBase); } // 0x74 RegisterExitHandler (stub): return 0 void RegisterExitHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + uint32_t func = getRegU32(ctx, 4); + uint32_t arg = getRegU32(ctx, 5); + if (func == 0) + { + setReturnS32(ctx, -1); + return; + } + + int tid = g_currentThreadId; + { + std::lock_guard lock(g_exit_handler_mutex); + g_exit_handlers[tid].push_back({func, arg}); + } + setReturnS32(ctx, 0); } } diff --git a/ps2xTest/CMakeLists.txt b/ps2xTest/CMakeLists.txt index 18c9a96..2f5fd3f 100644 --- a/ps2xTest/CMakeLists.txt +++ b/ps2xTest/CMakeLists.txt @@ -9,13 +9,21 @@ add_executable(ps2x_tests src/main.cpp src/code_generator_tests.cpp src/r5900_decoder_tests.cpp + src/elf_analyzer_tests.cpp ) +option(PRINT_GENERATED_CODE "Print generated code in tests" OFF) +if(PRINT_GENERATED_CODE) + target_compile_definitions(ps2x_tests PRIVATE PRINT_GENERATED_CODE) +endif() + target_include_directories(ps2x_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/ps2xRecomp/include + ${CMAKE_SOURCE_DIR}/ps2xAnalyzer/include ) target_link_libraries(ps2x_tests PRIVATE ps2_recomp_lib + ps2_analyzer_lib ) diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 4a72cc5..c540bd5 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -30,6 +30,51 @@ static Instruction makeNop(uint32_t address) return inst; } +static Instruction makeJal(uint32_t address, uint32_t target) +{ + Instruction inst{}; + inst.address = address; + inst.opcode = OPCODE_JAL; + inst.target = (target >> 2) & 0x3FFFFFF; + inst.hasDelaySlot = true; + inst.raw = (OPCODE_JAL << 26) | inst.target; + return inst; +} + +static Instruction makeJalr(uint32_t address, uint8_t rs, uint8_t rd) +{ + Instruction inst{}; + inst.address = address; + inst.opcode = OPCODE_SPECIAL; + inst.function = SPECIAL_JALR; + inst.rs = rs; + inst.rd = rd; // Destination for link address (default 31) + inst.hasDelaySlot = true; + inst.raw = (OPCODE_SPECIAL << 26) | (rs << 21) | (0 << 16) | (rd << 11) | (0 << 6) | SPECIAL_JALR; + return inst; +} + +static Instruction makeJr(uint32_t address, uint8_t rs) +{ + Instruction inst{}; + inst.address = address; + inst.opcode = OPCODE_SPECIAL; + inst.function = SPECIAL_JR; + inst.rs = rs; + inst.hasDelaySlot = true; + inst.raw = (OPCODE_SPECIAL << 26) | (rs << 21) | SPECIAL_JR; + return inst; +} + +static void printGeneratedCode(const std::string& name, const std::string& code) +{ +#ifdef PRINT_GENERATED_CODE + std::cout << "=== Generated Code for " << name << " ===" << std::endl; + std::cout << code << std::endl; + std::cout << "========================================" << std::endl; +#endif +} + void register_code_generator_tests() { MiniTest::Case("CodeGenerator", [](TestCase &tc) @@ -57,6 +102,7 @@ void register_code_generator_tests() CodeGenerator gen({}); std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("emits labels and gotos for internal branches", generated); t.IsTrue(generated.find("label_100c:") != std::string::npos, "branch target should emit a label"); t.IsTrue(generated.find("goto label_100c;") != std::string::npos, "internal branch should jump via goto"); @@ -79,6 +125,7 @@ void register_code_generator_tests() CodeGenerator gen({}); std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("labels delay slot when it is a branch target", generated); t.IsTrue(generated.find("label_2004:") != std::string::npos, "delay slot that is a target should emit a label"); t.IsTrue(generated.find("goto label_2004;") != std::string::npos, "branch to delay slot should use goto"); @@ -102,6 +149,7 @@ void register_code_generator_tests() CodeGenerator gen({}); std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("branches outside function still set pc", generated); t.IsTrue(generated.find("ctx->pc = 0x") != std::string::npos, "external branch should set ctx->pc"); t.IsTrue(generated.find("goto label_") == std::string::npos, "external branch should not use goto"); @@ -133,6 +181,7 @@ void register_code_generator_tests() CodeGenerator gen({targetSym}); std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("jumps to known symbols call by name", generated); t.IsTrue(generated.find("target_func(rdram, ctx, runtime); return;") != std::string::npos, "jump to known function should emit direct call"); @@ -158,6 +207,7 @@ void register_code_generator_tests() CodeGenerator gen({}); std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("jump to unknown target sets pc", generated); t.IsTrue(generated.find("ctx->pc = 0x") != std::string::npos, "unknown jump target should set ctx->pc"); t.IsTrue(generated.find("goto label_") == std::string::npos, "external jump should not use goto"); @@ -183,6 +233,7 @@ void register_code_generator_tests() gen.setRenamedFunctions({{0x8000, "renamed_target"}}); std::string sw = gen.generateJumpTableSwitch(inst, 0x0, entries); + printGeneratedCode("renamed function used in jump table", sw); t.IsTrue(sw.find("renamed_target(rdram, ctx, runtime);") != std::string::npos, "jump table should use renamed function name"); @@ -215,10 +266,186 @@ void register_code_generator_tests() gen.setRenamedFunctions({{targetSym.address, "ps2___is_pointer"}}); std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("reserved identifiers are sanitized and used in calls", generated); t.IsTrue(generated.find("void ps2___is_pointer(") != std::string::npos, "definition should use sanitized name"); t.IsTrue(generated.find("ps2___is_pointer(rdram, ctx, runtime); return;") != std::string::npos, - "call should use sanitized name"); - }); }); + "call should use sanitized name but got: " + generated); + }); + + tc.Run("JAL to known function emits call and check", [](TestCase &t) { + Function func; + func.name = "jal_test"; + func.start = 0xA000; + func.end = 0xA020; + func.isRecompiled = true; + func.isStub = false; + + Symbol targetSym; + targetSym.name = "some_func"; + targetSym.address = 0xB000; + targetSym.isFunction = true; + + // 0xA000: JAL 0xB000 + // 0xA004: NOP (delay slot) + Instruction jal = makeJal(0xA000, 0xB000); + Instruction delay = makeNop(0xA004); + + CodeGenerator gen({targetSym}); + std::string generated = gen.generateFunction(func, {jal, delay}, false); + printGeneratedCode("JAL to known function emits call and check", generated); + + // Expect: + // SET_GPR_U32(ctx, 31, 0xA008u); + // ctx->pc = 0xA004u; + // ... delay slot ... + // some_func(rdram, ctx, runtime); + // if (ctx->pc != 0xA008u) { return; } + + t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xA008u);") != std::string::npos, "JAL should set RA"); + t.IsTrue(generated.find("some_func(rdram, ctx, runtime);") != std::string::npos, "JAL should call function"); + t.IsTrue(generated.find("if (ctx->pc != 0xA008u) { return; }") != std::string::npos, "JAL should check return PC"); + }); + + tc.Run("JAL to internal target becomes goto", [](TestCase &t) { + Function func; + func.name = "jal_internal"; + func.start = 0xC000; + func.end = 0xC020; + func.isRecompiled = true; + func.isStub = false; + + // 0xC000: JAL 0xC010 + // 0xC004: NOP + // ... + // 0xC010: NOP + Instruction jal = makeJal(0xC000, 0xC010); + Instruction delay = makeNop(0xC004); + Instruction targetInst = makeNop(0xC010); + + CodeGenerator gen({}); + std::string generated = gen.generateFunction(func, {jal, delay, targetInst}, false); + printGeneratedCode("JAL to internal target becomes goto", generated); + + t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xC008u);") != std::string::npos, "Internal JAL should set RA"); + t.IsTrue(generated.find("goto label_c010;") != std::string::npos, "Internal JAL should use goto"); + }); + + tc.Run("JALR emits indirect call", [](TestCase &t) { + Function func; + func.name = "jalr_test"; + func.start = 0xD000; + func.end = 0xD020; + func.isRecompiled = true; + func.isStub = false; + + // 0xD000: JALR $4, $31 (call addr in $4, link to $31) + // 0xD004: NOP + Instruction jalr = makeJalr(0xD000, 4, 31); + Instruction delay = makeNop(0xD004); + + CodeGenerator gen({}); + std::string generated = gen.generateFunction(func, {jalr, delay}, false); + printGeneratedCode("JALR emits indirect call", generated); + + t.IsTrue(generated.find("uint32_t jumpTarget = GPR_U32(ctx, 4);") != std::string::npos, "JALR should read target from RS"); + t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xD008u);") != std::string::npos, "JALR should set link register"); + t.IsTrue(generated.find("auto targetFn = runtime->lookupFunction(jumpTarget);") != std::string::npos, "JALR should lookup function"); + t.IsTrue(generated.find("targetFn(rdram, ctx, runtime);") != std::string::npos, "JALR should call function"); + t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC"); + }); + + tc.Run("backward BEQ emits label and goto (sign-extended offset)", [](TestCase &t) { + Function func; + func.name = "backward_branch"; + func.start = 0x1100; + func.end = 0x1120; + func.isRecompiled = true; + func.isStub = false; + + // 0x1100: nop + // 0x1104: beq $1,$1, target 0x1100 (offset = -2 words) + // 0x1108: nop (delay) + std::vector instructions; + instructions.push_back(makeNop(0x1100)); + + Instruction br = makeBranch(0x1104, 0); + br.simmediate = static_cast(static_cast(-2)); + instructions.push_back(br); + + instructions.push_back(makeNop(0x1108)); + instructions.push_back(makeNop(0x110c)); + + CodeGenerator gen({}); + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("backward BEQ emits label and goto (sign-extended offset)", generated); + + t.IsTrue(generated.find("label_1100:") != std::string::npos, "target should emit a label"); + t.IsTrue(generated.find("goto label_1100;") != std::string::npos, "backward internal branch should goto label"); + }); + + tc.Run("branch-likely places delay slot only in taken path", [](TestCase &t) { + Function func; + func.name = "branch_likely"; + func.start = 0x1200; + func.end = 0x1220; + func.isRecompiled = true; + func.isStub = false; + + Instruction br{}; + br.address = 0x1200; + br.opcode = OPCODE_BEQL; // likely + br.rs = 1; + br.rt = 2; + br.simmediate = 1; // target = 0x1208 + br.isBranch = true; + br.hasDelaySlot = true; + br.raw = 0; + + Instruction delay{}; + delay.address = 0x1204; + delay.opcode = OPCODE_ADDIU; + delay.rs = 0; + delay.rt = 7; // make it non-nop so translation is distinctive + delay.simmediate = 123; + delay.raw = 0; + + Instruction target = makeNop(0x1208); + + CodeGenerator gen({}); + std::string generated = gen.generateFunction(func, { br, delay, target }, false); + printGeneratedCode("branch-likely places delay slot only in taken path", generated); + + t.IsTrue(generated.find("SET_GPR_S32(ctx, 7,") != std::string::npos, "delay slot should be translated"); + t.IsTrue(generated.find("if (branch_taken_0x1200)") != std::string::npos, "should generate branch_taken variable and if for likely branch"); + }); + + tc.Run("JR $31 emits switch for internal return targets", [](TestCase &t) { + Function func; + func.name = "jr_ra_switch"; + func.start = 0x1300; + func.end = 0x1340; + func.isRecompiled = true; + func.isStub = false; + + // Create an internal JAL so collectInternalBranchTargets inserts returnAddr (0x1308) as internal target. + Instruction jal = makeJal(0x1300, 0x1310); + Instruction jalDelay = makeNop(0x1304); + Instruction atTarget = makeNop(0x1310); + + // JR $31 at 0x1314 with delay slot at 0x1318 + Instruction jr = makeJr(0x1314, 31); + Instruction jrDelay = makeNop(0x1318); + + CodeGenerator gen({}); + std::string generated = gen.generateFunction(func, { jal, jalDelay, atTarget, jr, jrDelay }, false); + printGeneratedCode("JR $31 emits switch for internal return targets", generated); + + t.IsTrue(generated.find("switch (jumpTarget)") != std::string::npos, "JR $31 should emit switch for internal targets"); + t.IsTrue(generated.find("case 0x1308u: goto label_1308;") != std::string::npos, "switch should include return address from internal JAL"); + }); + + + }); } diff --git a/ps2xTest/src/elf_analyzer_tests.cpp b/ps2xTest/src/elf_analyzer_tests.cpp new file mode 100644 index 0000000..38558fc --- /dev/null +++ b/ps2xTest/src/elf_analyzer_tests.cpp @@ -0,0 +1,235 @@ +#include "MiniTest.h" +#include "ps2recomp/elf_analyzer.h" +#include "ps2recomp/instructions.h" +#include "ps2recomp/types.h" + +#include +#include + +using namespace ps2recomp; + +namespace +{ + Instruction makeInstruction(uint32_t address, uint32_t opcode) + { + Instruction inst; + inst.address = address; + inst.opcode = opcode; + return inst; + } +} + +void register_elf_analyzer_tests() +{ + MiniTest::Case("ElfAnalyzerHeuristics", [](TestCase &tc) + { + tc.Run("library-symbol classification table", [](TestCase &t) + { + ElfAnalyzer analyzer("dummy.elf"); + + t.IsTrue(analyzer.isLibrarySymbolNameForHeuristics("printf"), + "printf should be classified as library"); + t.IsTrue(analyzer.isLibrarySymbolNameForHeuristics("_printf"), + "_printf should be classified as library"); + t.IsTrue(analyzer.isLibrarySymbolNameForHeuristics("sceCdRead"), + "sce-prefixed PS2 API should be classified as library"); + + t.IsFalse(analyzer.isLibrarySymbolNameForHeuristics("bhEne13_Brain"), + "named game function should not be classified as library"); + t.IsFalse(analyzer.isLibrarySymbolNameForHeuristics("sub_00100C00"), + "unreliable auto-generated names should not be classified as library"); }); + + tc.Run("reliable-symbol heuristic filters autogenerated names", [](TestCase &t) + { + t.IsTrue(ElfAnalyzer::isReliableSymbolNameForHeuristics("bhEne13_Brain"), + "expected game symbol to be considered reliable"); + t.IsTrue(ElfAnalyzer::isReliableSymbolNameForHeuristics("SetupSoundDriver"), + "expected named function to be considered reliable"); + t.IsTrue(ElfAnalyzer::isReliableSymbolNameForHeuristics("sceCdRead"), + "expected PS2 API symbol to be considered reliable"); + + t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("sub_00100C00"), + "sub_ prefix should be treated as unreliable"); + t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("func_1ABC"), + "func_ prefix should be treated as unreliable"); + t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("entry_001000"), + "entry_ prefix should be treated as unreliable"); + t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("LAB_00001234"), + "LAB_ prefix should be treated as unreliable"); + t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("0x00100ABC"), + "pure hex-style symbol should be treated as unreliable"); }); + + tc.Run("system-symbol heuristic is strict to system patterns", [](TestCase &t) + { + t.IsTrue(ElfAnalyzer::isSystemSymbolNameForHeuristics("__main"), + "__main should be classified as system"); + t.IsTrue(ElfAnalyzer::isSystemSymbolNameForHeuristics("_start"), + "_start should be classified as system"); + t.IsTrue(ElfAnalyzer::isSystemSymbolNameForHeuristics(".text.startup"), + ".text.* should be classified as system"); + + t.IsFalse(ElfAnalyzer::isSystemSymbolNameForHeuristics("bhObj001"), + "game symbol should not be classified as system"); + t.IsFalse(ElfAnalyzer::isSystemSymbolNameForHeuristics("SetupSoundDriver"), + "engine/game symbol should not be classified as system"); + t.IsFalse(ElfAnalyzer::isSystemSymbolNameForHeuristics("sub_00100C00"), + "unreliable names should not be considered system by this classifier"); }); + + tc.Run("entry-point mapping handles exact inside and fallback", [](TestCase &t) + { + Function f1; + f1.name = "funcA"; + f1.start = 0x1000; + f1.end = 0x1100; + + Function f2; + f2.name = "funcB"; + f2.start = 0x1100; + f2.end = 0x1200; + + Function f3; + f3.name = "fallbackA"; + f3.start = 0x100000; + f3.end = 0x100100; + + std::vector functions{f1, f2, f3}; + + t.Equals(ElfAnalyzer::findEntryFunctionIndexForHeuristics(functions, 0x1100), 1, + "exact entry should map to function start"); + t.Equals(ElfAnalyzer::findEntryFunctionIndexForHeuristics(functions, 0x10F0), 0, + "entry inside range should map to containing function"); + t.Equals(ElfAnalyzer::findEntryFunctionIndexForHeuristics(functions, 0x2000), -1, + "unknown entry should return no mapping"); + t.Equals(ElfAnalyzer::findFallbackEntryFunctionIndexForHeuristics(functions), 2, + "fallback should find 0x100000 entry"); + + Function fallbackB; + fallbackB.name = "fallbackB"; + fallbackB.start = 0x80100000; + fallbackB.end = 0x80100100; + + std::vector fallbackOnly{fallbackB}; + t.Equals(ElfAnalyzer::findFallbackEntryFunctionIndexForHeuristics(fallbackOnly), 0, + "fallback should also accept 0x80100000"); }); + + tc.Run("signal-based skip heuristics keep reliable names and skip unreliable/system", [](TestCase &t) + { + // Hardware I/O signal via LUI upper address in I/O region. + Instruction hw = makeInstruction(0x1000, OPCODE_LUI); + hw.immediate = 0x1002; // 0x10020000 + std::vector hwInst{hw}; + const bool hasHardwareIO = ElfAnalyzer::hasHardwareIOSignalForHeuristics(hwInst); + t.IsTrue(hasHardwareIO, "hardware I/O signal should be detected"); + + // Large + complex MMI signal. + std::vector largeMmi(501); + largeMmi[250] = makeInstruction(0x2000, OPCODE_MMI); + largeMmi[250].isMMI = true; + largeMmi[250].function = MMI_MMI1; + const bool hasLargeComplexMMI = ElfAnalyzer::hasLargeComplexMMISignalForHeuristics(largeMmi); + t.IsTrue(hasLargeComplexMMI, "large complex MMI signal should be detected"); + + // Self-modifying signal: SW into a code section, with base from preceding LUI. + Instruction lui = makeInstruction(0x3000, OPCODE_LUI); + lui.rt = 9; + lui.immediate = 0x1000; // base 0x10000000 + Instruction sw = makeInstruction(0x3004, OPCODE_SW); + sw.rs = 9; + sw.immediate = 0x2000; // target 0x10002000 + + std::vector smcInst{lui, sw}; + Section code{}; + code.name = ".text"; + code.address = 0x10002000; + code.size = 0x100; + code.isCode = true; + std::vector
sections{code}; + const bool hasSelfModifying = ElfAnalyzer::hasSelfModifyingSignalForHeuristics(smcInst, sections); + t.IsTrue(hasSelfModifying, "self-modifying signal should be detected"); + + // Decision behavior by name reliability/system-ness. + t.IsFalse(hasHardwareIO && ElfAnalyzer::shouldAutoSkipNameForHeuristics("bhEne13_Brain"), + "reliable game symbol should not auto-skip from hardware signal alone"); + t.IsTrue(hasHardwareIO && ElfAnalyzer::shouldAutoSkipNameForHeuristics("sub_00100C00"), + "unreliable symbol should auto-skip when risky signals exist"); + t.IsTrue(hasLargeComplexMMI && ElfAnalyzer::shouldAutoSkipNameForHeuristics("__main"), + "system symbol should auto-skip when risky signals exist"); + t.IsFalse(hasSelfModifying && ElfAnalyzer::shouldAutoSkipNameForHeuristics("topThread"), + "do-not-skip list should override auto-skip"); }); + + tc.Run("patch-density threshold behavior", [](TestCase &t) + { + t.IsTrue(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("sub_00100C00", 100, 6, false), + "high-density patches on unreliable names should skip"); + t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("sub_00100C00", 200, 6, false), + "density below threshold should not skip"); + t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("sub_00100C00", 100, 5, false), + "patch count <= 5 should not skip"); + t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("printf", 100, 6, true), + "library functions should not be auto-skipped by patch density"); + t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("bhEne13_Brain", 100, 6, false), + "reliable game function should not be auto-skipped by patch density"); + t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("topThread", 100, 6, false), + "do-not-skip names should never be auto-skipped"); }); + + tc.Run("jump-table detection finds canonical sltiu/bne/lw/jr pattern", [](TestCase &t) + { + // sltiu -> bne/beq bounds check -> ... -> lui/addiu base -> lw -> jr loadedReg + Instruction sltiu = makeInstruction(0x4000, OPCODE_SLTIU); + sltiu.immediate = 3; // number of entries + Instruction bne = makeInstruction(0x4004, OPCODE_BNE); + Instruction filler = makeInstruction(0x4008, OPCODE_ADDIU); + Instruction jtLui = makeInstruction(0x400C, OPCODE_LUI); + jtLui.rt = 8; + jtLui.immediate = 0x2000; + Instruction jtAddiu = makeInstruction(0x4010, OPCODE_ADDIU); + jtAddiu.rs = 8; + jtAddiu.rt = 9; // load base register + jtAddiu.immediate = 0x0100; + Instruction jtLoad = makeInstruction(0x4014, OPCODE_LW); + jtLoad.rs = 9; + jtLoad.rt = 10; + Instruction jtJump = makeInstruction(0x4018, OPCODE_SPECIAL); + jtJump.function = SPECIAL_JR; + jtJump.rs = 10; + + std::vector instructions{sltiu, bne, filler, jtLui, jtAddiu, jtLoad, jtJump}; + + const uint32_t base = (0x2000u << 16) | 0x0100u; + std::unordered_map tableMemory{ + {base + 0, 0x101000}, + {base + 4, 0x102000}, + {base + 8, 0x103000}, + }; + + auto readWord = [&tableMemory](uint32_t address, uint32_t &outWord) -> bool + { + auto it = tableMemory.find(address); + if (it == tableMemory.end()) + { + return false; + } + outWord = it->second; + return true; + }; + + auto jumpTables = ElfAnalyzer::detectJumpTablesForHeuristics(instructions, std::vector
(), readWord); + t.Equals(jumpTables.size(), static_cast(1), "one jump table should be detected"); + if (!jumpTables.empty()) + { + t.Equals(jumpTables[0].address, base, "jump table base address should match LUI/ADDIU pattern"); + t.Equals(jumpTables[0].baseRegister, static_cast(9), "base register should match LW base"); + t.Equals(jumpTables[0].entries.size(), static_cast(3), "entry count should match SLTIU bound"); + t.Equals(jumpTables[0].entries[0].target, static_cast(0x101000), "entry 0 target should match"); + t.Equals(jumpTables[0].entries[1].target, static_cast(0x102000), "entry 1 target should match"); + t.Equals(jumpTables[0].entries[2].target, static_cast(0x103000), "entry 2 target should match"); + } + + Instruction invalid = sltiu; + invalid.immediate = 1001; // rejected by guard + auto invalidTables = ElfAnalyzer::detectJumpTablesForHeuristics( + std::vector{invalid, bne, filler, jtLui, jtAddiu, jtLoad, jtJump}, std::vector
(), + readWord); + t.Equals(invalidTables.size(), static_cast(0), + "bounds over guard limit should not produce a jump table"); }); }); +} diff --git a/ps2xTest/src/main.cpp b/ps2xTest/src/main.cpp index 729b92e..c0bbee8 100644 --- a/ps2xTest/src/main.cpp +++ b/ps2xTest/src/main.cpp @@ -2,10 +2,12 @@ void register_code_generator_tests(); void register_r5900_decoder_tests(); +void register_elf_analyzer_tests(); int main() { register_code_generator_tests(); register_r5900_decoder_tests(); + register_elf_analyzer_tests(); return MiniTest::Run(); }