diff --git a/CMakeLists.txt b/CMakeLists.txt index fafc59aa..f55bfb20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,40 @@ cmake_minimum_required(VERSION 3.21) +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{VITASDK}) + set(PS2X_VITA_TOOLCHAIN_FILE "$ENV{VITASDK}/share/vita.toolchain.cmake") + if(EXISTS "${PS2X_VITA_TOOLCHAIN_FILE}") + set(CMAKE_TOOLCHAIN_FILE "${PS2X_VITA_TOOLCHAIN_FILE}" CACHE PATH + "Toolchain file used for cross-compiling" FORCE) + message(STATUS "Using VitaSDK toolchain from VITASDK: ${CMAKE_TOOLCHAIN_FILE}") + endif() +endif() + project("PS2 Retro X") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -option(PS2X_RUNTIME OFF) +option(PS2X_BUILD_RECOMP "Build ps2xRecomp" ON) +option(PS2X_BUILD_RUNTIME "Build ps2xRuntime" ON) +option(PS2X_BUILD_ANALYZER "Build ps2xAnalyzer" ON) +option(PS2X_BUILD_TEST "Build ps2xTest" ON) +option(PS2X_BUILD_STUDIO "Build ps2xStudio" ON) -# ARM64 support using sse2neon +set(PS2X_IS_ARM_TARGET OFF) +set(PS2X_IS_AARCH64_TARGET OFF) if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64") - message(STATUS "ARM64 detected, fetching sse2neon") + set(PS2X_IS_ARM_TARGET ON) + set(PS2X_IS_AARCH64_TARGET ON) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm|^ARM") + set(PS2X_IS_ARM_TARGET ON) +endif() + +if((CMAKE_C_COMPILER MATCHES "arm-vita-eabi") OR + (CMAKE_CXX_COMPILER MATCHES "arm-vita-eabi")) + set(PS2X_IS_ARM_TARGET ON) +endif() + +if(PS2X_IS_ARM_TARGET) + message(STATUS "ARM target detected, fetching sse2neon") include(FetchContent) FetchContent_Declare( @@ -22,13 +48,13 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64") include_directories(${sse2neon_SOURCE_DIR}) add_compile_definitions(USE_SSE2NEON) - if(APPLE) + if(PS2X_IS_AARCH64_TARGET AND APPLE) # macOS ARM64 already uses optimal defaults message(STATUS "macOS ARM64 - using default compiler flags") - elseif(MSVC) + elseif(PS2X_IS_AARCH64_TARGET AND MSVC) # Windows ARM64 - MSVC already uses optimal defaults message(STATUS "Windows ARM64 (MSVC) - using default compiler flags") - else() + elseif(PS2X_IS_AARCH64_TARGET) # Linux ARM64 - add NEON flags message(STATUS "Non-Apple ARM64 - adding NEON compiler flags") add_compile_options(-march=armv8-a+fp+simd) @@ -39,14 +65,28 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64") if(COMPILER_SUPPORTS_CRYPTO_CRC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+fp+simd+crypto+crc") message(STATUS "Crypto and CRC extensions enabled") + else() + add_compile_options(-march=armv8-a+fp+simd) endif() endif() endif() -add_subdirectory("ps2xRecomp") +if(PS2X_BUILD_RECOMP) + add_subdirectory("ps2xRecomp") +endif() + +if(PS2X_BUILD_RUNTIME) + add_subdirectory("ps2xRuntime") +endif() -add_subdirectory("ps2xRuntime") +if(PS2X_BUILD_ANALYZER) + add_subdirectory("ps2xAnalyzer") +endif() -add_subdirectory("ps2xAnalyzer") -add_subdirectory("ps2xTest") -add_subdirectory("ps2xStudio") +if(PS2X_BUILD_TEST) + add_subdirectory("ps2xTest") +endif() + +if(PS2X_BUILD_STUDIO) + add_subdirectory("ps2xStudio") +endif() diff --git a/ps2xAnalyzer/CMakeLists.txt b/ps2xAnalyzer/CMakeLists.txt index b374c2af..14e46f6e 100644 --- a/ps2xAnalyzer/CMakeLists.txt +++ b/ps2xAnalyzer/CMakeLists.txt @@ -32,3 +32,10 @@ install(TARGETS ps2_analyzer ps2_analyzer_lib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib ) + +include("${CMAKE_SOURCE_DIR}/ps2xRuntime/cmake/ReleaseMode.cmake") + +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + EnableFastReleaseMode(ps2_analyzer_lib) + EnableFastReleaseMode(ps2_analyzer) +endif() diff --git a/ps2xAnalyzer/src/elf_analyzer.cpp b/ps2xAnalyzer/src/elf_analyzer.cpp index 245322f3..1b7cd847 100644 --- a/ps2xAnalyzer/src/elf_analyzer.cpp +++ b/ps2xAnalyzer/src/elf_analyzer.cpp @@ -24,6 +24,7 @@ namespace ps2recomp static bool hasPs2ApiPrefix(const std::string &name); static bool hasReliableSymbolName(const std::string &name); static bool isDoNotSkipOrStub(const std::string &name); + static bool isKnownLocalHelperName(const std::string &name); static bool matchesKernelRuntimeName(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); @@ -313,7 +314,7 @@ namespace ps2recomp "malloc", "free", "calloc", "realloc", "aligned_alloc", "posix_memalign", // Memory manipulation - "memcpy", "memset", "memmove", "memcmp", "memcpy2", "memchr", "bcopy", "bzero", + "memcpy", "memset", "memmove", "memcmp", "memchr", "bcopy", "bzero", // String manipulation "strcpy", "strncpy", "strcat", "strncat", "strcmp", "strncmp", "strlen", "strstr", @@ -2020,7 +2021,6 @@ namespace ps2recomp const std::vector libraryPrefixes = { "sce", "Sce", "SCE", // Sony prefixes "sif", "Sif", "SIF", // SIF functions - "pad", "Pad", "PAD", // Pad functions "gs", "Gs", "GS", // Graphics Synthesizer "dma", "Dma", "DMA", // DMA functions "iop", "Iop", "IOP", // IOP functions @@ -2068,6 +2068,15 @@ namespace ps2recomp return kDoNotSkipOrStub.contains(name); } + static bool isKnownLocalHelperName(const std::string &name) + { + static const std::unordered_set kKnownLocalHelpers = { + "memcpy2", + "_memcpy2"}; + + return kKnownLocalHelpers.contains(name); + } + static bool hasReliableSymbolName(const std::string &name) { if (name.empty()) @@ -2182,12 +2191,18 @@ namespace ps2recomp if (!hasReliableSymbolName(name)) return false; + if (isKnownLocalHelperName(name)) + return false; + std::string normalizedName = name; if (normalizedName[0] == '_' && normalizedName.size() > 1) { normalizedName = normalizedName.substr(1); } + if (isKnownLocalHelperName(normalizedName)) + return false; + if (matchesKernelRuntimeName(normalizedName)) return true; diff --git a/ps2xRecomp/CMakeLists.txt b/ps2xRecomp/CMakeLists.txt index cae5ca87..69c37102 100644 --- a/ps2xRecomp/CMakeLists.txt +++ b/ps2xRecomp/CMakeLists.txt @@ -136,3 +136,10 @@ install(TARGETS ps2_recomp ps2_recomp_lib install(DIRECTORY include/ DESTINATION include ) + +include("${CMAKE_SOURCE_DIR}/ps2xRuntime/cmake/ReleaseMode.cmake") + +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + EnableFastReleaseMode(ps2_recomp_lib) + EnableFastReleaseMode(ps2_recomp) +endif() diff --git a/ps2xRecomp/include/ps2recomp/code_generator.h b/ps2xRecomp/include/ps2recomp/code_generator.h index 04adfdea..e5ce9e13 100644 --- a/ps2xRecomp/include/ps2recomp/code_generator.h +++ b/ps2xRecomp/include/ps2recomp/code_generator.h @@ -37,6 +37,8 @@ namespace ps2recomp struct AnalysisResult { std::unordered_set entryPoints; + std::unordered_set externalEntryPoints; + std::unordered_set resumeEntryPoints; std::unordered_map> jumpTableTargets; }; @@ -49,15 +51,18 @@ namespace ps2recomp void setBootstrapInfo(const BootstrapInfo &info); void setRelocationCallNames(const std::unordered_map &callNames); void setConfiguredJumpTables(const std::vector &jumpTables); + void setResumeEntryTargets(const std::unordered_map> &resumeTargetsByOwner); AnalysisResult collectInternalBranchTargets(const Function &function, - const std::vector &instructions); + const std::vector &instructions, + const std::vector *allFunctions = nullptr); public: std::unordered_map m_symbols; std::unordered_map m_renamedFunctions; std::unordered_map m_relocationCallNames; std::unordered_map> m_configJumpTableTargetsByAddress; + std::unordered_map> m_resumeEntryTargetsByOwner; const std::vector
& m_sections; BootstrapInfo m_bootstrapInfo; diff --git a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h index 10c9f5c2..5740de13 100644 --- a/ps2xRecomp/include/ps2recomp/ps2_recompiler.h +++ b/ps2xRecomp/include/ps2recomp/ps2_recompiler.h @@ -62,6 +62,7 @@ namespace ps2recomp std::unordered_map m_stubHandlerBindingsByStart; std::map m_generatedStubs; std::unordered_map m_functionRenames; + std::unordered_map> m_resumeEntryTargetsByOwner; CodeGenerator::BootstrapInfo m_bootstrapInfo; bool decodeFunction(Function &function); diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 666a59e7..faa05493 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -153,6 +153,17 @@ namespace ps2recomp } } + void CodeGenerator::setResumeEntryTargets(const std::unordered_map> &resumeTargetsByOwner) + { + m_resumeEntryTargetsByOwner = resumeTargetsByOwner; + for (auto &[owner, targets] : m_resumeEntryTargetsByOwner) + { + (void)owner; + std::sort(targets.begin(), targets.end()); + targets.erase(std::unique(targets.begin(), targets.end()), targets.end()); + } + } + std::string CodeGenerator::getFunctionName(uint32_t address) const { auto it = m_renamedFunctions.find(address); @@ -230,9 +241,14 @@ namespace ps2recomp auto emitInternalTarget = [&](uint32_t target, uint32_t sourcePc, std::string_view indent) { ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, target); - if (target <= sourcePc) + const bool isCallLikeEdge = + (branchInst.opcode == OPCODE_JAL) || + (branchInst.opcode == OPCODE_SPECIAL && branchInst.function == SPECIAL_JALR); + if (target <= sourcePc && !isCallLikeEdge) { - ss << fmt::format("{}runtime->cooperativeGuestYield();\n", indent); + ss << fmt::format("{}if (runtime->shouldPreemptGuestExecution()) {{\n", indent); + ss << fmt::format("{} return;\n", indent); + ss << fmt::format("{}}}\n", indent); ss << fmt::format("{}goto label_{:x};\n", indent, target); } else @@ -652,7 +668,7 @@ namespace ps2recomp CodeGenerator::~CodeGenerator() = default; CodeGenerator::AnalysisResult CodeGenerator::collectInternalBranchTargets( - const Function &function, const std::vector &instructions) + const Function &function, const std::vector &instructions, const std::vector *allFunctions) { AnalysisResult result; std::unordered_set instructionAddresses; @@ -660,6 +676,97 @@ namespace ps2recomp bool hasIndirectRegisterJump = false; std::vector indirectJumps; + auto isExecutableAddress = [&](uint32_t address) -> bool + { + for (const auto §ion : m_sections) + { + if (!section.isCode) + { + continue; + } + if (address >= section.address && address < (section.address + section.size)) + { + return true; + } + } + return false; + }; + + auto findContainingExternalFunction = [&](uint32_t address) -> const Function * + { + if (!allFunctions || !isExecutableAddress(address)) + { + return nullptr; + } + + const Function *best = nullptr; + for (const auto &candidateFn : *allFunctions) + { + if (!candidateFn.isRecompiled || candidateFn.isStub || candidateFn.isSkipped) + { + continue; + } + + if (candidateFn.name.rfind("entry_", 0) == 0) + { + continue; + } + + if (address < candidateFn.start || address >= candidateFn.end) + { + continue; + } + + if (!best || candidateFn.start > best->start) + { + best = &candidateFn; + } + } + + return best; + }; + + auto queueExternalEntryTarget = [&](uint32_t target) + { + const Function *containingFn = findContainingExternalFunction(target); + if (!containingFn) + { + return; + } + + if (containingFn->start == function.start) + { + return; + } + + if (target == containingFn->start) + { + return; + } + + result.externalEntryPoints.insert(target); + }; + + auto queueResumeEntryTarget = [&](uint32_t resumeAddr) + { + if (resumeAddr >= function.start && resumeAddr < function.end && + instructionAddresses.contains(resumeAddr)) + { + result.entryPoints.insert(resumeAddr); + result.resumeEntryPoints.insert(resumeAddr); + } + }; + + auto queueLoopResumeEntryTarget = [&](uint32_t target, uint32_t sourcePc) + { + if (target > sourcePc || target == function.start) + { + return; + } + + queueResumeEntryTarget(target); + }; + for (const auto &inst : instructions) { instructionAddresses.insert(inst.address); @@ -685,6 +792,11 @@ namespace ps2recomp instructionAddresses.contains(target)) { result.entryPoints.insert(target); + queueLoopResumeEntryTarget(target, inst.address); + } + else + { + queueExternalEntryTarget(target); } } else if (isStaticJump) @@ -694,15 +806,20 @@ namespace ps2recomp instructionAddresses.contains(target)) { result.entryPoints.insert(target); + queueLoopResumeEntryTarget(target, inst.address); if (inst.opcode == OPCODE_JAL) { - uint32_t returnAddr = inst.address + 8; - if (returnAddr >= function.start && returnAddr < function.end && - instructionAddresses.contains(returnAddr)) - { - result.entryPoints.insert(returnAddr); - } + queueResumeEntryTarget(inst.address + 8u); + } + } + else + { + queueExternalEntryTarget(target); + + if (inst.opcode == OPCODE_JAL) + { + queueResumeEntryTarget(inst.address + 8u); } } } @@ -712,6 +829,11 @@ namespace ps2recomp { bool needsJrFallback = false; for (const Instruction* jrInst : indirectJumps) { + if (jrInst->function == SPECIAL_JALR) + { + queueResumeEntryTarget(jrInst->address + 8u); + } + bool foundTable = false; uint32_t jrReg = jrInst->rs; @@ -790,6 +912,10 @@ namespace ps2recomp { jrTargets.push_back(target); } + else + { + queueExternalEntryTarget(target); + } } if (!jrTargets.empty()) @@ -849,6 +975,10 @@ namespace ps2recomp uniqueTargets.insert(target); } } + else + { + queueExternalEntryTarget(target); + } } else { validJumpTable = false; break; @@ -909,6 +1039,20 @@ namespace ps2recomp } AnalysisResult analysisResult = collectInternalBranchTargets(function, instructions); + std::vector resumeTargets(analysisResult.resumeEntryPoints.begin(), + analysisResult.resumeEntryPoints.end()); + auto resumeIt = m_resumeEntryTargetsByOwner.find(function.start); + if (resumeIt != m_resumeEntryTargetsByOwner.end()) + { + resumeTargets.insert(resumeTargets.end(), resumeIt->second.begin(), resumeIt->second.end()); + } + std::sort(resumeTargets.begin(), resumeTargets.end()); + resumeTargets.erase(std::unique(resumeTargets.begin(), resumeTargets.end()), resumeTargets.end()); + for (uint32_t target : resumeTargets) + { + analysisResult.entryPoints.insert(target); + } + const std::unordered_set& internalTargets = analysisResult.entryPoints; ss << "// Function: " << function.name << "\n"; ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n"; @@ -926,6 +1070,16 @@ namespace ps2recomp ss << " PS_LOG_ENTRY(\"" << sanitizedName << "\");\n"; ss << "#endif\n"; ss << "\n"; + if (!resumeTargets.empty()) + { + ss << " switch (ctx->pc) {\n"; + for (uint32_t target : resumeTargets) + { + ss << " case 0x" << std::hex << target << "u: goto label_" << target << ";\n" << std::dec; + } + ss << " default: break;\n"; + ss << " }\n\n"; + } ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n" << std::dec; ss << "\n"; @@ -3782,6 +3936,21 @@ namespace ps2recomp emitRegistration(first, second); } + ss << "\n // Register resumable entry points\n"; + for (const auto &[ownerStart, targets] : m_resumeEntryTargetsByOwner) + { + const std::string ownerName = getFunctionName(ownerStart); + if (ownerName.empty()) + { + continue; + } + + for (uint32_t target : targets) + { + emitRegistration(target, ownerName); + } + } + ss << "\n // Register stub functions\n"; for (const auto &[first, second] : stubFunctions) { diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index 2ea7d54c..428bbdfa 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -241,16 +241,16 @@ namespace ps2recomp size_t passCount = 0; }; - struct StaticEntryTarget + bool isEntryFunctionName(const std::string &name) { - uint32_t target = 0u; - bool isCall = false; - }; + return name.rfind("entry_", 0) == 0; + } EntryDiscoveryStats discoverAdditionalEntryPointsImpl( std::vector &functions, std::unordered_map> &decodedFunctions, const std::vector
§ions, + CodeGenerator *codeGenerator, const std::function &decodeExternalFunction) { std::unordered_set existingStarts; @@ -275,25 +275,6 @@ namespace ps2recomp return false; }; - auto getStaticEntryTarget = [](const Instruction &inst) -> std::optional - { - if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL) - { - StaticEntryTarget target{}; - target.target = decodeAbsoluteJumpTarget(inst.address, inst.target); - target.isCall = (inst.opcode == OPCODE_JAL); - return target; - } - - if (inst.opcode == OPCODE_SPECIAL && - (inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR)) - { - return std::nullopt; - } - - return std::nullopt; - }; - auto isSimpleReturnThunkStart = [](const Instruction &inst) -> bool { return inst.opcode == OPCODE_SPECIAL && @@ -355,6 +336,30 @@ namespace ps2recomp std::vector newEntries; std::unordered_set pendingStarts; + auto queuePendingEntry = [&](uint32_t target) + { + if (!isExecutableAddress(target)) + { + return; + } + + if (existingStarts.contains(target) || pendingStarts.contains(target)) + { + return; + } + + PendingEntry pending{}; + pending.target = target; + if (const Function *containingFunction = findContainingFunction(target)) + { + pending.containingStart = containingFunction->start; + pending.containingEnd = containingFunction->end; + } + + pendingEntries.push_back(pending); + pendingStarts.insert(target); + }; + for (const auto &function : functions) { if (!function.isRecompiled || function.isStub || function.isSkipped) @@ -362,6 +367,11 @@ namespace ps2recomp continue; } + if (isEntryFunctionName(function.name)) + { + continue; + } + auto decodedIt = decodedFunctions.find(function.start); if (decodedIt == decodedFunctions.end()) { @@ -369,60 +379,26 @@ namespace ps2recomp } const auto &instructions = decodedIt->second; - - for (const auto &inst : instructions) + CodeGenerator::AnalysisResult analysisResult{}; + if (codeGenerator) { - auto targetOpt = getStaticEntryTarget(inst); - if (!targetOpt.has_value()) - { - continue; - } - - const StaticEntryTarget staticTarget = targetOpt.value(); - const uint32_t target = staticTarget.target; - - if ((target & 0x3) != 0 || !isExecutableAddress(target)) - { - continue; - } - - if (existingStarts.contains(target) || pendingStarts.contains(target)) - { - continue; - } - - const bool targetInCurrentFunction = std::any_of( - instructions.begin(), instructions.end(), - [&](const Instruction &candidate) - { return candidate.address == target; }); - if (targetInCurrentFunction && !staticTarget.isCall) - { - // jumps with the current decoded function remain labels/gotos. - continue; - } - - const Function *containingFunction = findContainingFunction(target); - if (targetInCurrentFunction) - { - PendingEntry pending{}; - pending.target = target; - pending.containingStart = function.start; - pending.containingEnd = function.end; - pendingEntries.push_back(pending); - pendingStarts.insert(target); - continue; - } + analysisResult = codeGenerator->collectInternalBranchTargets( + function, instructions, &functions); + } + else + { + analysisResult.resumeEntryPoints.clear(); + analysisResult.externalEntryPoints.clear(); + } - PendingEntry pending{}; - pending.target = target; - if (containingFunction) - { - pending.containingStart = containingFunction->start; - pending.containingEnd = containingFunction->end; - } + for (uint32_t target : analysisResult.externalEntryPoints) + { + queuePendingEntry(target); + } - pendingEntries.push_back(pending); - pendingStarts.insert(target); + for (uint32_t target : analysisResult.resumeEntryPoints) + { + queuePendingEntry(target); } } @@ -553,12 +529,6 @@ namespace ps2recomp return stats; } - - bool isEntryFunctionName(const std::string &name) - { - return name.rfind("entry_", 0) == 0; - } - size_t resliceEntryFunctionsImpl( std::vector &functions, std::unordered_map> &decodedFunctions) @@ -1293,25 +1263,124 @@ namespace ps2recomp void PS2Recompiler::discoverAdditionalEntryPoints() { - const EntryDiscoveryStats stats = discoverAdditionalEntryPointsImpl( - m_functions, - m_decodedFunctions, - m_sections, - [&](Function &entryFunction) - { return decodeFunction(entryFunction); }); + m_resumeEntryTargetsByOwner.clear(); + if (!m_codeGenerator) + { + return; + } - if (stats.discoveredCount > 0) + auto findContainingFunction = [&](uint32_t address) -> const Function * { - std::cout << "Discovered " << stats.discoveredCount - << " additional entry point(s) inside existing functions across " - << stats.passCount << " pass(es)." << std::endl; + const Function *best = nullptr; + for (const auto &function : m_functions) + { + if (!function.isRecompiled || function.isStub || function.isSkipped) + { + continue; + } + + if (isEntryFunctionName(function.name)) + { + continue; + } + + if (address < function.start || address >= function.end) + { + continue; + } + + auto decodedIt = m_decodedFunctions.find(function.start); + if (decodedIt == m_decodedFunctions.end()) + { + continue; + } + + const auto &decoded = decodedIt->second; + const bool hasAddress = std::any_of(decoded.begin(), decoded.end(), + [&](const Instruction &candidate) + { return candidate.address == address; }); + if (!hasAddress) + { + continue; + } + + if (!best || function.start > best->start) + { + best = &function; + } + } + return best; + }; + + for (const auto &function : m_functions) + { + if (!function.isRecompiled || function.isStub || function.isSkipped) + { + continue; + } + + if (isEntryFunctionName(function.name)) + { + continue; + } + + auto decodedIt = m_decodedFunctions.find(function.start); + if (decodedIt == m_decodedFunctions.end()) + { + continue; + } + + const auto &instructions = decodedIt->second; + CodeGenerator::AnalysisResult analysisResult = + m_codeGenerator->collectInternalBranchTargets(function, instructions, &m_functions); + + auto &ownerTargets = m_resumeEntryTargetsByOwner[function.start]; + ownerTargets.insert(ownerTargets.end(), + analysisResult.resumeEntryPoints.begin(), + analysisResult.resumeEntryPoints.end()); + + for (uint32_t target : analysisResult.externalEntryPoints) + { + const Function *owner = findContainingFunction(target); + if (!owner) + { + continue; + } + + if (owner->start == target) + { + continue; + } + + auto &targets = m_resumeEntryTargetsByOwner[owner->start]; + targets.push_back(target); + } } - const size_t reslicedCount = resliceEntryFunctionsImpl(m_functions, m_decodedFunctions); - if (reslicedCount > 0) + size_t totalTargets = 0u; + for (auto it = m_resumeEntryTargetsByOwner.begin(); it != m_resumeEntryTargetsByOwner.end();) + { + auto &targets = it->second; + std::sort(targets.begin(), targets.end()); + targets.erase(std::unique(targets.begin(), targets.end()), targets.end()); + if (targets.empty()) + { + it = m_resumeEntryTargetsByOwner.erase(it); + continue; + } + + totalTargets += targets.size(); + ++it; + } + + m_codeGenerator->setResumeEntryTargets(m_resumeEntryTargetsByOwner); + + if (totalTargets > 0u) { - std::cout << "Resliced " << reslicedCount - << " entry function(s) after discovery." << std::endl; + std::cout << "Collected " << totalTargets + << " resumable entry point(s) across " + << m_resumeEntryTargetsByOwner.size() + << " owner function(s)." << std::endl; } } @@ -1542,10 +1611,12 @@ namespace ps2recomp std::unordered_map> &decodedFunctions, const std::vector
§ions) { + CodeGenerator codeGenerator({}, sections); const EntryDiscoveryStats stats = discoverAdditionalEntryPointsImpl( functions, decodedFunctions, sections, + &codeGenerator, [](Function &) { return false; }); return stats.discoveredCount; diff --git a/ps2xRecomp/tools/ghidra/ExportPS2Functions.java b/ps2xRecomp/tools/ghidra/ExportPS2Functions.java index 4f2987b1..4b107665 100644 --- a/ps2xRecomp/tools/ghidra/ExportPS2Functions.java +++ b/ps2xRecomp/tools/ghidra/ExportPS2Functions.java @@ -52,8 +52,13 @@ public class ExportPS2Functions extends GhidraScript { "cmd_sem_init" )); + private static final Set KNOWN_LOCAL_HELPER_NAMES = new HashSet<>(Arrays.asList( + "memcpy2", + "_memcpy2" + )); + private static final Set PS2_API_PREFIXES = new HashSet<>(Arrays.asList( - "sce", "sif", "pad", "gs", "dma", "iop", "vif", "spu", "mc", "libc" + "sce", "sif", "gs", "dma", "iop", "vif", "spu", "mc", "libc" )); private static final Set KNOWN_STDLIB_NAMES = new HashSet<>(Arrays.asList( @@ -61,7 +66,7 @@ public class ExportPS2Functions extends GhidraScript { "puts", "putchar", "getchar", "gets", "fgets", "fputs", "scanf", "fscanf", "sscanf", "sprint", "sbprintf", "malloc", "free", "calloc", "realloc", "aligned_alloc", "posix_memalign", - "memcpy", "memset", "memmove", "memcmp", "memcpy2", "memchr", "bcopy", "bzero", + "memcpy", "memset", "memmove", "memcmp", "memchr", "bcopy", "bzero", "strcpy", "strncpy", "strcat", "strncat", "strcmp", "strncmp", "strlen", "strstr", "strchr", "strrchr", "strdup", "strtok", "strtok_r", "strerror", "fopen", "fclose", "fread", "fwrite", "fseek", "ftell", "rewind", "fflush", @@ -237,7 +242,14 @@ private static boolean isLibraryFunctionName(String name) { return false; } + if (KNOWN_LOCAL_HELPER_NAMES.contains(name)) { + return false; + } + String normalized = normalizeOptionalLeadingUnderscore(name); + if (KNOWN_LOCAL_HELPER_NAMES.contains(normalized)) { + return false; + } if (KERNEL_RUNTIME_NAME_PATTERN.matcher(normalized).matches()) { return true; } diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index d6b481a8..ddaf5fa5 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -5,18 +5,161 @@ project(PS2Runtime VERSION 0.1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -include(FetchContent) -set(FETCHCONTENT_QUIET FALSE) -set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(BUILD_GAMES OFF CACHE BOOL "" FORCE) - -FetchContent_Declare( - raylib - GIT_REPOSITORY "https://github.com/raysan5/raylib.git" - GIT_TAG "5.5" # we will migrate to 4.2.0 later, trust me it will be better - GIT_PROGRESS TRUE -) -FetchContent_MakeAvailable(raylib) +option(PS2X_ENABLE_RUNNER_UNITY_BUILD "Build ps2EntryRunner with CMake unity build" ON) +set(PS2X_RUNNER_UNITY_BUILD_BATCH_SIZE 8 CACHE STRING "Unity build batch size for ps2EntryRunner") +option(PS2X_ENABLE_SCCACHE "Use sccache as compiler launcher when available" ON) + +if(PS2X_ENABLE_SCCACHE) + find_program(PS2X_SCCACHE_PROGRAM sccache) + if(PS2X_SCCACHE_PROGRAM) + if(CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER_LAUNCHER "${PS2X_SCCACHE_PROGRAM}") + endif() + set(CMAKE_CXX_COMPILER_LAUNCHER "${PS2X_SCCACHE_PROGRAM}") + message(STATUS "Using sccache: ${PS2X_SCCACHE_PROGRAM}") + else() + message(STATUS "sccache not found; continuing without compiler launcher") + endif() +endif() + +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/ReleaseMode.cmake") + +set(PS2X_IS_VITA OFF) +if((CMAKE_C_COMPILER MATCHES "arm-vita-eabi") OR + (CMAKE_CXX_COMPILER MATCHES "arm-vita-eabi")) + set(PS2X_IS_VITA ON) +endif() + +option(PS2X_VITA_CREATE_PACKAGE "Create Vita eboot/vpk targets" ON) +set(PS2X_VITA_APP_NAME "PS2 Retro X" CACHE STRING "Display name for the Vita bubble") +set(PS2X_VITA_TITLEID "RANJ00001" CACHE STRING "9-character Vita title id") +set(PS2X_VITA_VERSION "01.00" CACHE STRING "Vita app version") +set(PS2X_DEFAULT_BOOT_ELF "" CACHE STRING "Guest ELF path passed directly to main() when argv is unavailable") + +if(PS2X_IS_VITA) + if(NOT DEFINED ENV{VITASDK}) + message(FATAL_ERROR "VITASDK is not defined. Configure inside the VitaSDK environment or pass the Vita toolchain file.") + endif() + + set(PS2X_VITASDK "$ENV{VITASDK}") + if(NOT EXISTS "${PS2X_VITASDK}/share/vita.cmake") + message(FATAL_ERROR "Could not find vita.cmake under ${PS2X_VITASDK}/share.") + endif() + + include("${PS2X_VITASDK}/share/vita.cmake" REQUIRED) +else() + include(FetchContent) + set(FETCHCONTENT_QUIET FALSE) + set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(BUILD_GAMES OFF CACHE BOOL "" FORCE) + + FetchContent_Declare( + raylib + GIT_REPOSITORY "https://github.com/raysan5/raylib.git" + GIT_TAG "5.5" # we will migrate to 4.2.0 later, trust me it will be better + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(raylib) +endif() + +add_library(ps2_host_backend INTERFACE) + +set(PS2X_DEFAULT_BOOT_ELF_DEFINE "") +if(PS2X_DEFAULT_BOOT_ELF) + set(PS2X_DEFAULT_BOOT_ELF_DEFINE "${PS2X_DEFAULT_BOOT_ELF}") + string(REPLACE "\\" "\\\\" PS2X_DEFAULT_BOOT_ELF_DEFINE "${PS2X_DEFAULT_BOOT_ELF_DEFINE}") + string(REPLACE "\"" "\\\"" PS2X_DEFAULT_BOOT_ELF_DEFINE "${PS2X_DEFAULT_BOOT_ELF_DEFINE}") +endif() + +if(PS2X_IS_VITA) + set(PS2X_VITA_PREFIX "${PS2X_VITASDK}/arm-vita-eabi") + set(PS2X_VITA_EXTRA_INCLUDE_DIRS "" CACHE STRING "Extra include directories for Vita host dependencies") + set(PS2X_VITA_LIBRARY_DIR "${PS2X_VITA_PREFIX}/lib") + set(PS2X_VITA_REQUIRED_LIBRARY_NAMES + raylib + SDL2 + OpenSLES + taihen_stub + SceAppMgr_stub + SceCtrl_stub + SceGxm_stub + SceCommonDialog_stub + SceLibKernel_stub + SceAudio_stub + SceTouch_stub + SceHid_stub + SceMotion_stub + SceSysmodule_stub + SceIofilemgr_stub + SceNetCtl_stub + SceNet_stub + SceDisplay_stub + SceAppUtil_stub + SceAudioIn_stub + ScePower_stub + SceProcessmgr_stub + SceIme_stub + libIMGEGL_stub_weak + libgpu_es4_ext_stub_weak + libGLESv2_stub_weak + ) + + set(PS2X_VITA_INCLUDE_DIRS + "${PS2X_VITA_PREFIX}/include" + ) + foreach(PS2X_VITA_OPTIONAL_INCLUDE_DIR IN ITEMS + "${PS2X_VITA_PREFIX}/include/raylib" + "${PS2X_VITA_PREFIX}/include/SDL2") + if(EXISTS "${PS2X_VITA_OPTIONAL_INCLUDE_DIR}") + list(APPEND PS2X_VITA_INCLUDE_DIRS "${PS2X_VITA_OPTIONAL_INCLUDE_DIR}") + endif() + endforeach() + if(PS2X_VITA_EXTRA_INCLUDE_DIRS) + list(APPEND PS2X_VITA_INCLUDE_DIRS ${PS2X_VITA_EXTRA_INCLUDE_DIRS}) + endif() + + if(NOT EXISTS "${PS2X_VITA_PREFIX}/include/SDL2/SDL.h") + message(FATAL_ERROR + "Missing SDL2 headers under ${PS2X_VITA_PREFIX}/include/SDL2. " + "Quenom/raylib-5.5-vita requires SDL2 with PVR support installed into VitaSDK.") + endif() + + if(NOT EXISTS "${PS2X_VITA_LIBRARY_DIR}") + message(FATAL_ERROR "Missing Vita library directory: ${PS2X_VITA_LIBRARY_DIR}") + endif() + + function(ps2x_find_vita_library out_var library_name) + string(MAKE_C_IDENTIFIER "${library_name}" library_id) + find_library(${out_var} + NAMES "${library_name}" "${library_name}.a" "lib${library_name}.a" + PATHS "${PS2X_VITA_LIBRARY_DIR}" + NO_DEFAULT_PATH + ) + if(NOT ${out_var}) + message(FATAL_ERROR + "Could not find Vita library '${library_name}' under ${PS2X_VITA_LIBRARY_DIR}. " + "Install Quenom/raylib-5.5-vita and its SDL2/PVR dependencies into VitaSDK first.") + endif() + set(${out_var} "${${out_var}}" PARENT_SCOPE) + endfunction() + + set(PS2X_VITA_RESOLVED_LIBRARIES "") + foreach(PS2X_VITA_LIBRARY_NAME IN LISTS PS2X_VITA_REQUIRED_LIBRARY_NAMES) + ps2x_find_vita_library(PS2X_VITA_LIBRARY_PATH "${PS2X_VITA_LIBRARY_NAME}") + list(APPEND PS2X_VITA_RESOLVED_LIBRARIES "${PS2X_VITA_LIBRARY_PATH}") + endforeach() + + target_compile_definitions(ps2_host_backend INTERFACE PLATFORM_VITA) + target_include_directories(ps2_host_backend INTERFACE ${PS2X_VITA_INCLUDE_DIRS}) + target_link_libraries(ps2_host_backend INTERFACE + ${PS2X_VITA_RESOLVED_LIBRARIES} + m + c + pthread + ) +else() + target_link_libraries(ps2_host_backend INTERFACE raylib) +endif() add_library(ps2_runtime STATIC src/lib/game_overrides.cpp @@ -30,10 +173,17 @@ add_library(ps2_runtime STATIC src/lib/ps2_memory.cpp src/lib/ps2_pad.cpp src/lib/ps2_runtime.cpp - src/lib/ps2_stubs.cpp - src/lib/ps2_syscalls.cpp src/lib/ps2_vif1_interpreter.cpp src/lib/ps2_vu1.cpp + src/lib/games_database.cpp +) + +file(GLOB_RECURSE KERNEL_SRC_FILES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/lib/Kernel/*.cpp" +) + +target_sources(ps2_runtime PRIVATE + ${KERNEL_SRC_FILES} ) file(GLOB RUNNER_SRC_FILES CONFIGURE_DEPENDS @@ -53,21 +203,63 @@ add_executable(ps2EntryRunner ${RUNNER_SRC_FILES} ) +if(PS2X_ENABLE_RUNNER_UNITY_BUILD) + set_target_properties(ps2EntryRunner PROPERTIES + UNITY_BUILD ON + UNITY_BUILD_BATCH_SIZE "${PS2X_RUNNER_UNITY_BUILD_BATCH_SIZE}" + ) +endif() + +if(PS2X_DEFAULT_BOOT_ELF_DEFINE) + target_compile_definitions(ps2EntryRunner PRIVATE + PS2X_DEFAULT_BOOT_ELF="${PS2X_DEFAULT_BOOT_ELF_DEFINE}" + ) +endif() + if(MSVC) target_compile_options(ps2EntryRunner PRIVATE /FS) endif() target_include_directories(ps2_runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/Kernel ) -target_link_libraries(ps2_runtime PRIVATE raylib) +target_link_libraries(ps2_runtime PUBLIC ps2_host_backend) target_link_libraries(ps2EntryRunner PRIVATE ps2_runtime - raylib ) +if(PS2X_IS_VITA) + set(VITA_MKSFOEX_FLAGS "${VITA_MKSFOEX_FLAGS} -d PARENTAL_LEVEL=1") + + if(PS2X_VITA_CREATE_PACKAGE) + vita_create_self(eboot.bin ps2EntryRunner UNSAFE) + vita_create_vpk(ps2EntryRunner.vpk ${PS2X_VITA_TITLEID} eboot.bin + VERSION ${PS2X_VITA_VERSION} + NAME ${PS2X_VITA_APP_NAME} + ) + endif() +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + EnableFastReleaseMode(ps2_runtime) + EnableFastReleaseMode(ps2EntryRunner) + + if(MSVC AND WIN32) + target_link_options(ps2EntryRunner PRIVATE + $<$,$>:/SUBSYSTEM:WINDOWS> + $<$,$>:/ENTRY:mainCRTStartup> + ) + elseif(MINGW AND WIN32) + target_link_options(ps2EntryRunner PRIVATE + $<$,$>:-mwindows> + ) + endif() + +endif() + # Work around WinAPI vs raylib symbol clash for CloseWindow on x64 if(MSVC) target_link_options(ps2EntryRunner PRIVATE "/FORCE:MULTIPLE") diff --git a/ps2xRuntime/Readme.md b/ps2xRuntime/Readme.md index 8016fca6..4ae035fe 100644 --- a/ps2xRuntime/Readme.md +++ b/ps2xRuntime/Readme.md @@ -10,6 +10,19 @@ The runtime library provides the execution environment for recompiled code, incl Take your decompiled code and place the cpp files on ps2xRuntime/src/runner and header files on ps2xRuntime/include and compile/be happy. +## Vita Build Notes + +The Vita runtime uses `Quenom/raylib-5.5-vita` for vita build. I recommend build runtime only. + +Expected environment: + +* `VITASDK` points to your VitaSDK root. +* `Quenom/raylib-5.5-vita` has already been built and installed into `$VITASDK/arm-vita-eabi`. +* SDL2 with the PVR backend required by that raylib fork is also installed into the same VitaSDK prefix. +* `PS2X_DEFAULT_BOOT_ELF` is mandatory, you need to define where your game is like "ux0:data/RANJ00001/game/SLUS_201.84". + +The CMake for `ps2xRuntime` consumes those preinstalled headers and libraries from VitaSDK. It does not fetch or install the Vita raylib fork for you. + ## Adding Custom Function Implementations You can add custom implementations for PS2 system calls or game functions by: @@ -41,4 +54,4 @@ You can patch specific instructions in the recompiled code to fix game issues or * Graphics and sound output require external implementations * Some PS2-specific hardware features may not be fully supported -* Performance may vary based on the complexity of the game \ No newline at end of file +* Performance may vary based on the complexity of the game diff --git a/ps2xRuntime/cmake/ReleaseMode.cmake b/ps2xRuntime/cmake/ReleaseMode.cmake new file mode 100644 index 00000000..92ef0d54 --- /dev/null +++ b/ps2xRuntime/cmake/ReleaseMode.cmake @@ -0,0 +1,42 @@ +include(CheckIPOSupported) + +check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT IPO_ERROR) + +function(EnableFastReleaseMode TargetName) + message("> Enabling optimization for: ${TargetName}") + if(MSVC) + target_compile_options(${TargetName} PRIVATE + $<$: + /O2 # speed + /Ob2 # inline aggressively + /Oi # intrinsics + /GL # whole program opt + /Gy # function-level linking + /Gw # global data in COMDAT + /GF # string pooling + /Zc:inline # remove unreferenced inline + /fp:fast # fast math (graphics friendly) + /DNDEBUG + /arch:AVX2 # Advanced Vector Extensions 2 + /GS- # Disable Buffer Security Check (faster) + /Qspectre- # Disable Spectre mitigations (faster) + > + ) + + if(TARGET ${TargetName}) + target_link_options(${TargetName} PRIVATE + $<$: + /LTCG # link-time code generation + /OPT:REF # remove unreferenced + /OPT:ICF # fold identical COMDATs + > + ) + endif() + endif() + + if(IPO_SUPPORTED) + set_property(TARGET ${TargetName} PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) + else() + message(WARNING "Interprocedural optimization not supported: ${ipo_error}") + endif() +endfunction() \ No newline at end of file diff --git a/ps2xRuntime/include/ps2_call_list.h b/ps2xRuntime/include/ps2_call_list.h index 8ad4b4ca..39fbf1e3 100644 --- a/ps2xRuntime/include/ps2_call_list.h +++ b/ps2xRuntime/include/ps2_call_list.h @@ -4,127 +4,133 @@ // I know ugly, but will work for now. -#define PS2_SYSCALL_LIST(X) \ - X(FlushCache) \ - X(iFlushCache) \ - X(ResetEE) \ - X(SetMemoryMode) \ - \ - X(CreateThread) \ - X(DeleteThread) \ - X(StartThread) \ - X(ExitThread) \ - X(ExitDeleteThread) \ - X(TerminateThread) \ - X(SuspendThread) \ - X(ResumeThread) \ - X(GetThreadId) \ - X(ReferThreadStatus) \ - X(iReferThreadStatus) \ - X(SleepThread) \ - X(WakeupThread) \ - X(iWakeupThread) \ - X(CancelWakeupThread) \ - X(iCancelWakeupThread) \ - X(ChangeThreadPriority) \ - X(iChangeThreadPriority) \ - X(RotateThreadReadyQueue) \ - X(iRotateThreadReadyQueue)\ - X(ReleaseWaitThread) \ - X(iReleaseWaitThread) \ - \ - X(CreateSema) \ - X(DeleteSema) \ - X(SignalSema) \ - X(iSignalSema) \ - X(WaitSema) \ - X(PollSema) \ - X(iPollSema) \ - X(ReferSemaStatus) \ - X(iReferSemaStatus) \ - \ - X(CreateEventFlag) \ - X(DeleteEventFlag) \ - X(SetEventFlag) \ - X(iSetEventFlag) \ - X(ClearEventFlag) \ - X(iClearEventFlag) \ - X(WaitEventFlag) \ - X(PollEventFlag) \ - X(iPollEventFlag) \ - X(ReferEventFlagStatus) \ - X(iReferEventFlagStatus) \ - \ - X(SetAlarm) \ - X(iSetAlarm) \ - X(CancelAlarm) \ - X(iCancelAlarm) \ - \ - X(AddIntcHandler) \ - X(AddIntcHandler2) \ - X(RemoveIntcHandler) \ - X(AddDmacHandler) \ - X(AddDmacHandler2) \ - X(RemoveDmacHandler) \ - X(EnableIntc) \ - X(iEnableIntc) \ - X(DisableIntc) \ - X(iDisableIntc) \ - X(EnableDmac) \ - X(iEnableDmac) \ - X(DisableDmac) \ - X(iDisableDmac) \ - \ - X(SifStopModule) \ - X(SifLoadModule) \ - X(SifInitRpc) \ - X(SifBindRpc) \ - X(SifCallRpc) \ - X(SifRegisterRpc) \ - X(SifCheckStatRpc) \ - X(SifSetRpcQueue) \ - X(SifRemoveRpcQueue) \ - X(SifRemoveRpc) \ - X(sceSifCallRpc) \ - X(sceSifSendCmd) \ - X(sceRpcGetPacket) \ - \ - X(fioOpen) \ - X(fioClose) \ - X(fioRead) \ - X(fioWrite) \ - X(fioLseek) \ - X(fioMkdir) \ - X(fioChdir) \ - X(fioRmdir) \ - X(fioGetstat) \ - X(fioRemove) \ - \ - X(SetGsCrt) \ - X(GsSetCrt) \ - X(GsGetIMR) \ - X(iGsGetIMR) \ - X(GsPutIMR) \ - X(iGsPutIMR) \ - X(SetVSyncFlag) \ - X(SetSyscall) \ - X(GsSetVideoMode) \ - \ - X(GetOsdConfigParam) \ - X(SetOsdConfigParam) \ - X(GetRomName) \ - X(SifLoadElfPart) \ - X(sceSifLoadElf) \ - X(sceSifLoadElfPart) \ - X(sceSifLoadModule) \ - X(sceSifLoadModuleBuffer) \ - \ - X(SetupThread) \ - X(EndOfHeap) \ - X(GetMemorySize) \ - X(Deci2Call) \ - X(QueryBootMode) \ - X(GetThreadTLS) \ +#define PS2_SYSCALL_LIST(X) \ + X(FlushCache) \ + X(iFlushCache) \ + X(ResetEE) \ + X(SetMemoryMode) \ + \ + X(InitThread) \ + X(CreateThread) \ + X(DeleteThread) \ + X(StartThread) \ + X(ExitThread) \ + X(ExitDeleteThread) \ + X(TerminateThread) \ + X(SuspendThread) \ + X(ResumeThread) \ + X(GetThreadId) \ + X(ReferThreadStatus) \ + X(iReferThreadStatus) \ + X(SleepThread) \ + X(WakeupThread) \ + X(iWakeupThread) \ + X(CancelWakeupThread) \ + X(iCancelWakeupThread) \ + X(ChangeThreadPriority) \ + X(iChangeThreadPriority) \ + X(RotateThreadReadyQueue) \ + X(iRotateThreadReadyQueue) \ + X(ReleaseWaitThread) \ + X(iReleaseWaitThread) \ + \ + X(CreateSema) \ + X(DeleteSema) \ + X(SignalSema) \ + X(iSignalSema) \ + X(WaitSema) \ + X(PollSema) \ + X(iPollSema) \ + X(ReferSemaStatus) \ + X(iReferSemaStatus) \ + \ + X(CreateEventFlag) \ + X(DeleteEventFlag) \ + X(SetEventFlag) \ + X(iSetEventFlag) \ + X(ClearEventFlag) \ + X(iClearEventFlag) \ + X(WaitEventFlag) \ + X(PollEventFlag) \ + X(iPollEventFlag) \ + X(ReferEventFlagStatus) \ + X(iReferEventFlagStatus) \ + \ + X(InitAlarm) \ + X(SetAlarm) \ + X(iSetAlarm) \ + X(CancelAlarm) \ + X(iCancelAlarm) \ + X(ReleaseAlarm) \ + X(iReleaseAlarm) \ + \ + X(AddIntcHandler) \ + X(AddIntcHandler2) \ + X(RemoveIntcHandler) \ + X(AddDmacHandler) \ + X(AddDmacHandler2) \ + X(RemoveDmacHandler) \ + X(EnableIntc) \ + X(iEnableIntc) \ + X(DisableIntc) \ + X(iDisableIntc) \ + X(EnableDmac) \ + X(iEnableDmac) \ + X(DisableDmac) \ + X(iDisableDmac) \ + \ + X(SifStopModule) \ + X(SifLoadModule) \ + X(SifInitRpc) \ + X(SifBindRpc) \ + X(SifCallRpc) \ + X(SifRegisterRpc) \ + X(SifCheckStatRpc) \ + X(SifSetRpcQueue) \ + X(SifRemoveRpcQueue) \ + X(SifRemoveRpc) \ + X(sceSifCallRpc) \ + X(sceSifSendCmd) \ + X(sceRpcGetPacket) \ + \ + X(fioOpen) \ + X(fioClose) \ + X(fioRead) \ + X(fioWrite) \ + X(fioLseek) \ + X(fioMkdir) \ + X(fioChdir) \ + X(fioRmdir) \ + X(fioGetstat) \ + X(fioRemove) \ + \ + X(SetGsCrt) \ + X(GsSetCrt) \ + X(GsGetIMR) \ + X(iGsGetIMR) \ + X(GsPutIMR) \ + X(iGsPutIMR) \ + X(SetVSyncFlag) \ + X(SetSyscall) \ + X(GsSetVideoMode) \ + \ + X(GetOsdConfigParam) \ + X(SetOsdConfigParam) \ + X(EnableCache) \ + X(DisableCache) \ + X(GetRomName) \ + X(SifLoadElfPart) \ + X(sceSifLoadElf) \ + X(sceSifLoadElfPart) \ + X(sceSifLoadModule) \ + X(sceSifLoadModuleBuffer) \ + \ + X(SetupThread) \ + X(EndOfHeap) \ + X(GetMemorySize) \ + X(Deci2Call) \ + X(QueryBootMode) \ + X(GetThreadTLS) \ X(RegisterExitHandler) // Stubs @@ -203,11 +209,11 @@ X(write) \ /* PS2 native */ \ X(DmaAddr) \ - X(Pad_init) \ - X(Pad_set) \ X(builtin_set_imask) \ X(sceCdRI) \ X(sceCdRM) \ + X(sceDevVif0Reset) \ + X(sceDevVu0Reset) \ X(sceFsDbChk) \ X(sceFsIntrSigSema) \ X(sceFsSemExit) \ @@ -224,7 +230,6 @@ X(sceSifLoadModule) \ X(sceSifSendCmd) \ X(sceVu0ecossin) \ - X(iopGetArea) \ X(mcCallMessageTypeSe) \ X(mcCheckReadStartConfigFile) \ X(mcCheckReadStartSaveFile) \ @@ -268,8 +273,6 @@ X(mceGetInfoApdx) \ X(mceIntrReadFixAlign) \ X(mceStorePwd) \ - X(pdGetPeripheral) \ - X(pdInitPeripheral) \ X(sceCdApplyNCmd) \ X(sceCdBreak) \ X(sceCdCallback) \ @@ -341,6 +344,18 @@ X(sceDmaWatch) \ X(sceFsInit) \ X(sceFsReset) \ + X(sceGifPkAddGsAD) \ + X(sceGifPkAddGsData) \ + X(sceGifPkCloseGifTag) \ + X(sceGifPkCnt) \ + X(sceGifPkEnd) \ + X(sceGifPkInit) \ + X(sceGifPkOpenGifTag) \ + X(sceGifPkRef) \ + X(sceGifPkRefLoadImage) \ + X(sceGifPkReset) \ + X(sceGifPkReserve) \ + X(sceGifPkTerminate) \ X(sceGsExecLoadImage) \ X(sceGsExecStoreImage) \ X(sceGsGetGParam) \ @@ -362,6 +377,19 @@ X(sceGsSyncV) \ X(sceGsSyncVCallback) \ X(sceGszbufaddr) \ + X(sceVif1PkAddGsAD) \ + X(sceVif1PkAlign) \ + X(sceVif1PkCall) \ + X(sceVif1PkCloseDirectCode) \ + X(sceVif1PkCloseGifTag) \ + X(sceVif1PkCnt) \ + X(sceVif1PkEnd) \ + X(sceVif1PkInit) \ + X(sceVif1PkOpenDirectCode) \ + X(sceVif1PkOpenGifTag) \ + X(sceVif1PkReset) \ + X(sceVif1PkReserve) \ + X(sceVif1PkTerminate) \ X(sceeFontInit) \ X(sceeFontLoadFont) \ X(sceeFontPrintfAt) \ @@ -382,6 +410,7 @@ X(sceMcChdir) \ X(sceMcClose) \ X(sceMcDelete) \ + X(sceMcEnd) \ X(sceMcFlush) \ X(sceMcFormat) \ X(sceMcGetDir) \ @@ -477,6 +506,7 @@ X(sceSetPtm) \ X(sceSifAddCmdHandler) \ X(sceSifAllocIopHeap) \ + X(sceSifAllocSysMemory) \ X(sceSifBindRpc) \ X(sceSifCheckStatRpc) \ X(sceSifDmaStat) \ @@ -484,6 +514,7 @@ X(sceSifExitCmd) \ X(sceSifExitRpc) \ X(sceSifFreeIopHeap) \ + X(sceSifFreeSysMemory) \ X(sceSifGetDataTable) \ X(sceSifGetIopAddr) \ X(sceSifGetNextRequest) \ @@ -509,6 +540,8 @@ X(sceSifSetCmdBuffer) \ X(sceSifSetDChain) \ X(sceSifSetDma) \ + X(isceSifSetDChain) \ + X(isceSifSetDma) \ X(sceSifSetIopAddr) \ X(sceSifSetReg) \ X(sceSifSetRpcQueue) \ @@ -639,19 +672,10 @@ X(sceVu0UnitMatrix) \ X(sceVu0ViewScreenMatrix) \ X(sceWrite) \ - X(sdDrvInit) \ - X(sdSndStopAll) \ - X(sdSysFinish) \ - X(syFree) \ - X(syHwInit) \ - X(syHwInit2) \ - X(syMallocInit) \ - X(syRtcInit) \ - X(InitThread) \ /* Game/middleware */ // Test hooks: override pad input for scePadRead. -#define PS2_TEST_HOOK_LIST(X) \ - X(setPadOverrideState, (uint16_t buttons, uint8_t lx, uint8_t ly, \ - uint8_t rx, uint8_t ry)) \ +#define PS2_TEST_HOOK_LIST(X) \ + X(setPadOverrideState, (uint16_t buttons, uint8_t lx, uint8_t ly, \ + uint8_t rx, uint8_t ry)) \ X(clearPadOverrideState, (void)) diff --git a/ps2xRuntime/include/ps2_gs_psmt4.h b/ps2xRuntime/include/ps2_gs_psmt4.h deleted file mode 100644 index 1e5ce353..00000000 --- a/ps2xRuntime/include/ps2_gs_psmt4.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef PS2_GS_PSMT4_H -#define PS2_GS_PSMT4_H - -#include - -namespace GSPSMT4 -{ - - static const uint16_t columnTable4[16][32] = { - {0, 8, 16, 24, 32, 40, 48, 56, 2, 10, 18, 26, 34, 42, 50, 58, 4, 12, 20, 28, 36, 44, 52, 60, 6, 14, 22, 30, 38, 46, 54, 62}, - {512, 520, 528, 536, 544, 552, 560, 568, 514, 522, 530, 538, 546, 554, 562, 570, 516, 524, 532, 540, 548, 556, 564, 572, 518, 526, 534, 542, 550, 558, 566, 574}, - {33, 41, 49, 57, 1, 9, 17, 25, 35, 43, 51, 59, 3, 11, 19, 27, 37, 45, 53, 61, 5, 13, 21, 29, 39, 47, 55, 63, 7, 15, 23, 31}, - {545, 553, 561, 569, 513, 521, 529, 537, 547, 555, 563, 571, 515, 523, 531, 539, 549, 557, 565, 573, 517, 525, 533, 541, 551, 559, 567, 575, 519, 527, 535, 543}, - {1056, 1064, 1072, 1080, 1024, 1032, 1040, 1048, 1058, 1066, 1074, 1082, 1026, 1034, 1042, 1050, 1060, 1068, 1076, 1084, 1028, 1036, 1044, 1052, 1062, 1070, 1078, 1086, 1030, 1038, 1046, 1054}, - {1568, 1576, 1584, 1592, 1536, 1544, 1552, 1560, 1570, 1578, 1586, 1594, 1538, 1546, 1554, 1562, 1572, 1580, 1588, 1596, 1540, 1548, 1556, 1564, 1574, 1582, 1590, 1598, 1542, 1550, 1558, 1566}, - {1025, 1033, 1041, 1049, 1057, 1065, 1073, 1081, 1027, 1035, 1043, 1051, 1059, 1067, 1075, 1083, 1029, 1037, 1045, 1053, 1061, 1069, 1077, 1085, 1031, 1039, 1047, 1055, 1063, 1071, 1079, 1087}, - {1537, 1545, 1553, 1561, 1569, 1577, 1585, 1593, 1539, 1547, 1555, 1563, 1571, 1579, 1587, 1595, 1541, 1549, 1557, 1565, 1573, 1581, 1589, 1597, 1543, 1551, 1559, 1567, 1575, 1583, 1591, 1599}, - {2048, 2056, 2064, 2072, 2080, 2088, 2096, 2104, 2050, 2058, 2066, 2074, 2082, 2090, 2098, 2106, 2052, 2060, 2068, 2076, 2084, 2092, 2100, 2108, 2054, 2062, 2070, 2078, 2086, 2094, 2102, 2110}, - {2560, 2568, 2576, 2584, 2592, 2600, 2608, 2616, 2562, 2570, 2578, 2586, 2594, 2602, 2610, 2618, 2564, 2572, 2580, 2588, 2596, 2604, 2612, 2620, 2566, 2574, 2582, 2590, 2598, 2606, 2614, 2622}, - {2081, 2089, 2097, 2105, 2049, 2057, 2065, 2073, 2083, 2091, 2099, 2107, 2051, 2059, 2067, 2075, 2085, 2093, 2101, 2109, 2053, 2061, 2069, 2077, 2087, 2095, 2103, 2111, 2055, 2063, 2071, 2079}, - {2593, 2601, 2609, 2617, 2561, 2569, 2577, 2585, 2595, 2603, 2611, 2619, 2563, 2571, 2579, 2587, 2597, 2605, 2613, 2621, 2565, 2573, 2581, 2589, 2599, 2607, 2615, 2623, 2567, 2575, 2583, 2591}, - {3104, 3112, 3120, 3128, 3072, 3080, 3088, 3096, 3106, 3114, 3122, 3130, 3074, 3082, 3090, 3098, 3108, 3116, 3124, 3132, 3076, 3084, 3092, 3100, 3110, 3118, 3126, 3134, 3078, 3086, 3094, 3102}, - {3616, 3624, 3632, 3640, 3584, 3592, 3600, 3608, 3618, 3626, 3634, 3642, 3586, 3594, 3602, 3610, 3620, 3628, 3636, 3644, 3588, 3596, 3604, 3612, 3622, 3630, 3638, 3646, 3590, 3598, 3606, 3614}, - {3073, 3081, 3089, 3097, 3105, 3113, 3121, 3129, 3075, 3083, 3091, 3099, 3107, 3115, 3123, 3131, 3077, 3085, 3093, 3101, 3109, 3117, 3125, 3133, 3079, 3087, 3095, 3103, 3111, 3119, 3127, 3135}, - {3585, 3593, 3601, 3609, 3617, 3625, 3633, 3641, 3587, 3595, 3603, 3611, 3619, 3627, 3635, 3643, 3589, 3597, 3605, 3613, 3621, 3629, 3637, 3645, 3591, 3599, 3607, 3615, 3623, 3631, 3639, 3647}, - }; - - inline uint32_t pageLocalNibbleOffset(uint32_t x, uint32_t y) - { - uint32_t yy = y & 0x7Fu; - uint32_t xx = x & 0x7Fu; - uint32_t blockBase = (((xx >> 5) & 3u) << 12) + - (((yy >> 4) & 7u) << 6); - return blockBase + columnTable4[yy & 15u][xx & 31u]; - } - - inline uint32_t addrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y) - { - const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u; - const uint32_t localNibble = pageLocalNibbleOffset(x, y); - const uint32_t localByte = localNibble >> 1u; - const uint32_t localRow = localByte >> 8u; - const uint32_t localColumnByte = localByte & 0xFFu; - const uint32_t globalByte = - block * 256u + - (((y >> 7u) * 32u + localRow) * (pagesPerRow * 256u)) + - ((x >> 7u) * 256u + localColumnByte); - return (globalByte << 1u) | (localNibble & 1u); - } - -} - -#endif diff --git a/ps2xRuntime/include/ps2_gs_psmt8.h b/ps2xRuntime/include/ps2_gs_psmt8.h deleted file mode 100644 index 994b67b3..00000000 --- a/ps2xRuntime/include/ps2_gs_psmt8.h +++ /dev/null @@ -1,256 +0,0 @@ -#ifndef PS2_GS_PSMT8_H -#define PS2_GS_PSMT8_H - -#include -#include - -namespace GSPSMT8 -{ - - static constexpr uint8_t blockTable8[4][8] = { - {0, 1, 4, 5, 16, 17, 20, 21}, - {2, 3, 6, 7, 18, 19, 22, 23}, - {8, 9, 12, 13, 24, 25, 28, 29}, - {10, 11, 14, 15, 26, 27, 30, 31}, - }; - - static constexpr uint8_t blockTable32[32] = { - 0, - 1, - 4, - 5, - 16, - 17, - 20, - 21, - 2, - 3, - 6, - 7, - 18, - 19, - 22, - 23, - 8, - 9, - 12, - 13, - 24, - 25, - 28, - 29, - 10, - 11, - 14, - 15, - 26, - 27, - 30, - 31, - }; - - inline const std::array &index32X() - { - static const std::array table = [] - { - std::array result{}; - for (uint8_t i = 0; i < 4; ++i) - { - for (uint8_t j = 0; j < 8; ++j) - { - const uint8_t index = blockTable32[i * 8u + j]; - result[index] = j; - } - } - return result; - }(); - return table; - } - - inline const std::array &index32Y() - { - static const std::array table = [] - { - std::array result{}; - for (uint8_t i = 0; i < 4; ++i) - { - for (uint8_t j = 0; j < 8; ++j) - { - const uint8_t index = blockTable32[i * 8u + j]; - result[index] = i; - } - } - return result; - }(); - return table; - } - - inline const std::array &columnTable8() - { - static const std::array table = [] - { - std::array result{}; - static constexpr uint8_t lut[128] = { - 0, - 36, - 8, - 44, - 1, - 37, - 9, - 45, - 2, - 38, - 10, - 46, - 3, - 39, - 11, - 47, - 4, - 32, - 12, - 40, - 5, - 33, - 13, - 41, - 6, - 34, - 14, - 42, - 7, - 35, - 15, - 43, - 16, - 52, - 24, - 60, - 17, - 53, - 25, - 61, - 18, - 54, - 26, - 62, - 19, - 55, - 27, - 63, - 20, - 48, - 28, - 56, - 21, - 49, - 29, - 57, - 22, - 50, - 30, - 58, - 23, - 51, - 31, - 59, - 4, - 32, - 12, - 40, - 5, - 33, - 13, - 41, - 6, - 34, - 14, - 42, - 7, - 35, - 15, - 43, - 0, - 36, - 8, - 44, - 1, - 37, - 9, - 45, - 2, - 38, - 10, - 46, - 3, - 39, - 11, - 47, - 20, - 48, - 28, - 56, - 21, - 49, - 29, - 57, - 22, - 50, - 30, - 58, - 23, - 51, - 31, - 59, - 16, - 52, - 24, - 60, - 17, - 53, - 25, - 61, - 18, - 54, - 26, - 62, - 19, - 55, - 27, - 63, - }; - - uint32_t outputIndex = 0u; - for (uint32_t k = 0; k < 4u; ++k) - { - uint32_t inputBase = (k % 2u) * 64u; - for (uint32_t i = 0; i < 16u; ++i) - { - for (uint32_t j = 0; j < 4u; ++j) - { - result[k * 64u + lut[inputBase++]] = static_cast(outputIndex++); - } - } - } - - return result; - }(); - return table; - } - - inline uint32_t addrPSMT8(uint32_t block, uint32_t width, uint32_t x, uint32_t y) - { - const uint32_t page = (block >> 5) + (y >> 6) * (width >> 1) + (x >> 7); - const uint32_t blockId = (block & 0x1Fu) + blockTable8[(y >> 4) & 3u][(x >> 4) & 7u]; - const uint32_t pageOffset = (blockId >> 5) << 13; - const uint32_t localBlock = blockId & 0x1Fu; - const uint32_t blockBase = static_cast(index32Y()[localBlock]) * 2048u + - static_cast(index32X()[localBlock]) * 32u; - const uint32_t column = columnTable8()[(y & 0xFu) * 16u + (x & 0xFu)]; - return (page << 13) + pageOffset + blockBase + column; - } - -} - -#endif diff --git a/ps2xRuntime/include/ps2_host_backend.h b/ps2xRuntime/include/ps2_host_backend.h new file mode 100644 index 00000000..54910f8e --- /dev/null +++ b/ps2xRuntime/include/ps2_host_backend.h @@ -0,0 +1,3 @@ +#pragma once + +#include "raylib.h" diff --git a/ps2xRuntime/include/ps2_iop.h b/ps2xRuntime/include/ps2_iop.h deleted file mode 100644 index 4f7aac10..00000000 --- a/ps2xRuntime/include/ps2_iop.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PS2_IOP_H -#define PS2_IOP_H - -#include - -constexpr uint32_t IOP_SID_LIBSD = 0x80000701u; - -class ps2_iop -{ -public: - ps2_iop(); - ~ps2_iop() = default; - - void init(uint8_t *rdram); - void reset(); - - bool handleRPC(uint32_t sid, uint32_t rpcNum, - uint32_t sendBufAddr, uint32_t sendSize, - uint32_t recvBufAddr, uint32_t recvSize); - -private: - uint8_t *m_rdram = nullptr; -}; - -#endif diff --git a/ps2xRuntime/include/ps2_log.h b/ps2xRuntime/include/ps2_log.h index e743e667..ef32c8f7 100644 --- a/ps2xRuntime/include/ps2_log.h +++ b/ps2xRuntime/include/ps2_log.h @@ -4,27 +4,31 @@ #include #include #include -#if defined(_WIN32) -#define NOMINMAX -#include + +#if defined(AGRESSIVE_LOGS) +#define PS2_AGRESSIVE_LOGS_ENABLED 1 +#else +#define PS2_AGRESSIVE_LOGS_ENABLED 0 #endif -#ifdef _DEBUG +#if defined(_DEBUG) +#define RUNTIME_LOG(x) do { std::cout << x; } while (0) +#else +#define RUNTIME_LOG(x) do {} while(0) +#endif + +#ifdef neverDone namespace ps2_log { +inline constexpr bool agressive_logs_enabled = PS2_AGRESSIVE_LOGS_ENABLED != 0; + inline std::string log_path() { static std::string path; if (path.empty()) { -#if defined(_WIN32) - char buf[MAX_PATH]; - if (GetModuleFileNameA(nullptr, buf, sizeof(buf))) - path = (std::filesystem::path(buf).parent_path() / "ps2_log.txt").string(); -#endif - if (path.empty()) - path = (std::filesystem::current_path() / "ps2_log.txt").string(); + path = (std::filesystem::current_path() / "ps2_log.txt").string(); } return path; } @@ -64,14 +68,24 @@ inline void print_saved_location() ps2_log::log_entry(name); \ struct _ps2_log_guard_ { const char *_n; _ps2_log_guard_(const char *n) : _n(n) {} \ ~_ps2_log_guard_() { ps2_log::log_exit(_n); } } _ps2_log_guard_(name) +#define PS2_IF_AGRESSIVE_LOGS(code) \ + do \ + { \ + if constexpr (ps2_log::agressive_logs_enabled) \ + { \ + code; \ + } \ + } while (0) #else namespace ps2_log { +inline constexpr bool agressive_logs_enabled = false; inline void print_saved_location() {} } #define PS_LOG_ENTRY(name) ((void)0) +#define PS2_IF_AGRESSIVE_LOGS(code) ((void)0) #endif diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index a3bba0a2..e40d96b5 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -16,18 +16,20 @@ #include // For SSE4.1 instructions #endif #include +#include #include #include #include #include -#include "ps2_gif_arbiter.h" -#include "ps2_memory.h" -#include "ps2_gs_gpu.h" -#include "ps2_iop.h" -#include "ps2_vu1.h" -#include "ps2_audio.h" -#include "ps2_pad.h" +#include "ps2_log.h" +#include "runtime/ps2_gif_arbiter.h" +#include "runtime/ps2_memory.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_iop.h" +#include "runtime/ps2_vu1.h" +#include "runtime/ps2_audio.h" +#include "runtime/ps2_pad.h" enum PS2Exception { @@ -356,6 +358,78 @@ inline void ps2TraceGuestRangeWrite(uint8_t *rdram, std::cout << std::endl; } +struct PS2SoundDriverCompatLayout +{ + uint32_t primarySeCheckAddr = 0; + uint32_t primaryMidiCheckAddr = 0; + uint32_t fallbackSeCheckAddr = 0; + uint32_t fallbackMidiCheckAddr = 0; + uint32_t busyFlagAddr = 0; + std::array completionCallbacks{}; + std::array clearBusyCallbacks{}; + + [[nodiscard]] bool hasChecksumTables() const + { + return primarySeCheckAddr != 0u || primaryMidiCheckAddr != 0u || + fallbackSeCheckAddr != 0u || fallbackMidiCheckAddr != 0u; + } + + [[nodiscard]] bool matchesCompletionCallback(uint32_t addr) const + { + for (const uint32_t candidate : completionCallbacks) + { + if (candidate != 0u && candidate == addr) + { + return true; + } + } + return false; + } + + [[nodiscard]] bool matchesClearBusyCallback(uint32_t addr) const + { + for (const uint32_t candidate : clearBusyCallbacks) + { + if (candidate != 0u && candidate == addr) + { + return true; + } + } + return false; + } +}; + +struct PS2DtxCompatLayout +{ + uint32_t rpcSid = 0; + uint32_t urpcObjBase = 0; + uint32_t urpcObjLimit = 0; + uint32_t urpcObjStride = 0x20u; + uint32_t urpcFnTableBase = 0; + uint32_t urpcObjTableBase = 0; + uint32_t dispatcherFuncAddr = 0; + + [[nodiscard]] bool isConfigured() const + { + return rpcSid != 0u; + } + + [[nodiscard]] bool hasUrpcObjectRange() const + { + return urpcObjBase != 0u && urpcObjLimit > urpcObjBase && urpcObjStride != 0u; + } + + [[nodiscard]] bool hasUrpcTables() const + { + return urpcFnTableBase != 0u && urpcObjTableBase != 0u; + } + + [[nodiscard]] bool isUrpcRpc(uint32_t sid, uint32_t rpcNum) const + { + return isConfigured() && sid == rpcSid && rpcNum >= 0x400u && rpcNum < 0x500u; + } +}; + class PS2Runtime { public: @@ -373,6 +447,7 @@ class PS2Runtime ~PS2Runtime(); bool initialize(const char *title = "PS2 Game"); + bool syncCoreSubsystems(); bool loadELF(const std::string &elfPath); void run(); @@ -438,7 +513,7 @@ class PS2Runtime uint32_t guestHeapEnd() const; uint32_t reserveAsyncCallbackStack(uint32_t size, uint32_t alignment = 16u); void dispatchLoop(uint8_t *rdram, R5900Context *ctx); - void cooperativeGuestYield(); + bool shouldPreemptGuestExecution(); void requestStop(); bool isStopRequested() const; uint32_t guestExecutionWaiterCountForTesting() const @@ -579,6 +654,8 @@ class PS2Runtime }; std::vector m_loadedModules; + uint8_t *m_boundRdram = nullptr; + uint8_t *m_boundGSVram = nullptr; }; #endif // PS2_RUNTIME_H diff --git a/ps2xRuntime/include/ps2_stubs.h b/ps2xRuntime/include/ps2_stubs.h index b9857e8e..a99becab 100644 --- a/ps2xRuntime/include/ps2_stubs.h +++ b/ps2xRuntime/include/ps2_stubs.h @@ -1,28 +1,44 @@ -#ifndef PS2_STUBS_H -#define PS2_STUBS_H +#pragma once + +struct R5900Context; +class PS2Runtime; -#include "ps2_runtime.h" -#include "ps2_call_list.h" #include +#include "ps2_call_list.h" +#include "runtime/ps2_memory.h" +#include "Stubs/Unimplemented.h" -namespace ps2_stubs +struct PS2MpegCompatLayout { - #define PS2_DECLARE_STUB(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); - PS2_STUB_LIST(PS2_DECLARE_STUB) - #undef PS2_DECLARE_STUB + uint32_t mpegObjectAddr = 0; + uint32_t videoStateAddr = 0; + uint32_t movieStateAddr = 0; + uint32_t syntheticFramesBeforeEnd = 1u; + uint32_t playingVideoStateValue = 0u; + uint32_t playingMovieStateValue = 2u; + uint32_t finishedVideoStateValue = 3u; + uint32_t finishedMovieStateValue = 3u; - #define PS2_DECLARE_TEST_HOOK(name, signature) void name signature; - PS2_TEST_HOOK_LIST(PS2_DECLARE_TEST_HOOK) - #undef PS2_DECLARE_TEST_HOOK - void resetGsSyncVCallbackState(); - void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick); + [[nodiscard]] bool matchesMpegObject(uint32_t addr) const + { + return mpegObjectAddr != 0u && ((addr & PS2_RAM_MASK) == (mpegObjectAddr & PS2_RAM_MASK)); + } - void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); - void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + [[nodiscard]] bool hasFinishTargets() const + { + return videoStateAddr != 0u || movieStateAddr != 0u; + } +}; - void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); - void TODO_NAMED(const char *name, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); -} +namespace ps2_stubs +{ +#define PS2_DECLARE_STUB(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + PS2_STUB_LIST(PS2_DECLARE_STUB) +#undef PS2_DECLARE_STUB -#endif // PS2_STUBS_H + void resetSifState(); + + void setMpegCompatLayout(const PS2MpegCompatLayout &layout); + void clearMpegCompatLayout(); +} diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index 45f036e1..0411a2af 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -13,7 +13,7 @@ std::string translatePs2Path(const char *ps2Path); extern std::atomic g_activeThreads; -static std::mutex g_sys_fd_mutex; +inline std::mutex g_sys_fd_mutex; namespace ps2_syscalls { @@ -21,6 +21,12 @@ namespace ps2_syscalls PS2_SYSCALL_LIST(PS2_DECLARE_SYSCALL) #undef PS2_DECLARE_SYSCALL + void iDeleteSema(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); + bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause); void initializeGuestKernelState(uint8_t *rdram); @@ -28,6 +34,11 @@ namespace ps2_syscalls void notifyRuntimeStop(); void joinAllGuestHostThreads(); void detachAllGuestHostThreads(); + void resetSoundDriverRpcState(); + void setSoundDriverCompatLayout(const PS2SoundDriverCompatLayout &layout); + void clearSoundDriverCompatLayout(); + void setDtxCompatLayout(const PS2DtxCompatLayout &layout); + void clearDtxCompatLayout(); void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime); uint64_t GetCurrentVSyncTick(); uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime); @@ -35,3 +46,4 @@ namespace ps2_syscalls } #endif // PS2_SYSCALLS_H + diff --git a/ps2xRuntime/include/ps2_audio.h b/ps2xRuntime/include/runtime/ps2_audio.h similarity index 96% rename from ps2xRuntime/include/ps2_audio.h rename to ps2xRuntime/include/runtime/ps2_audio.h index dc33a40d..a8c2a555 100644 --- a/ps2xRuntime/include/ps2_audio.h +++ b/ps2xRuntime/include/runtime/ps2_audio.h @@ -37,6 +37,7 @@ class PS2AudioBackend bool m_audioReady = false; uint32_t m_mostRecentSampleKey = 0; std::vector m_loadOrderSamples; + std::vector m_loadOrderSampleKeys; std::unordered_map m_sampleBank; std::mutex m_mutex; diff --git a/ps2xRuntime/include/ps2_gif_arbiter.h b/ps2xRuntime/include/runtime/ps2_gif_arbiter.h similarity index 100% rename from ps2xRuntime/include/ps2_gif_arbiter.h rename to ps2xRuntime/include/runtime/ps2_gif_arbiter.h diff --git a/ps2xRuntime/include/ps2_gs_common.h b/ps2xRuntime/include/runtime/ps2_gs_common.h similarity index 92% rename from ps2xRuntime/include/ps2_gs_common.h rename to ps2xRuntime/include/runtime/ps2_gs_common.h index 5cc8b45a..3dfa8eb1 100644 --- a/ps2xRuntime/include/ps2_gs_common.h +++ b/ps2xRuntime/include/runtime/ps2_gs_common.h @@ -39,6 +39,11 @@ static inline uint32_t fbStride(uint32_t fbw, uint8_t psm) return pixelsPerRow * (bitsPerPixel(psm) / 8u); } +static inline uint32_t framePageBaseToBlock(uint32_t fbp) +{ + return fbp << 5u; +} + static inline int clampInt(int v, int lo, int hi) { if (v < lo) return lo; diff --git a/ps2xRuntime/include/ps2_gs_gpu.h b/ps2xRuntime/include/runtime/ps2_gs_gpu.h similarity index 72% rename from ps2xRuntime/include/ps2_gs_gpu.h rename to ps2xRuntime/include/runtime/ps2_gs_gpu.h index 6eeef871..1549bbee 100644 --- a/ps2xRuntime/include/ps2_gs_gpu.h +++ b/ps2xRuntime/include/runtime/ps2_gs_gpu.h @@ -148,6 +148,20 @@ struct GSXYOffsetReg uint16_t ofy; }; +struct GSTexaReg +{ + uint8_t ta0; + bool aem; + uint8_t ta1; +}; + +struct GSTexClutReg +{ + uint8_t cbw; + uint8_t cou; + uint16_t cov; +}; + struct GSContext { GSFrameReg frame; @@ -220,6 +234,16 @@ class GS { return m_ctx[(index != 0) ? 1 : 0].frame; } + bool getPreferredDisplaySource(GSFrameReg &outSource, uint32_t &outDestFbp) const; + void latchHostPresentationFrame(); + bool copyLatchedHostPresentationFrame(std::vector &outPixels, + uint32_t &outWidth, + uint32_t &outHeight, + uint32_t *outDisplayFbp = nullptr, + uint32_t *outSourceFbp = nullptr, + bool *outUsedPreferred = nullptr) const; + bool clearFramebufferContext(uint32_t contextIndex, uint32_t rgba); + bool clearActiveFramebuffer(uint32_t rgba); uint32_t consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes); @@ -233,12 +257,22 @@ class GS void processImageData(const uint8_t *data, uint32_t sizeBytes); void performLocalToLocalTransfer(); void performLocalToHostToBuffer(); + bool copyFrameToHostRgbaUnlocked(const GSFrameReg &frame, + uint32_t width, + uint32_t height, + std::vector &outPixels, + bool preserveAlpha = false, + bool useLocalMemoryLayout = false, + bool frameBaseIsPages = true, + uint32_t sourceOriginX = 0u, + uint32_t sourceOriginY = 0u) const; GSContext &activeContext(); uint8_t *m_vram = nullptr; uint32_t m_vramSize = 0; struct GSRegisters *m_privRegs = nullptr; + mutable std::recursive_mutex m_stateMutex; GSContext m_ctx[2]; GSPrimReg m_prim{}; @@ -250,6 +284,9 @@ class GS uint8_t m_curFog = 0; bool m_prmodecont = true; + bool m_pabe = false; + GSTexaReg m_texa{0u, false, 0u}; + GSTexClutReg m_texclut{0u, 0u, 0u}; GSBitBltBuf m_bitbltbuf{}; GSTrxPos m_trxpos{}; @@ -266,6 +303,16 @@ class GS std::vector m_displaySnapshot; std::mutex m_snapshotMutex; uint32_t m_lastDisplayBaseBytes = 0; + GSFrameReg m_preferredDisplaySourceFrame{}; + uint32_t m_preferredDisplayDestFbp = 0; + bool m_hasPreferredDisplaySource = false; + std::vector m_hostPresentationFrame; + uint32_t m_hostPresentationWidth = 0; + uint32_t m_hostPresentationHeight = 0; + uint32_t m_hostPresentationDisplayFbp = 0; + uint32_t m_hostPresentationSourceFbp = 0; + bool m_hostPresentationUsedPreferred = false; + bool m_hasHostPresentationFrame = false; std::vector m_localToHostBuffer; size_t m_localToHostReadPos = 0; diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmct16.h b/ps2xRuntime/include/runtime/ps2_gs_psmct16.h new file mode 100644 index 00000000..5a285e4c --- /dev/null +++ b/ps2xRuntime/include/runtime/ps2_gs_psmct16.h @@ -0,0 +1,96 @@ +#ifndef PS2_GS_PSMCT16_H +#define PS2_GS_PSMCT16_H + +#include + +namespace GSPSMCT16 +{ + + static constexpr uint8_t blockTable16[8][4] = { + {0, 2, 8, 10}, + {1, 3, 9, 11}, + {4, 6, 12, 14}, + {5, 7, 13, 15}, + {16, 18, 24, 26}, + {17, 19, 25, 27}, + {20, 22, 28, 30}, + {21, 23, 29, 31}, + }; + + static constexpr uint8_t blockTable16S[8][4] = { + {0, 2, 16, 18}, + {1, 3, 17, 19}, + {8, 10, 24, 26}, + {9, 11, 25, 27}, + {4, 6, 20, 22}, + {5, 7, 21, 23}, + {12, 14, 28, 30}, + {13, 15, 29, 31}, + }; + + static constexpr uint8_t blockTableZ16[8][4] = { + {24, 26, 16, 18}, + {25, 27, 17, 19}, + {28, 30, 20, 22}, + {29, 31, 21, 23}, + {8, 10, 0, 2}, + {9, 11, 1, 3}, + {12, 14, 4, 6}, + {13, 15, 5, 7}, + }; + + static constexpr uint8_t blockTableZ16S[8][4] = { + {24, 26, 8, 10}, + {25, 27, 9, 11}, + {16, 18, 0, 2}, + {17, 19, 1, 3}, + {28, 30, 12, 14}, + {29, 31, 13, 15}, + {20, 22, 4, 6}, + {21, 23, 5, 7}, + }; + + static constexpr uint8_t columnTable16[2][16] = { + {0, 2, 8, 10, 16, 18, 24, 26, 1, 3, 9, 11, 17, 19, 25, 27}, + {4, 6, 12, 14, 20, 22, 28, 30, 5, 7, 13, 15, 21, 23, 29, 31}, + }; + + inline uint32_t addrPSMCT16Like(uint32_t block, + uint32_t width, + uint32_t x, + uint32_t y, + const uint8_t (&blockTable)[8][4]) + { + const uint32_t pagesPerRow = (width != 0u) ? width : 1u; + const uint32_t page = (block >> 5u) + (y >> 6u) * pagesPerRow + (x >> 6u); + const uint32_t blockId = (block & 0x1Fu) + blockTable[(y >> 3u) & 0x7u][(x >> 4u) & 0x3u]; + const uint32_t pageOffset = (blockId >> 5u) << 13u; + const uint32_t localBlock = blockId & 0x1Fu; + const uint32_t columnOffset = ((y >> 1u) & 0x3u) * 64u; + return (page << 13u) + pageOffset + localBlock * 256u + columnOffset + + static_cast(columnTable16[y & 0x1u][x & 0x0Fu]) * 2u; + } + + inline uint32_t addrPSMCT16(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + return addrPSMCT16Like(block, width, x, y, blockTable16); + } + + inline uint32_t addrPSMCT16S(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + return addrPSMCT16Like(block, width, x, y, blockTable16S); + } + + inline uint32_t addrPSMZ16(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + return addrPSMCT16Like(block, width, x, y, blockTableZ16); + } + + inline uint32_t addrPSMZ16S(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + return addrPSMCT16Like(block, width, x, y, blockTableZ16S); + } + +} + +#endif diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmct32.h b/ps2xRuntime/include/runtime/ps2_gs_psmct32.h new file mode 100644 index 00000000..32da4f1c --- /dev/null +++ b/ps2xRuntime/include/runtime/ps2_gs_psmct32.h @@ -0,0 +1,40 @@ +#ifndef PS2_GS_PSMCT32_H +#define PS2_GS_PSMCT32_H + +#include + +namespace GSPSMCT32 +{ + + static constexpr uint8_t blockTable32[4][8] = { + {0, 1, 4, 5, 16, 17, 20, 21}, + {2, 3, 6, 7, 18, 19, 22, 23}, + {8, 9, 12, 13, 24, 25, 28, 29}, + {10, 11, 14, 15, 26, 27, 30, 31}, + }; + + static constexpr uint8_t columnTable32[8][8] = { + {0, 1, 4, 5, 8, 9, 12, 13}, + {2, 3, 6, 7, 10, 11, 14, 15}, + {16, 17, 20, 21, 24, 25, 28, 29}, + {18, 19, 22, 23, 26, 27, 30, 31}, + {32, 33, 36, 37, 40, 41, 44, 45}, + {34, 35, 38, 39, 42, 43, 46, 47}, + {48, 49, 52, 53, 56, 57, 60, 61}, + {50, 51, 54, 55, 58, 59, 62, 63}, + }; + + inline uint32_t addrPSMCT32(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + const uint32_t pagesPerRow = (width != 0u) ? width : 1u; + const uint32_t page = (block >> 5u) + (y >> 5u) * pagesPerRow + (x >> 6u); + const uint32_t blockId = (block & 0x1Fu) + blockTable32[(y >> 3u) & 3u][(x >> 3u) & 7u]; + const uint32_t pageOffset = (blockId >> 5u) << 13u; + const uint32_t localBlock = blockId & 0x1Fu; + return (page << 13u) + pageOffset + localBlock * 256u + + static_cast(columnTable32[y & 0x7u][x & 0x7u]) * 4u; + } + +} + +#endif diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmt4.h b/ps2xRuntime/include/runtime/ps2_gs_psmt4.h new file mode 100644 index 00000000..7fbe64f2 --- /dev/null +++ b/ps2xRuntime/include/runtime/ps2_gs_psmt4.h @@ -0,0 +1,51 @@ +#ifndef PS2_GS_PSMT4_H +#define PS2_GS_PSMT4_H + +#include + +namespace GSPSMT4 +{ + + static constexpr uint8_t blockTable4[8][4] = { + {0, 2, 8, 10}, + {1, 3, 9, 11}, + {4, 6, 12, 14}, + {5, 7, 13, 15}, + {16, 18, 24, 26}, + {17, 19, 25, 27}, + {20, 22, 28, 30}, + {21, 23, 29, 31}, + }; + + static const uint16_t columnTable4[16][32] = { + {0, 8, 32, 40, 64, 72, 96, 104, 2, 10, 34, 42, 66, 74, 98, 106, 4, 12, 36, 44, 68, 76, 100, 108, 6, 14, 38, 46, 70, 78, 102, 110}, + {16, 24, 48, 56, 80, 88, 112, 120, 18, 26, 50, 58, 82, 90, 114, 122, 20, 28, 52, 60, 84, 92, 116, 124, 22, 30, 54, 62, 86, 94, 118, 126}, + {65, 73, 97, 105, 1, 9, 33, 41, 67, 75, 99, 107, 3, 11, 35, 43, 69, 77, 101, 109, 5, 13, 37, 45, 71, 79, 103, 111, 7, 15, 39, 47}, + {81, 89, 113, 121, 17, 25, 49, 57, 83, 91, 115, 123, 19, 27, 51, 59, 85, 93, 117, 125, 21, 29, 53, 61, 87, 95, 119, 127, 23, 31, 55, 63}, + {192, 200, 224, 232, 128, 136, 160, 168, 194, 202, 226, 234, 130, 138, 162, 170, 196, 204, 228, 236, 132, 140, 164, 172, 198, 206, 230, 238, 134, 142, 166, 174}, + {208, 216, 240, 248, 144, 152, 176, 184, 210, 218, 242, 250, 146, 154, 178, 186, 212, 220, 244, 252, 148, 156, 180, 188, 214, 222, 246, 254, 150, 158, 182, 190}, + {129, 137, 161, 169, 193, 201, 225, 233, 131, 139, 163, 171, 195, 203, 227, 235, 133, 141, 165, 173, 197, 205, 229, 237, 135, 143, 167, 175, 199, 207, 231, 239}, + {145, 153, 177, 185, 209, 217, 241, 249, 147, 155, 179, 187, 211, 219, 243, 251, 149, 157, 181, 189, 213, 221, 245, 253, 151, 159, 183, 191, 215, 223, 247, 255}, + {256, 264, 288, 296, 320, 328, 352, 360, 258, 266, 290, 298, 322, 330, 354, 362, 260, 268, 292, 300, 324, 332, 356, 364, 262, 270, 294, 302, 326, 334, 358, 366}, + {272, 280, 304, 312, 336, 344, 368, 376, 274, 282, 306, 314, 338, 346, 370, 378, 276, 284, 308, 316, 340, 348, 372, 380, 278, 286, 310, 318, 342, 350, 374, 382}, + {321, 329, 353, 361, 257, 265, 289, 297, 323, 331, 355, 363, 259, 267, 291, 299, 325, 333, 357, 365, 261, 269, 293, 301, 327, 335, 359, 367, 263, 271, 295, 303}, + {337, 345, 369, 377, 273, 281, 305, 313, 339, 347, 371, 379, 275, 283, 307, 315, 341, 349, 373, 381, 277, 285, 309, 317, 343, 351, 375, 383, 279, 287, 311, 319}, + {448, 456, 480, 488, 384, 392, 416, 424, 450, 458, 482, 490, 386, 394, 418, 426, 452, 460, 484, 492, 388, 396, 420, 428, 454, 462, 486, 494, 390, 398, 422, 430}, + {464, 472, 496, 504, 400, 408, 432, 440, 466, 474, 498, 506, 402, 410, 434, 442, 468, 476, 500, 508, 404, 412, 436, 444, 470, 478, 502, 510, 406, 414, 438, 446}, + {385, 393, 417, 425, 449, 457, 481, 489, 387, 395, 419, 427, 451, 459, 483, 491, 389, 397, 421, 429, 453, 461, 485, 493, 391, 399, 423, 431, 455, 463, 487, 495}, + {401, 409, 433, 441, 465, 473, 497, 505, 403, 411, 435, 443, 467, 475, 499, 507, 405, 413, 437, 445, 469, 477, 501, 509, 407, 415, 439, 447, 471, 479, 503, 511}, + }; + + inline uint32_t addrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u; + const uint32_t page = (block >> 5u) + (y >> 7u) * pagesPerRow + (x >> 7u); + const uint32_t blockId = (block & 0x1Fu) + blockTable4[(y >> 4u) & 7u][(x >> 5u) & 3u]; + const uint32_t pageOffset = (blockId >> 5u) << 14u; + const uint32_t localBlock = blockId & 0x1Fu; + return (page << 14u) + pageOffset + localBlock * 512u + columnTable4[y & 0x0Fu][x & 0x1Fu]; + } + +} + +#endif diff --git a/ps2xRuntime/include/runtime/ps2_gs_psmt8.h b/ps2xRuntime/include/runtime/ps2_gs_psmt8.h new file mode 100644 index 00000000..dd4c8004 --- /dev/null +++ b/ps2xRuntime/include/runtime/ps2_gs_psmt8.h @@ -0,0 +1,47 @@ +#ifndef PS2_GS_PSMT8_H +#define PS2_GS_PSMT8_H + +#include + +namespace GSPSMT8 +{ + + static constexpr uint8_t blockTable8[4][8] = { + {0, 1, 4, 5, 16, 17, 20, 21}, + {2, 3, 6, 7, 18, 19, 22, 23}, + {8, 9, 12, 13, 24, 25, 28, 29}, + {10, 11, 14, 15, 26, 27, 30, 31}, + }; + + static constexpr uint8_t columnTable8[16][16] = { + {0, 4, 16, 20, 32, 36, 48, 52, 2, 6, 18, 22, 34, 38, 50, 54}, + {8, 12, 24, 28, 40, 44, 56, 60, 10, 14, 26, 30, 42, 46, 58, 62}, + {33, 37, 49, 53, 1, 5, 17, 21, 35, 39, 51, 55, 3, 7, 19, 23}, + {41, 45, 57, 61, 9, 13, 25, 29, 43, 47, 59, 63, 11, 15, 27, 31}, + {96, 100, 112, 116, 64, 68, 80, 84, 98, 102, 114, 118, 66, 70, 82, 86}, + {104, 108, 120, 124, 72, 76, 88, 92, 106, 110, 122, 126, 74, 78, 90, 94}, + {65, 69, 81, 85, 97, 101, 113, 117, 67, 71, 83, 87, 99, 103, 115, 119}, + {73, 77, 89, 93, 105, 109, 121, 125, 75, 79, 91, 95, 107, 111, 123, 127}, + {128, 132, 144, 148, 160, 164, 176, 180, 130, 134, 146, 150, 162, 166, 178, 182}, + {136, 140, 152, 156, 168, 172, 184, 188, 138, 142, 154, 158, 170, 174, 186, 190}, + {161, 165, 177, 181, 129, 133, 145, 149, 163, 167, 179, 183, 131, 135, 147, 151}, + {169, 173, 185, 189, 137, 141, 153, 157, 171, 175, 187, 191, 139, 143, 155, 159}, + {224, 228, 240, 244, 192, 196, 208, 212, 226, 230, 242, 246, 194, 198, 210, 214}, + {232, 236, 248, 252, 200, 204, 216, 220, 234, 238, 250, 254, 202, 206, 218, 222}, + {193, 197, 209, 213, 225, 229, 241, 245, 195, 199, 211, 215, 227, 231, 243, 247}, + {201, 205, 217, 221, 233, 237, 249, 253, 203, 207, 219, 223, 235, 239, 251, 255}, + }; + + inline uint32_t addrPSMT8(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u; + const uint32_t page = (block >> 5u) + (y >> 6u) * pagesPerRow + (x >> 7u); + const uint32_t blockId = (block & 0x1Fu) + blockTable8[(y >> 4) & 3u][(x >> 4) & 7u]; + const uint32_t pageOffset = (blockId >> 5u) << 13u; + const uint32_t localBlock = blockId & 0x1Fu; + return (page << 13u) + pageOffset + localBlock * 256u + columnTable8[y & 0x0Fu][x & 0x0Fu]; + } + +} + +#endif diff --git a/ps2xRuntime/include/ps2_gs_rasterizer.h b/ps2xRuntime/include/runtime/ps2_gs_rasterizer.h similarity index 100% rename from ps2xRuntime/include/ps2_gs_rasterizer.h rename to ps2xRuntime/include/runtime/ps2_gs_rasterizer.h diff --git a/ps2xRuntime/include/runtime/ps2_iop.h b/ps2xRuntime/include/runtime/ps2_iop.h new file mode 100644 index 00000000..5f78d3df --- /dev/null +++ b/ps2xRuntime/include/runtime/ps2_iop.h @@ -0,0 +1,36 @@ +#ifndef PS2_IOP_H +#define PS2_IOP_H + +#include + +class PS2Runtime; + +constexpr uint32_t IOP_SID_SNDDRV_COMMAND = 0x00000000u; +constexpr uint32_t IOP_SID_SNDDRV_STATE = 0x00000001u; +constexpr uint32_t IOP_SID_LIBSD = 0x80000701u; + +constexpr uint32_t IOP_RPC_SNDDRV_SUBMIT = 0x00000000u; +constexpr uint32_t IOP_RPC_SNDDRV_GET_STATUS_ADDR = 0x00000012u; +constexpr uint32_t IOP_RPC_SNDDRV_GET_ADDR_TABLE = 0x00000013u; + +class ps2_iop +{ +public: + ps2_iop(); + ~ps2_iop() = default; + + void init(uint8_t *rdram); + void reset(); + + bool handleRPC(PS2Runtime *runtime, + uint32_t sid, uint32_t rpcNum, + uint32_t sendBufAddr, uint32_t sendSize, + uint32_t recvBufAddr, uint32_t recvSize, + uint32_t &resultPtr, + bool &signalNowaitCompletion); + +private: + uint8_t *m_rdram = nullptr; +}; + +#endif diff --git a/ps2xRuntime/include/ps2_iop_audio.h b/ps2xRuntime/include/runtime/ps2_iop_audio.h similarity index 100% rename from ps2xRuntime/include/ps2_iop_audio.h rename to ps2xRuntime/include/runtime/ps2_iop_audio.h diff --git a/ps2xRuntime/include/ps2_memory.h b/ps2xRuntime/include/runtime/ps2_memory.h similarity index 99% rename from ps2xRuntime/include/ps2_memory.h rename to ps2xRuntime/include/runtime/ps2_memory.h index d8cd8141..6dfc4cce 100644 --- a/ps2xRuntime/include/ps2_memory.h +++ b/ps2xRuntime/include/runtime/ps2_memory.h @@ -380,6 +380,8 @@ class PS2Memory uint8_t *m_vu1Code = nullptr; uint8_t *m_vu1Data = nullptr; bool m_path3Masked = false; + uint32_t m_vif1PendingPath2ImageQwc = 0u; + bool m_vif1PendingPath2DirectHl = false; std::vector> m_path3MaskedFifo; struct PendingTransfer diff --git a/ps2xRuntime/include/ps2_pad.h b/ps2xRuntime/include/runtime/ps2_pad.h similarity index 100% rename from ps2xRuntime/include/ps2_pad.h rename to ps2xRuntime/include/runtime/ps2_pad.h diff --git a/ps2xRuntime/include/ps2_vu1.h b/ps2xRuntime/include/runtime/ps2_vu1.h similarity index 100% rename from ps2xRuntime/include/ps2_vu1.h rename to ps2xRuntime/include/runtime/ps2_vu1.h diff --git a/ps2xRuntime/main_example.cpp b/ps2xRuntime/main_example.cpp deleted file mode 100644 index 7b1ce7e1..00000000 --- a/ps2xRuntime/main_example.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "ps2_runtime.h" -#include -#include - -// Example of how to use the PS2 runtime with recompiled code - -// Stub implementation for PS2 syscalls -void syscall(uint8_t *rdram, R5900Context *ctx) -{ - uint32_t syscallNum = ctx->r[4].m128i_u32[0]; - std::cout << "Syscall " << syscallNum << " called" << std::endl; - - switch (syscallNum) - { - case 0x01: // Exit program - std::cout << "Program requested exit with code: " << ctx->r[5].m128i_u32[0] << std::endl; - break; - - case 0x3C: // PutChar - print a character to stdout - std::cout << (char)ctx->r[5].m128i_u32[0]; - break; - - case 0x3D: // PutString - print a string to stdout - { - uint32_t strAddr = ctx->r[5].m128i_u32[0]; - if (strAddr == 0) - { - std::cout << "(null)"; - } - else - { - uint32_t physAddr = strAddr & 0x1FFFFFFF; - const char *str = reinterpret_cast(rdram + physAddr); - std::cout << str; - } - } - break; - - default: - std::cout << "Unhandled syscall: " << syscallNum << std::endl; - break; - } -} - -// Example implementation of FlushCache -void FlushCache(uint8_t *rdram, R5900Context *ctx) -{ - uint32_t cacheType = ctx->r[4].m128i_u32[0]; - std::cout << "FlushCache called with type: " << cacheType << std::endl; -} - -// Example implementation of a recompiled function -void recompiled_main(uint8_t *rdram, R5900Context *ctx) -{ - std::cout << "Running recompiled main function" << std::endl; - - // Example of memory access - uint32_t addr = 0x100000; // Some address in memory - uint32_t physAddr = addr & 0x1FFFFFFF; - uint32_t value = *reinterpret_cast(rdram + physAddr); - std::cout << "Value at 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl; - - // Example of register manipulation - ctx->r[2] = _mm_set1_epi32(0x12345678); // Set register v0 - ctx->r[4] = _mm_set1_epi32(0x3D); // Set register a0 for syscall (PutString) - ctx->r[5] = _mm_set1_epi32(0x10000); // Set register a1 with string address - - // Call a "syscall" function - syscall(rdram, ctx); - - // Example of returning a value - ctx->r[2] = _mm_set1_epi32(0); // Return 0 (success) -} - -int main(int argc, char *argv[]) -{ - if (argc < 2) - { - std::cout << "Usage: " << argv[0] << " " << std::endl; - return 1; - } - - std::string elfPath = argv[1]; - - PS2Runtime runtime; - if (!runtime.initialize()) - { - std::cerr << "Failed to initialize PS2 runtime" << std::endl; - return 1; - } - - // Register built-in functions - runtime.registerFunction(0x00000001, syscall); - runtime.registerFunction(0x00000002, FlushCache); - runtime.registerFunction(0x00100000, recompiled_main); // Example address for main - - // Load the ELF file - if (!runtime.loadELF(elfPath)) - { - std::cerr << "Failed to load ELF file: " << elfPath << std::endl; - return 1; - } - - // Run the program - runtime.run(); - - return 0; -} \ No newline at end of file diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Audio.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/Audio.cpp new file mode 100644 index 00000000..19f55b2c --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Audio.cpp @@ -0,0 +1,549 @@ +#include "Common.h" +#include "Audio.h" + +namespace ps2_stubs +{ + namespace + { + constexpr uint32_t kLibSdCmdSetParam = 0x8010u; + constexpr uint32_t kLibSdCmdBlockTrans = 0x80D0u; + constexpr uint32_t kLibSdCmdBlockTransAlt = 0x80E0u; + constexpr uint32_t kAudioPositionMask = 0x00FFFFFFu; + + struct AudioStubState + { + bool initialized = false; + uint32_t currentBlockBase = 0u; + uint32_t currentBlockSize = 0u; + uint32_t currentPauseBase = 0u; + }; + + std::mutex g_audio_stub_mutex; + AudioStubState g_audio_stub_state; + + void resetAudioStubStateUnlocked() + { + g_audio_stub_state = {}; + } + } + + void resetAudioStubState() + { + std::lock_guard lock(g_audio_stub_mutex); + resetAudioStubStateUnlocked(); + } + + void sceSdCallBack(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSdCallBack", rdram, ctx, runtime); + } + + void sceSdRemote(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + + const uint32_t cmd = getRegU32(ctx, 5); + const uint32_t cmdArg0 = getRegU32(ctx, 6); + const uint32_t cmdArg1 = getRegU32(ctx, 7); + const uint32_t sp = getRegU32(ctx, 29); + const uint32_t arg4Reg = getRegU32(ctx, 8); + const uint32_t arg5Reg = getRegU32(ctx, 9); + const uint32_t arg6Reg = getRegU32(ctx, 10); + const uint32_t arg4Stk = FAST_READ32(sp + 0x10u); + const uint32_t arg5Stk = FAST_READ32(sp + 0x14u); + const uint32_t arg6Stk = FAST_READ32(sp + 0x18u); + const uint32_t arg4 = (arg4Reg != 0u) ? arg4Reg : arg4Stk; + const uint32_t arg5 = (arg5Reg != 0u) ? arg5Reg : arg5Stk; + const uint32_t arg6 = (arg6Reg != 0u) ? arg6Reg : arg6Stk; + + std::lock_guard lock(g_audio_stub_mutex); + g_audio_stub_state.initialized = true; + + if (cmd == kLibSdCmdBlockTrans || cmd == kLibSdCmdBlockTransAlt) + { + if (arg4 != 0u) + { + g_audio_stub_state.currentBlockBase = arg4 & kAudioPositionMask; + } + if (arg5 != 0u) + { + g_audio_stub_state.currentBlockSize = arg5; + } + if (arg6 != 0u) + { + g_audio_stub_state.currentPauseBase = arg6 & kAudioPositionMask; + } + } + else if (cmd == kLibSdCmdSetParam) + { + (void)cmdArg0; + (void)cmdArg1; + } + + // Some games only sample the low 24 bits of the reported SPU transfer head. + // Returning the last configured transfer base keeps the ring-buffer math + // stable without emulating SPU DMA progress. + setReturnU32(ctx, g_audio_stub_state.currentBlockBase & kAudioPositionMask); + } + + void sceSdRemoteInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + std::lock_guard lock(g_audio_stub_mutex); + resetAudioStubStateUnlocked(); + g_audio_stub_state.initialized = true; + setReturnS32(ctx, 0); + } + + void sceSdTransToIOP(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSdTransToIOP", rdram, ctx, runtime); + } + + void sceSSyn_BreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_BreakAtick", rdram, ctx, runtime); + } + + void sceSSyn_ClearBreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_ClearBreakAtick", rdram, ctx, runtime); + } + + void sceSSyn_SendExcMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SendExcMsg", rdram, ctx, runtime); + } + + void sceSSyn_SendNrpnMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SendNrpnMsg", rdram, ctx, runtime); + } + + void sceSSyn_SendRpnMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SendRpnMsg", rdram, ctx, runtime); + } + + void sceSSyn_SendShortMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SendShortMsg", rdram, ctx, runtime); + } + + void sceSSyn_SetChPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetChPriority", rdram, ctx, runtime); + } + + void sceSSyn_SetMasterVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetMasterVolume", rdram, ctx, runtime); + } + + void sceSSyn_SetOutPortVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetOutPortVolume", rdram, ctx, runtime); + } + + void sceSSyn_SetOutputAssign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetOutputAssign", rdram, ctx, runtime); + } + + void sceSSyn_SetOutputMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSSyn_SetPortMaxPoly(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetPortMaxPoly", rdram, ctx, runtime); + } + + void sceSSyn_SetPortVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetPortVolume", rdram, ctx, runtime); + } + + void sceSSyn_SetTvaEnvMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSSyn_SetTvaEnvMode", rdram, ctx, runtime); + } + + void sceSynthesizerAmpProcI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAmpProcI", rdram, ctx, runtime); + } + + void sceSynthesizerAmpProcNI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAmpProcNI", rdram, ctx, runtime); + } + + void sceSynthesizerAssignAllNoteOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAssignAllNoteOff", rdram, ctx, runtime); + } + + void sceSynthesizerAssignAllSoundOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAssignAllSoundOff", rdram, ctx, runtime); + } + + void sceSynthesizerAssignHoldChange(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAssignHoldChange", rdram, ctx, runtime); + } + + void sceSynthesizerAssignNoteOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAssignNoteOff", rdram, ctx, runtime); + } + + void sceSynthesizerAssignNoteOn(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerAssignNoteOn", rdram, ctx, runtime); + } + + void sceSynthesizerCalcEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerCalcEnv", rdram, ctx, runtime); + } + + void sceSynthesizerCalcPortamentPitch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerCalcPortamentPitch", rdram, ctx, runtime); + } + + void sceSynthesizerCalcTvfCoefAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerCalcTvfCoefAll", rdram, ctx, runtime); + } + + void sceSynthesizerCalcTvfCoefF0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerCalcTvfCoefF0", rdram, ctx, runtime); + } + + void sceSynthesizerCent2PhaseInc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerCent2PhaseInc", rdram, ctx, runtime); + } + + void sceSynthesizerChangeEffectSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeEffectSend", rdram, ctx, runtime); + } + + void sceSynthesizerChangeHsPanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeHsPanpot", rdram, ctx, runtime); + } + + void sceSynthesizerChangeNrpnCutOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeNrpnCutOff", rdram, ctx, runtime); + } + + void sceSynthesizerChangeNrpnLfoDepth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeNrpnLfoDepth", rdram, ctx, runtime); + } + + void sceSynthesizerChangeNrpnLfoRate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeNrpnLfoRate", rdram, ctx, runtime); + } + + void sceSynthesizerChangeOutAttrib(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeOutAttrib", rdram, ctx, runtime); + } + + void sceSynthesizerChangeOutVol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangeOutVol", rdram, ctx, runtime); + } + + void sceSynthesizerChangePanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePanpot", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartBendSens(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartBendSens", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartExpression(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartExpression", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartHsExpression(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartHsExpression", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartHsPitchBend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartHsPitchBend", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartModuration(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartModuration", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartPitchBend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartPitchBend", rdram, ctx, runtime); + } + + void sceSynthesizerChangePartVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePartVolume", rdram, ctx, runtime); + } + + void sceSynthesizerChangePortamento(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePortamento", rdram, ctx, runtime); + } + + void sceSynthesizerChangePortamentoTime(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerChangePortamentoTime", rdram, ctx, runtime); + } + + void sceSynthesizerClearKeyMap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerClearKeyMap", rdram, ctx, runtime); + } + + void sceSynthesizerClearSpr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerClearSpr", rdram, ctx, runtime); + } + + void sceSynthesizerCopyOutput(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerCopyOutput", rdram, ctx, runtime); + } + + void sceSynthesizerDmaFromSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerDmaFromSPR", rdram, ctx, runtime); + } + + void sceSynthesizerDmaSpr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerDmaSpr", rdram, ctx, runtime); + } + + void sceSynthesizerDmaToSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerDmaToSPR", rdram, ctx, runtime); + } + + void sceSynthesizerGetPartial(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerGetPartial", rdram, ctx, runtime); + } + + void sceSynthesizerGetPartOutLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerGetPartOutLevel", rdram, ctx, runtime); + } + + void sceSynthesizerGetSampleParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerGetSampleParam", rdram, ctx, runtime); + } + + void sceSynthesizerHsMessage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerHsMessage", rdram, ctx, runtime); + } + + void sceSynthesizerLfoNone(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerLfoNone", rdram, ctx, runtime); + } + + void sceSynthesizerLfoProc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerLfoProc", rdram, ctx, runtime); + } + + void sceSynthesizerLfoSawDown(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerLfoSawDown", rdram, ctx, runtime); + } + + void sceSynthesizerLfoSawUp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerLfoSawUp", rdram, ctx, runtime); + } + + void sceSynthesizerLfoSquare(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerLfoSquare", rdram, ctx, runtime); + } + + void sceSynthesizerReadNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerReadNoise", rdram, ctx, runtime); + } + + void sceSynthesizerReadNoiseAdd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerReadNoiseAdd", rdram, ctx, runtime); + } + + void sceSynthesizerReadSample16(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerReadSample16", rdram, ctx, runtime); + } + + void sceSynthesizerReadSample16Add(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerReadSample16Add", rdram, ctx, runtime); + } + + void sceSynthesizerReadSample8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerReadSample8", rdram, ctx, runtime); + } + + void sceSynthesizerReadSample8Add(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerReadSample8Add", rdram, ctx, runtime); + } + + void sceSynthesizerResetPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerResetPart", rdram, ctx, runtime); + } + + void sceSynthesizerRestorDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerRestorDma", rdram, ctx, runtime); + } + + void sceSynthesizerSelectPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSelectPatch", rdram, ctx, runtime); + } + + void sceSynthesizerSendShortMessage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSendShortMessage", rdram, ctx, runtime); + } + + void sceSynthesizerSetMasterVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetMasterVolume", rdram, ctx, runtime); + } + + void sceSynthesizerSetRVoice(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetRVoice", rdram, ctx, runtime); + } + + void sceSynthesizerSetupDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupDma", rdram, ctx, runtime); + } + + void sceSynthesizerSetupLfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupLfo", rdram, ctx, runtime); + } + + void sceSynthesizerSetupMidiModuration(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupMidiModuration", rdram, ctx, runtime); + } + + void sceSynthesizerSetupMidiPanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupMidiPanpot", rdram, ctx, runtime); + } + + void sceSynthesizerSetupNewNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupNewNoise", rdram, ctx, runtime); + } + + void sceSynthesizerSetupReleaseEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupReleaseEnv", rdram, ctx, runtime); + } + + void sceSynthesizerSetuptEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetuptEnv", rdram, ctx, runtime); + } + + void sceSynthesizerSetupTruncateTvaEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupTruncateTvaEnv", rdram, ctx, runtime); + } + + void sceSynthesizerSetupTruncateTvfPitchEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerSetupTruncateTvfPitchEnv", rdram, ctx, runtime); + } + + void sceSynthesizerTonegenerator(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerTonegenerator", rdram, ctx, runtime); + } + + void sceSynthesizerTransposeMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerTransposeMatrix", rdram, ctx, runtime); + } + + void sceSynthesizerTvfProcI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerTvfProcI", rdram, ctx, runtime); + } + + void sceSynthesizerTvfProcNI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerTvfProcNI", rdram, ctx, runtime); + } + + void sceSynthesizerWaitDmaFromSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerWaitDmaFromSPR", rdram, ctx, runtime); + } + + void sceSynthesizerWaitDmaToSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthesizerWaitDmaToSPR", rdram, ctx, runtime); + } + + void sceSynthsizerGetDrumPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthsizerGetDrumPatch", rdram, ctx, runtime); + } + + void sceSynthsizerGetMeloPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthsizerGetMeloPatch", rdram, ctx, runtime); + } + + void sceSynthsizerLfoNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthsizerLfoNoise", rdram, ctx, runtime); + } + + void sceSynthSizerLfoTriangle(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSynthSizerLfoTriangle", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Audio.h b/ps2xRuntime/src/lib/Kernel/Stubs/Audio.h new file mode 100644 index 00000000..b450e446 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Audio.h @@ -0,0 +1,101 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void resetAudioStubState(); + void sceSdCallBack(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSdRemote(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSdRemoteInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSdTransToIOP(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_BreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_ClearBreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SendExcMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SendNrpnMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SendRpnMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SendShortMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetChPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetMasterVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetOutPortVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetOutputAssign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetOutputMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetPortMaxPoly(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetPortVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSSyn_SetTvaEnvMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAmpProcI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAmpProcNI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAssignAllNoteOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAssignAllSoundOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAssignHoldChange(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAssignNoteOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerAssignNoteOn(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerCalcEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerCalcPortamentPitch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerCalcTvfCoefAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerCalcTvfCoefF0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerCent2PhaseInc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeEffectSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeHsPanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeNrpnCutOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeNrpnLfoDepth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeNrpnLfoRate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeOutAttrib(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangeOutVol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartBendSens(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartExpression(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartHsExpression(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartHsPitchBend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartModuration(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartPitchBend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePartVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePortamento(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerChangePortamentoTime(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerClearKeyMap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerClearSpr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerCopyOutput(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerDmaFromSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerDmaSpr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerDmaToSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerGetPartial(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerGetPartOutLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerGetSampleParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerHsMessage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerLfoNone(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerLfoProc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerLfoSawDown(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerLfoSawUp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerLfoSquare(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerReadNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerReadNoiseAdd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerReadSample16(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerReadSample16Add(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerReadSample8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerReadSample8Add(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerResetPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerRestorDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSelectPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSendShortMessage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetMasterVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetRVoice(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupLfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupMidiModuration(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupMidiPanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupNewNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupReleaseEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetuptEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupTruncateTvaEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerSetupTruncateTvfPitchEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerTonegenerator(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerTransposeMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerTvfProcI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerTvfProcNI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerWaitDmaFromSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthesizerWaitDmaToSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthsizerGetDrumPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthsizerGetMeloPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthsizerLfoNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSynthSizerLfoTriangle(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/CD.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/CD.cpp new file mode 100644 index 00000000..286886f3 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/CD.cpp @@ -0,0 +1,559 @@ +#include "Common.h" +#include "CD.h" + +namespace ps2_stubs +{ + void sceCdRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t a0 = getRegU32(ctx, 4); // usually lbn + const uint32_t a1 = getRegU32(ctx, 5); // usually sector count + const uint32_t a2 = getRegU32(ctx, 6); // usually destination buffer + + struct CdReadArgs + { + uint32_t lbn = 0; + uint32_t sectors = 0; + uint32_t buf = 0; + const char *tag = ""; + }; + + auto clampReadBytes = [](uint32_t sectors, uint32_t offset) -> size_t + { + const uint64_t requested = static_cast(sectors) * static_cast(kCdSectorSize); + if (requested == 0) + { + return 0; + } + + const uint64_t maxBytes = static_cast(PS2_RAM_SIZE - offset); + const uint64_t clamped = std::min(requested, maxBytes); + return static_cast(clamped); + }; + + auto tryRead = [&](const CdReadArgs &args) -> bool + { + const uint32_t offset = args.buf & PS2_RAM_MASK; + const size_t bytes = clampReadBytes(args.sectors, offset); + if (bytes == 0) + { + return true; + } + + return readCdSectors(args.lbn, args.sectors, rdram + offset, bytes); + }; + + CdReadArgs selected{a0, a1, a2, "a0/a1/a2"}; + bool ok = tryRead(selected); + + if (!ok) + { + // Some game-side wrappers use a nonstandard register layout. + // If primary decode does not resolve to a known LBN, try safe alternatives. + constexpr uint32_t kMaxReasonableSectors = PS2_RAM_SIZE / kCdSectorSize; + if (!isResolvableCdLbn(selected.lbn)) + { + const std::array alternatives = { + CdReadArgs{a2, a1, a0, "a2/a1/a0"}, + CdReadArgs{a0, a2, a1, "a0/a2/a1"}, + CdReadArgs{a1, a0, a2, "a1/a0/a2"}, + CdReadArgs{a1, a2, a0, "a1/a2/a0"}, + CdReadArgs{a2, a0, a1, "a2/a0/a1"}}; + + for (const CdReadArgs &candidate : alternatives) + { + if (candidate.sectors > kMaxReasonableSectors) + { + continue; + } + if (!isResolvableCdLbn(candidate.lbn)) + { + continue; + } + + if (tryRead(candidate)) + { + static uint32_t recoverLogCount = 0; + if (recoverLogCount < 16) + { + RUNTIME_LOG("[sceCdRead] recovered with alternate args " << candidate.tag + << " (pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << " a0=0x" << a0 + << " a1=0x" << a1 + << " a2=0x" << a2 << std::dec << ")" << std::endl); + ++recoverLogCount; + } + selected = candidate; + ok = true; + break; + } + } + } + + if (!ok) + { + const uint32_t offset = a2 & PS2_RAM_MASK; + const size_t bytes = clampReadBytes(a1, offset); + if (bytes > 0) + { + std::memset(rdram + offset, 0, bytes); + } + + static uint32_t unresolvedLogCount = 0; + if (unresolvedLogCount < 32) + { + std::cerr << "[sceCdRead] unresolved request pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << " a0=0x" << a0 + << " a1=0x" << a1 + << " a2=0x" << a2 << std::dec << std::endl; + ++unresolvedLogCount; + } + } + } + + if (ok) + { + g_cdStreamingLbn = selected.lbn + selected.sectors; + setReturnS32(ctx, 1); // command accepted/success + return; + } + + setReturnS32(ctx, 0); + } + + void sceCdSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); // 0 = completed/not busy + } + + void sceCdGetError(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, g_lastCdError); + } + + void sceCdRI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceCdRI", rdram, ctx, runtime); + } + + void sceCdRM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceCdRM", rdram, ctx, runtime); + } + + void sceCdApplyNCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdBreak(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceCdChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdDelayThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceCdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 2); + } + + void sceCdGetDiskType(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + // SCECdPS2DVD + setReturnS32(ctx, 0x14); + } + + void sceCdGetReadPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, g_cdStreamingLbn); + } + + void sceCdGetToc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + g_cdInitialized = true; + g_lastCdError = 0; + setReturnS32(ctx, 1); + } + + void sceCdInitEeCB(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdIntToPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + g_cdMode = getRegU32(ctx, 4); + setReturnS32(ctx, 1); + } + + void sceCdNcmdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 2); + } + + void sceCdPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdPosToInt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + 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) + { + 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) + { + sceCdRead(rdram, ctx, runtime); + } + + void sceCdSearchFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + RUNTIME_LOG("[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; + + 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 (!writeCdSearchResult(rdram, fileAddr, path, resolvedEntry)) + { + g_lastCdError = -1; + setReturnS32(ctx, 0); + return; + } + + g_cdStreamingLbn = resolvedEntry.baseLbn; + if (shouldTrace) + { + RUNTIME_LOG("[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) + { + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); + } + + void sceCdStandby(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, g_cdInitialized ? 6 : 0); + } + + void sceCdStInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdStPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdStRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + setReturnS32(ctx, 1); + } + + void sceCdStResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdStSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); + } + + void sceCdStSeekF(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); + } + + void sceCdStStart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + g_cdStreamingLbn = getRegU32(ctx, 4); + setReturnS32(ctx, 1); + } + + void sceCdStStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceCdStStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceCdSyncS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceCdTrayReq(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t statusPtr = getRegU32(ctx, 5); + if (uint32_t *status = reinterpret_cast(getMemPtr(rdram, statusPtr)); status) + { + *status = 0; + } + setReturnS32(ctx, 1); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/CD.h b/ps2xRuntime/src/lib/Kernel/Stubs/CD.h new file mode 100644 index 00000000..9bf87603 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/CD.h @@ -0,0 +1,48 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceCdRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdGetError(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdRI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdRM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdApplyNCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdBreak(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdDelayThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdGetDiskType(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdGetReadPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdGetToc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdInitEeCB(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdIntToPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdMmode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdNcmdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdPosToInt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdReadChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdReadClock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdReadIOPm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdSearchFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStandby(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStream(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStSeekF(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStStart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdStStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdSyncS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceCdTrayReq(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Common.h b/ps2xRuntime/src/lib/Kernel/Stubs/Common.h new file mode 100644 index 00000000..59045a1c --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Common.h @@ -0,0 +1,29 @@ +#pragma once + +#include "ps2_stubs.h" +#include "ps2_runtime.h" +#include "ps2_runtime_macros.h" +#include "ps2_syscalls.h" +#include "Unimplemented.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ps2_host_backend.h" +#include "Helpers/Support.h" diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Compatibility.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/Compatibility.cpp new file mode 100644 index 00000000..f548b640 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Compatibility.cpp @@ -0,0 +1,123 @@ +#include "Common.h" +#include "Compatibility.h" +#include "ps2_log.h" + +namespace ps2_stubs +{ + void calloc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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 ret0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, 0u); + ctx->pc = getRegU32(ctx, 31); + } + + void ret1(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, 1u); + ctx->pc = getRegU32(ctx, 31); + } + + void reta0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, getRegU32(ctx, 4)); + ctx->pc = getRegU32(ctx, 31); + } + + void free_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + 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) + { + setReturnS32(ctx, 0); + } + + void mbtowc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t wcAddr = getRegU32(ctx, 5); // $a1 + const uint32_t strAddr = getRegU32(ctx, 6); // $a2 + const int32_t n = static_cast(getRegU32(ctx, 7)); // $a3 + if (n <= 0 || strAddr == 0u) + { + setReturnS32(ctx, 0); + return; + } + + const uint8_t *src = getConstMemPtr(rdram, strAddr); + if (!src) + { + setReturnS32(ctx, -1); + return; + } + + const uint8_t ch = *src; + if (wcAddr != 0u) + { + if (uint8_t *dst = getMemPtr(rdram, wcAddr)) + { + const uint32_t out = static_cast(ch); + std::memcpy(dst, &out, sizeof(out)); + } + } + setReturnS32(ctx, (ch == 0u) ? 0 : 1); + } + + void printf_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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); + } + PS2_IF_AGRESSIVE_LOGS({ + const std::string logLine = sanitizeForLog(rendered); + uint32_t count = 0; + { + std::lock_guard lock(g_printfLogMutex); + count = ++g_printfLogCount; + } + if (count <= kMaxPrintfLogs) + { + RUNTIME_LOG("PS2 printf: " << logLine); + RUNTIME_LOG(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); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Compatibility.h b/ps2xRuntime/src/lib/Kernel/Stubs/Compatibility.h new file mode 100644 index 00000000..2159c441 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Compatibility.h @@ -0,0 +1,16 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void calloc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ret0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ret1(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void reta0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void free_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void malloc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void malloc_trim_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mbtowc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void printf_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/DMA.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/DMA.cpp new file mode 100644 index 00000000..b32e44a2 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/DMA.cpp @@ -0,0 +1,236 @@ +#include "Common.h" +#include "DMA.h" + +namespace ps2_stubs +{ + namespace + { + struct SceDmaEnv + { + uint8_t sts = 0; + uint8_t std = 0; + uint8_t mfd = 0; + uint8_t rele = 0; + uint32_t pcr = 0; + uint32_t sqwc = 0; + uint32_t rbor = 0; + uint32_t rbsr = 0; + }; + + static_assert(sizeof(SceDmaEnv) == 0x14, "sceDmaEnv must match the guest ABI"); + + constexpr uint32_t DMA_REG_CTRL = 0x1000E000u; + constexpr uint32_t DMA_REG_PCR = 0x1000E020u; + constexpr uint32_t DMA_REG_SQWC = 0x1000E030u; + constexpr uint32_t DMA_REG_RBSR = 0x1000E040u; + constexpr uint32_t DMA_REG_RBOR = 0x1000E050u; + constexpr uint32_t DMA_REG_STADR = 0x1000E060u; + + constexpr std::array kStsTable = {0u, 0u, 0u, 3u, 0u, 1u, 0u, 0u, 2u, 0u}; + constexpr std::array kStdTable = {0u, 1u, 2u, 0u, 0u, 0u, 3u, 0u, 0u, 0u}; + constexpr std::array kMfdTable = {0u, 2u, 3u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}; + + std::mutex g_dmaEnvMutex; + SceDmaEnv g_dmaCurrentEnv; + } + + void DmaAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, getRegU32(ctx, 4)); + } + + void sceDmaCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaCallback", rdram, ctx, runtime); + } + + void sceDmaDebug(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaDebug", rdram, ctx, runtime); + } + + void sceDmaGetChan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + const uint32_t envAddr = getRegU32(ctx, 4); + if (uint8_t *dst = getMemPtr(rdram, envAddr)) + { + std::lock_guard lock(g_dmaEnvMutex); + std::memcpy(dst, &g_dmaCurrentEnv, sizeof(g_dmaCurrentEnv)); + } + setReturnU32(ctx, envAddr); + } + + void sceDmaLastSyncTime(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaLastSyncTime", rdram, ctx, runtime); + } + + void sceDmaPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaPause", rdram, ctx, runtime); + } + + void sceDmaPutEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t envAddr = getRegU32(ctx, 4); + const uint8_t *src = getConstMemPtr(rdram, envAddr); + if (!src || !runtime) + { + setReturnS32(ctx, -1); + return; + } + + SceDmaEnv env{}; + std::memcpy(&env, src, sizeof(env)); + + if (env.sts >= kStsTable.size()) + { + setReturnS32(ctx, -1); + return; + } + if (env.std >= kStdTable.size()) + { + setReturnS32(ctx, -2); + return; + } + if (env.mfd >= kMfdTable.size()) + { + setReturnS32(ctx, -3); + return; + } + if (env.rele >= 7u) + { + setReturnS32(ctx, -4); + return; + } + + PS2Memory &mem = runtime->memory(); + uint32_t ctrl = mem.readIORegister(DMA_REG_CTRL); + ctrl = (ctrl & 0xFFFFFFCFu) | (static_cast(kStsTable[env.sts]) << 4); + ctrl = (ctrl & 0xFFFFFF3Fu) | (static_cast(kStdTable[env.std]) << 6); + ctrl = (ctrl & 0xFFFFFFF3u) | (static_cast(kMfdTable[env.mfd]) << 2); + if (env.rele == 0u) + { + ctrl &= 0xFFFFFFFDu; + } + else + { + ctrl = ((ctrl | 0x2u) & 0xFFFFFCFFu) | ((static_cast(env.rele - 1u) & 0x7u) << 8); + } + + mem.writeIORegister(DMA_REG_CTRL, ctrl); + mem.writeIORegister(DMA_REG_PCR, env.pcr); + mem.writeIORegister(DMA_REG_SQWC, env.sqwc); + mem.writeIORegister(DMA_REG_RBOR, env.rbor); + mem.writeIORegister(DMA_REG_RBSR, env.rbsr); + + { + std::lock_guard lock(g_dmaEnvMutex); + g_dmaCurrentEnv = env; + } + + setReturnS32(ctx, 0); + } + + void sceDmaPutStallAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t newAddr = getRegU32(ctx, 4); + uint32_t oldAddr = 0; + if (runtime) + { + PS2Memory &mem = runtime->memory(); + oldAddr = mem.readIORegister(DMA_REG_STADR); + if (newAddr != 0xFFFFFFFFu) + { + mem.writeIORegister(DMA_REG_STADR, newAddr); + } + } + setReturnU32(ctx, oldAddr); + } + + void sceDmaRecv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaRecv", rdram, ctx, runtime); + } + + void sceDmaRecvI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaRecvI", rdram, ctx, runtime); + } + + void sceDmaRecvN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaRecvN", rdram, ctx, runtime); + } + + void sceDmaReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + if (runtime) + { + PS2Memory &mem = runtime->memory(); + + // libdma reset leaves the controller runnable; DMAE must be re-enabled or chain submissions will be accepted but never execute. + mem.writeIORegister(DMA_REG_CTRL, 0u); + mem.writeIORegister(DMA_REG_PCR, 0u); + mem.writeIORegister(DMA_REG_SQWC, 0u); + mem.writeIORegister(DMA_REG_RBOR, 0u); + mem.writeIORegister(DMA_REG_RBSR, 0u); + mem.writeIORegister(DMA_REG_STADR, 0u); + mem.writeIORegister(DMA_REG_CTRL, 1u); + } + + { + std::lock_guard lock(g_dmaEnvMutex); + g_dmaCurrentEnv = {}; + } + + setReturnS32(ctx, 0); + } + + void sceDmaRestart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaRestart", rdram, ctx, runtime); + } + + void sceDmaSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); + } + + void sceDmaSendI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); + } + + void sceDmaSendM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); + } + + void sceDmaSendN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, true)); + } + + void sceDmaSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, submitDmaSync(rdram, ctx, runtime)); + } + + void sceDmaSyncN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, submitDmaSync(rdram, ctx, runtime)); + } + + void sceDmaWatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDmaWatch", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/DMA.h b/ps2xRuntime/src/lib/Kernel/Stubs/DMA.h new file mode 100644 index 00000000..fdbcfa53 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/DMA.h @@ -0,0 +1,28 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void DmaAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaDebug(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaGetChan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaGetEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaLastSyncTime(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaPutEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaPutStallAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaRecv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaRecvI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaRecvN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaRestart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaSendI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaSendM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaSendN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaSyncN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDmaWatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Deci2.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/Deci2.cpp new file mode 100644 index 00000000..1309d3cf --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Deci2.cpp @@ -0,0 +1,50 @@ +#include "Common.h" +#include "Deci2.h" + +namespace ps2_stubs +{ + void sceDeci2Close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2Close", rdram, ctx, runtime); + } + + void sceDeci2ExLock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2ExLock", rdram, ctx, runtime); + } + + void sceDeci2ExRecv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2ExRecv", rdram, ctx, runtime); + } + + void sceDeci2ExReqSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2ExReqSend", rdram, ctx, runtime); + } + + void sceDeci2ExSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2ExSend", rdram, ctx, runtime); + } + + void sceDeci2ExUnLock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2ExUnLock", rdram, ctx, runtime); + } + + void sceDeci2Open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2Open", rdram, ctx, runtime); + } + + void sceDeci2Poll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2Poll", rdram, ctx, runtime); + } + + void sceDeci2ReqSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceDeci2ReqSend", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Deci2.h b/ps2xRuntime/src/lib/Kernel/Stubs/Deci2.h new file mode 100644 index 00000000..cf6ec07d --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Deci2.h @@ -0,0 +1,16 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceDeci2Close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2ExLock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2ExRecv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2ExReqSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2ExSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2ExUnLock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2Open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2Poll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDeci2ReqSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/FileIO.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/FileIO.cpp new file mode 100644 index 00000000..b1083af7 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/FileIO.cpp @@ -0,0 +1,151 @@ +#include "Common.h" +#include "FileIO.h" + +namespace ps2_stubs +{ + void sceFsDbChk(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceFsDbChk", rdram, ctx, runtime); + } + + void sceFsIntrSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceFsIntrSigSema", rdram, ctx, runtime); + } + + void sceFsSemExit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceFsSemExit", rdram, ctx, runtime); + } + + void sceFsSemInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceFsSemInit", rdram, ctx, runtime); + } + + void sceFsSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceFsSigSema", rdram, ctx, runtime); + } + + void close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioClose(rdram, ctx, runtime); + } + + void fstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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 lseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioLseek(rdram, ctx, runtime); + } + + void open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioOpen(rdram, ctx, runtime); + } + + void read(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioRead(rdram, ctx, runtime); + } + + void sceClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioClose(rdram, ctx, runtime); + } + + void sceFsInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceFsReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceIoctl(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t cmd = static_cast(getRegU32(ctx, 5)); + const uint32_t argAddr = getRegU32(ctx, 6); + + // HTCI wait paths poll sceIoctl(fd, 1, &state) and expect state to move + // away from 1 once host-side I/O is no longer busy. + if (cmd == 1 && argAddr != 0u) + { + uint8_t *argPtr = getMemPtr(rdram, argAddr); + if (!argPtr) + { + setReturnS32(ctx, -1); + return; + } + + const uint32_t ready = 0u; + std::memcpy(argPtr, &ready, sizeof(ready)); + } + + setReturnS32(ctx, 0); + } + + void sceLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioLseek(rdram, ctx, runtime); + } + + void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioOpen(rdram, ctx, runtime); + } + + void sceRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioRead(rdram, ctx, runtime); + } + + void sceWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioWrite(rdram, ctx, runtime); + } + + void stat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t statAddr = getRegU32(ctx, 5); + uint8_t *statBuf = getMemPtr(rdram, statAddr); + if (!statBuf) + { + setReturnS32(ctx, -1); + return; + } + + // Minimal fake stat payload: zeroed structure indicates a valid, readable file. + std::memset(statBuf, 0, 128); + setReturnS32(ctx, 0); + } + + void write(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::fioWrite(rdram, ctx, runtime); + } + + void cvFsSetDefDev(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + if (logCount < 8) + { + RUNTIME_LOG("ps2_stub cvFsSetDefDev"); + ++logCount; + } + setReturnS32(ctx, 0); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/FileIO.h b/ps2xRuntime/src/lib/Kernel/Stubs/FileIO.h new file mode 100644 index 00000000..dc9268f3 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/FileIO.h @@ -0,0 +1,28 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceFsDbChk(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceFsIntrSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceFsSemExit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceFsSemInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceFsSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void lseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void read(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceFsInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceFsReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceIoctl(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void stat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void write(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void cvFsSetDefDev(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Font.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/Font.cpp new file mode 100644 index 00000000..41e43874 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Font.cpp @@ -0,0 +1,667 @@ +#include "Common.h" +#include "Font.h" + +namespace ps2_stubs +{ + static void writeU32AtGp(uint8_t *rdram, uint32_t gp, int32_t offset, uint32_t value) + { + const uint32_t addr = gp + static_cast(offset); + if (uint8_t *p = getMemPtr(rdram, addr)) + *reinterpret_cast(p) = value; + } + + void sceeFontInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t gp = getRegU32(ctx, 28); + const uint32_t a0 = getRegU32(ctx, 4); + const uint32_t a1 = getRegU32(ctx, 5); + const uint32_t a2 = getRegU32(ctx, 6); + const uint32_t a3 = getRegU32(ctx, 7); + writeU32AtGp(rdram, gp, -0x7b60, a1); + writeU32AtGp(rdram, gp, -0x7b5c, a2); + writeU32AtGp(rdram, gp, -0x7b64, a0); + writeU32AtGp(rdram, gp, -0x7c98, a3); + writeU32AtGp(rdram, gp, -0x7b4c, 0x7f7f7f7f); + writeU32AtGp(rdram, gp, -0x7b50, 0x3f800000); + writeU32AtGp(rdram, gp, -0x7b54, 0x3f800000); + writeU32AtGp(rdram, gp, -0x7b58, 0); + + if (runtime && a0 != 0u) + { + if ((a0 * 256u) + 64u <= PS2_GS_VRAM_SIZE) + { + uint32_t clutData[16]; + for (uint32_t i = 0; i < 16u; ++i) + { + uint8_t alpha = static_cast((i * 0x80u) / 15u); + clutData[i] = (i == 0) + ? 0x00000000u + : (0x80u | (0x80u << 8) | (0x80u << 16) | (static_cast(alpha) << 24)); + } + constexpr uint32_t kClutQwc = 4u; + constexpr uint32_t kHeaderQwc = 6u; + constexpr uint32_t kTotalQwc = kHeaderQwc + kClutQwc; + uint32_t pktAddr = runtime->guestMalloc(kTotalQwc * 16u, 16u); + if (pktAddr != 0u) + { + uint8_t *pkt = getMemPtr(rdram, pktAddr); + if (pkt) + { + uint64_t *q = reinterpret_cast(pkt); + const uint32_t dbp = a0 & 0x3FFFu; + constexpr uint8_t psm = 0u; + q[0] = makeGiftagAplusD(4u); + q[1] = 0xEULL; + q[2] = (static_cast(dbp) << 32) | (1ULL << 48) | (static_cast(psm) << 56); + q[3] = 0x50ULL; + q[4] = 0ULL; + q[5] = 0x51ULL; + q[6] = 16ULL | (1ULL << 32); + q[7] = 0x52ULL; + q[8] = 0ULL; + q[9] = 0x53ULL; + q[10] = (2ULL << 58) | (kClutQwc & 0x7FFF) | (1ULL << 15); + q[11] = 0ULL; + std::memcpy(pkt + 12u * 8u, clutData, 64u); + constexpr uint32_t GIF_CHANNEL = 0x1000A000; + constexpr uint32_t CHCR_STR_MODE0 = 0x101u; + runtime->memory().writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); + runtime->memory().writeIORegister(GIF_CHANNEL + 0x20u, kTotalQwc & 0xFFFFu); + runtime->memory().writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); + runtime->memory().processPendingTransfers(); + } + } + } + } + + setReturnS32(ctx, static_cast(a0 + 4)); + } + + void sceeFontLoadFont(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static constexpr uint32_t kFontBase = 0x176148u; + static constexpr uint32_t kFontEntrySz = 0x24u; + + const uint32_t fontDataAddr = getRegU32(ctx, 4); + const int fontId = static_cast(getRegU32(ctx, 5)); + const int tbp0 = static_cast(getRegU32(ctx, 7)); + + if (!fontDataAddr || !runtime) + { + setReturnS32(ctx, tbp0); + return; + } + + const uint8_t *fontPtr = getConstMemPtr(rdram, fontDataAddr); + if (!fontPtr) + { + setReturnS32(ctx, tbp0); + return; + } + + int width = static_cast(*reinterpret_cast(fontPtr + 0x00u)); + int height = static_cast(*reinterpret_cast(fontPtr + 0x04u)); + uint32_t raw8 = *reinterpret_cast(fontPtr + 0x08u); + int fontDataSz = static_cast(*reinterpret_cast(fontPtr + 0x0cu)); + + uint32_t pointsize = raw8; + uint32_t fontOff = static_cast(fontId * static_cast(kFontEntrySz)); + if (raw8 & 0x40000000u) + { + pointsize = raw8 - 0x40000000u; + if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff + 0x20u)) + *reinterpret_cast(p) = 1u; + } + else + { + if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff + 0x20u)) + *reinterpret_cast(p) = 0u; + } + + int tw = (width >= 0) ? (width >> 6) : ((width + 0x3f) >> 6); + int qwc = (fontDataSz >= 0) ? (fontDataSz >> 4) : ((fontDataSz + 0xf) >> 4); + + uint32_t glyphSrc = fontDataAddr + static_cast(fontDataSz) + 0x10u; + uint32_t glyphAlloc = runtime->guestMalloc(0x2010u, 0x40u); + if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff)) + *reinterpret_cast(p) = glyphAlloc; + + if (glyphAlloc != 0u) + { + uint8_t *dst = getMemPtr(rdram, glyphAlloc); + const uint8_t *src = getConstMemPtr(rdram, glyphSrc); + if (dst && src) + std::memcpy(dst, src, 0x2010u); + } + + uint32_t isDoubleByte = 0; + if (const uint8_t *p = getConstMemPtr(rdram, kFontBase + fontOff + 0x20u)) + isDoubleByte = *reinterpret_cast(p); + if (isDoubleByte == 0u) + { + uint32_t kernSrc = glyphSrc + 0x2010u; + uint32_t kernAlloc = runtime->guestMalloc(0xc400u, 0x40u); + if (glyphAlloc != 0u) + *reinterpret_cast(getMemPtr(rdram, glyphAlloc + 0x2000u)) = kernAlloc; + if (kernAlloc != 0u) + { + uint8_t *dst = getMemPtr(rdram, kernAlloc); + const uint8_t *src = getConstMemPtr(rdram, kernSrc); + if (dst && src) + std::memcpy(dst, src, 0xc400u); + } + } + + auto writeFontField = [&](uint32_t off, uint32_t val) + { + if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff + off)) + *reinterpret_cast(p) = val; + }; + writeFontField(0x18u, pointsize); + writeFontField(0x08u, static_cast(tbp0)); + writeFontField(0x0cu, static_cast(tw)); + + int logW = 0; + for (int w = width; w != 1 && w != 0; w = static_cast(static_cast(w) >> 1)) + logW++; + writeFontField(0x10u, static_cast(logW)); + + int logH = 0; + for (int h = height; h != 1 && h != 0; h = static_cast(static_cast(h) >> 1)) + logH++; + writeFontField(0x14u, static_cast(logH)); + writeFontField(0x04u, 0u); + writeFontField(0x1cu, getRegU32(ctx, 6)); + + if (qwc > 0) + { + const uint32_t imageBytes = static_cast(qwc) * 16u; + const uint8_t psm = 20u; + const uint32_t headerQwc = 12u; + const uint32_t imageQwc = static_cast(qwc); + const uint32_t totalQwc = headerQwc + imageQwc; + uint32_t pktAddr = runtime->guestMalloc(totalQwc * 16u, 16u); + if (pktAddr != 0u) + { + uint8_t *pkt = getMemPtr(rdram, pktAddr); + const uint8_t *imgSrc = getConstMemPtr(rdram, fontDataAddr + 0x10u); + if (pkt && imgSrc) + { + uint64_t *q = reinterpret_cast(pkt); + const uint32_t dbp = static_cast(tbp0) & 0x3FFFu; + const uint32_t dbw = static_cast(tw > 0 ? tw : 1) & 0x3Fu; + const uint32_t rrw = static_cast(width > 0 ? width : 64); + const uint32_t rrh = static_cast(height > 0 ? height : 1); + + q[0] = makeGiftagAplusD(4u); + q[1] = 0xEULL; + q[2] = (static_cast(psm) << 24) | (1ULL << 16) | + (static_cast(dbp) << 32) | (static_cast(dbw) << 48) | + (static_cast(psm) << 56); + q[3] = 0x50ULL; + q[4] = 0ULL; + q[5] = 0x51ULL; + q[6] = (static_cast(rrh) << 32) | static_cast(rrw); + q[7] = 0x52ULL; + q[8] = 0ULL; + q[9] = 0x53ULL; + q[10] = (2ULL << 58) | (imageQwc & 0x7FFF) | (1ULL << 15); + q[11] = 0ULL; + std::memcpy(pkt + 12 * 8, imgSrc, imageBytes); + + constexpr uint32_t GIF_CHANNEL = 0x1000A000; + constexpr uint32_t CHCR_STR_MODE0 = 0x101u; + runtime->memory().writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); + runtime->memory().writeIORegister(GIF_CHANNEL + 0x20u, totalQwc & 0xFFFFu); + runtime->memory().writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); + } + } + } + + int retTbp = tbp0 + ((fontDataSz >= 0 ? fontDataSz : fontDataSz + 0x7f) >> 7); + setReturnS32(ctx, retTbp); + } + + static constexpr uint32_t kFontBase = 0x176148u; + static constexpr uint32_t kFontEntrySz = 0x24u; + + void sceeFontGenerateString(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const float sclx = ctx->f[12]; + const float scly = ctx->f[13]; + const uint32_t bufAddr = getRegU32(ctx, 4); + const uint64_t paramX = GPR_U64(ctx, 5); + const int64_t paramY = GPR_S64(ctx, 6); + const int paramW = static_cast(getRegU32(ctx, 7)); + const int paramH = static_cast(getRegU32(ctx, 8)); + const uint32_t colour = getRegU32(ctx, 9); + const int alignCh = static_cast(getRegU32(ctx, 10) & 0xffu); + const int fontId = static_cast(getRegU32(ctx, 11)); + + const uint32_t sp = getRegU32(ctx, 29); + const uint32_t strAddr = FAST_READ32(sp + 0x00u); + const uint32_t param14 = FAST_READ32(sp + 0x18u); + + if (bufAddr == 0u) + { + setReturnS32(ctx, 0); + ctx->pc = getRegU32(ctx, 31); + return; + } + + const uint32_t gp = getRegU32(ctx, 28); + const uint32_t fontModeAdj = FAST_READ32(gp + static_cast(static_cast(-0x7c98))); + const uint32_t shiftAmt = fontModeAdj & 0x1fu; + const int scrHeight = static_cast(FAST_READ32(gp + static_cast(static_cast(-0x7b5c)))); + const int scrWidth = static_cast(FAST_READ32(gp + static_cast(static_cast(-0x7b60)))); + const uint32_t fontClut = FAST_READ32(gp + static_cast(static_cast(-0x7b64))); + + const uint32_t fontOff = static_cast(fontId * static_cast(kFontEntrySz)); + const int lineH = static_cast(FAST_READ32(kFontBase + fontOff + 0x18u)); + + int iVar21 = 0; + int iStack_dc = 0; + uint32_t uStack_d8 = 0; + int iVar15 = 0; + + int16_t sVar8; + { + int yStepRaw = static_cast(static_cast((lineH + 6) * 16) * scly); + sVar8 = static_cast((static_cast(paramY) + 0x700) * 16) + static_cast(yStepRaw >> static_cast(shiftAmt)); + } + + int16_t baseX = static_cast((static_cast(paramX) + 0x6c0) * 16); + + if (param14 != 0u) + { + int64_t clipY1 = static_cast(static_cast(paramY) + paramH); + int64_t clipX1 = static_cast(static_cast(paramX) + paramW); + if (clipY1 > scrHeight - 1) + clipY1 = static_cast(scrHeight - 1); + if (clipX1 > scrWidth - 1) + clipX1 = static_cast(scrWidth - 1); + int64_t clipY0 = 0; + if (paramY > 0) + clipY0 = paramY; + uint64_t clipX0 = 0; + if (static_cast(paramX) > 0) + clipX0 = paramX; + + uint64_t scissor = clipX0 | (static_cast(static_cast(clipX1)) << 16) | (static_cast(static_cast(clipY0)) << 32) | (static_cast(static_cast(clipY1)) << 48); + + FAST_WRITE64(bufAddr + 0x00, 0x1000000000000005ull); + FAST_WRITE64(bufAddr + 0x08, 0x0eull); + FAST_WRITE64(bufAddr + 0x10, scissor); + FAST_WRITE64(bufAddr + 0x18, 0x40ull); + FAST_WRITE64(bufAddr + 0x20, 0x20000ull); + FAST_WRITE64(bufAddr + 0x28, 0x47ull); + FAST_WRITE64(bufAddr + 0x30, 0x44ull); + FAST_WRITE64(bufAddr + 0x38, 0x42ull); + FAST_WRITE64(bufAddr + 0x40, 0x160ull); + FAST_WRITE64(bufAddr + 0x48, 0x14ull); + FAST_WRITE64(bufAddr + 0x50, 0x156ull); + FAST_WRITE64(bufAddr + 0x58, 0ull); + FAST_WRITE64(bufAddr + 0x60, 0x1000000000000001ull); + FAST_WRITE64(bufAddr + 0x68, 0x0eull); + + uint64_t iVar5 = static_cast(FAST_READ32(kFontBase + fontOff + 0x08u)); + uint64_t iVar22 = static_cast(FAST_READ32(kFontBase + fontOff + 0x0cu)); + uint64_t iVar3 = static_cast(FAST_READ32(kFontBase + fontOff + 0x10u)); + uint64_t iVar4 = static_cast(FAST_READ32(kFontBase + fontOff + 0x14u)); + + uint64_t tex0 = iVar5 | 0x2000000000000000ull | (iVar22 << 14) | 0x400000000ull | (iVar3 << 26) | 0x1400000ull | (iVar4 << 30) | (static_cast(fontClut) << 37); + + FAST_WRITE64(bufAddr + 0x70, tex0); + FAST_WRITE64(bufAddr + 0x78, 6ull); + FAST_WRITE64(bufAddr + 0x80, 0x1000000000000001ull); + FAST_WRITE64(bufAddr + 0x88, 0x0eull); + FAST_WRITE64(bufAddr + 0x90, static_cast(colour)); + FAST_WRITE64(bufAddr + 0x98, 1ull); + + iVar21 = 10; + } + + int iVar22_qw = iVar21 + 1; + uint32_t s2 = bufAddr + static_cast(iVar22_qw * 16); + uint32_t uVar20 = 0; + + size_t sLen = 0; + { + const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); + if (hostStr) + sLen = ::strlen(hostStr); + } + + while (uVar20 < sLen) + { + uint8_t bVar1 = FAST_READ8(strAddr + uVar20); + uint32_t uVar9 = static_cast(bVar1); + int8_t chSigned = static_cast(bVar1); + + if (uStack_d8 < 0x21u) + { + goto label_check_printable; + } + + if (uVar9 > 0x20u) + { + uint32_t dat176168 = FAST_READ32(kFontBase + fontOff + 0x20u); + if (dat176168 == 0u) + { + uint32_t fontPtr0 = FAST_READ32(kFontBase + fontOff); + uint32_t tableAddr = FAST_READ32(fontPtr0 + 0x2000u); + int8_t kern = static_cast(FAST_READ8(tableAddr - 0x1c20u + uStack_d8 * 0xe0u + uVar9)); + iVar15 += static_cast(static_cast(static_cast(kern)) * sclx); + } + goto label_check_printable; + } + + goto label_space; + + label_check_printable: + if (uVar9 < 0x21u) + { + goto label_space; + } + + { + int glyphIdx = static_cast(chSigned); + uint32_t iVar19_off = static_cast(glyphIdx * 0x20); + + if (param14 != 0u) + { + uint32_t fontPtr = FAST_READ32(kFontBase + fontOff); + int16_t sVar7 = baseX + static_cast(iVar15); + + iVar22_qw += 2; + iStack_dc += 1; + + uint16_t wU0 = FAST_READ16(fontPtr + iVar19_off + 0); + uint16_t wV0 = FAST_READ16(fontPtr + iVar19_off + 2); + FAST_WRITE16(s2 + 0x00, wU0); + FAST_WRITE16(s2 + 0x02, wV0); + + int16_t dx0 = static_cast(FAST_READ16(fontPtr + iVar19_off + 8)); + int16_t dy0 = static_cast(FAST_READ16(fontPtr + iVar19_off + 10)); + uint16_t wX0 = static_cast(sVar7 + static_cast(static_cast(static_cast(static_cast(dx0)) * sclx))); + int yVal0 = static_cast(static_cast(static_cast(dy0)) * scly) >> static_cast(shiftAmt); + uint16_t wY0 = static_cast(sVar8 + static_cast(yVal0)); + FAST_WRITE16(s2 + 0x08, wX0); + FAST_WRITE16(s2 + 0x0a, wY0); + FAST_WRITE32(s2 + 0x0c, 1u); + + s2 += 0x10u; + + uint16_t wU1 = FAST_READ16(fontPtr + iVar19_off + 4); + uint16_t wV1 = FAST_READ16(fontPtr + iVar19_off + 6); + FAST_WRITE16(s2 + 0x00, wU1); + FAST_WRITE16(s2 + 0x02, wV1); + + int16_t dx1 = static_cast(FAST_READ16(fontPtr + iVar19_off + 12)); + int16_t dy1 = static_cast(FAST_READ16(fontPtr + iVar19_off + 14)); + uint16_t wX1 = static_cast(sVar7 + static_cast(static_cast(static_cast(static_cast(dx1)) * sclx))); + int yVal1 = static_cast(static_cast(static_cast(dy1)) * scly) >> static_cast(shiftAmt); + uint16_t wY1 = static_cast(sVar8 + static_cast(yVal1)); + FAST_WRITE16(s2 + 0x08, wX1); + FAST_WRITE16(s2 + 0x0a, wY1); + FAST_WRITE32(s2 + 0x0c, 1u); + + s2 += 0x10u; + } + + { + uint32_t fontPtr = FAST_READ32(kFontBase + fontOff); + uint32_t advOff = static_cast((glyphIdx * 2 + 1) * 16 + 8); + int16_t advW = static_cast(FAST_READ16(fontPtr + advOff)); + iVar15 += static_cast(static_cast(static_cast(advW)) * sclx); + } + } + goto label_next; + + label_space: + { + int spaceW = static_cast(FAST_READ32(kFontBase + fontOff + 0x1cu)); + iVar15 += static_cast(static_cast(spaceW) * sclx); + } + + label_next: + uStack_d8 = uVar9; + uVar20++; + } + + if (param14 != 0u) + { + if (alignCh != 'L') + { + if (alignCh == 'C' || alignCh == 'R') + { + int shift = paramW * 16 - iVar15; + if (alignCh == 'C') + shift >>= 1; + if (iStack_dc > 0) + { + uint32_t adj = bufAddr + static_cast(iVar21 * 16) + 0x20u; + for (int k = 0; k < iStack_dc; k++) + { + int16_t oldX0 = static_cast(FAST_READ16(adj - 8u)); + int16_t oldX1 = static_cast(FAST_READ16(adj + 8u)); + FAST_WRITE16(adj - 8u, static_cast(oldX0 + static_cast(shift))); + FAST_WRITE16(adj + 8u, static_cast(oldX1 + static_cast(shift))); + adj += 0x20u; + } + } + } + else if (alignCh == 'J' && sLen > 1) + { + int iVar19_div = static_cast(sLen) - 1; + if (iVar19_div == 0) + iVar19_div = 1; + int spacePer = (paramW * 16 - iVar15) / iVar19_div; + uint32_t adj = bufAddr + static_cast(iVar21 * 16) + 0x20u; + int accum = 0; + for (uint32_t jj = 0; jj < sLen; jj++) + { + int8_t jch = static_cast(FAST_READ8(strAddr + jj)); + if (jch > 0x20) + { + int16_t oldX0 = static_cast(FAST_READ16(adj - 8u)); + int16_t oldX1 = static_cast(FAST_READ16(adj + 8u)); + FAST_WRITE16(adj - 8u, static_cast(oldX0 + static_cast(accum))); + FAST_WRITE16(adj + 8u, static_cast(oldX1 + static_cast(accum))); + adj += 0x20u; + } + accum += spacePer; + } + } + } + + if (param14 != 0u) + { + uint32_t tagAddr = bufAddr + static_cast(iVar21 * 16); + FAST_WRITE64(tagAddr + 0x00, static_cast(static_cast(iStack_dc)) | 0x4400000000000000ull); + FAST_WRITE64(tagAddr + 0x08, 0x5353ull); + + uint32_t endAddr = bufAddr + static_cast(iVar22_qw * 16); + FAST_WRITE64(endAddr + 0x00, 0x1000000000008001ull); + FAST_WRITE64(endAddr + 0x08, 0x0eull); + + int iVar19_end = iVar22_qw + 1; + uint32_t endAddr2 = bufAddr + static_cast(iVar19_end * 16); + FAST_WRITE64(endAddr2 + 0x00, 0x01ff0000027f0000ull); + FAST_WRITE64(endAddr2 + 0x08, 0x40ull); + + iVar22_qw += 2; + } + } + + int ret = 0; + if (param14 != 0u) + ret = iVar22_qw; + setReturnS32(ctx, ret); + ctx->pc = getRegU32(ctx, 31); + } + + void sceeFontPrintfAt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t oldSp = getRegU32(ctx, 29); + const uint32_t frame = oldSp - 0x900u; + + const uint32_t bufAddr = getRegU32(ctx, 4); + const uint32_t paramX = getRegU32(ctx, 5); + const uint32_t paramY = getRegU32(ctx, 6); + const uint32_t fmtAddr = getRegU32(ctx, 7); + + const uint8_t *callerVa = getConstMemPtr(rdram, oldSp + 16u); + uint8_t *frameVa = getMemPtr(rdram, frame + 0x8f8u); + if (callerVa && frameVa) + std::memcpy(frameVa, callerVa, 64u); + + SET_GPR_U32(ctx, 4, frame + 0x20u); + SET_GPR_U32(ctx, 5, fmtAddr); + SET_GPR_U32(ctx, 6, frame + 0x8f8u); + vsprintf(rdram, ctx, runtime); + + const uint32_t gp = getRegU32(ctx, 28); + uint32_t defaultSclxBits = FAST_READ32(gp + static_cast(static_cast(-0x7b54))); + uint32_t defaultSclyBits = FAST_READ32(gp + static_cast(static_cast(-0x7b50))); + uint32_t defaultColour = FAST_READ32(gp + static_cast(static_cast(-0x7b4c))); + uint32_t defaultFontId = FAST_READ32(gp + static_cast(static_cast(-0x7b58))); + uint32_t scrWidth = FAST_READ32(gp + static_cast(static_cast(-0x7b60))); + uint32_t scrHeight = FAST_READ32(gp + static_cast(static_cast(-0x7b5c))); + + std::memcpy(&ctx->f[12], &defaultSclxBits, sizeof(float)); + std::memcpy(&ctx->f[13], &defaultSclyBits, sizeof(float)); + + FAST_WRITE32(frame + 0x00u, frame + 0x20u); + FAST_WRITE32(frame + 0x08u, frame + 0x820u); + FAST_WRITE32(frame + 0x10u, frame + 0x824u); + FAST_WRITE32(frame + 0x18u, 1u); + + SET_GPR_U32(ctx, 29, frame); + SET_GPR_U32(ctx, 4, bufAddr); + SET_GPR_U32(ctx, 5, paramX); + SET_GPR_U32(ctx, 6, paramY); + SET_GPR_U32(ctx, 7, scrWidth); + SET_GPR_U32(ctx, 8, scrHeight); + SET_GPR_U32(ctx, 9, defaultColour); + SET_GPR_U32(ctx, 10, 0x4cu); + SET_GPR_U32(ctx, 11, defaultFontId); + + sceeFontGenerateString(rdram, ctx, runtime); + + SET_GPR_U32(ctx, 29, oldSp); + ctx->pc = getRegU32(ctx, 31); + } + + void sceeFontPrintfAt2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t oldSp = getRegU32(ctx, 29); + const uint32_t frame = oldSp - 0x900u; + + const uint32_t bufAddr = getRegU32(ctx, 4); + const uint32_t paramX = getRegU32(ctx, 5); + const uint32_t paramY = getRegU32(ctx, 6); + const uint32_t paramW = getRegU32(ctx, 7); + const uint32_t paramH = getRegU32(ctx, 8); + const uint32_t alignRaw = getRegU32(ctx, 9); + const uint32_t fmtAddr = getRegU32(ctx, 10); + const uint64_t param8 = GPR_U64(ctx, 11); + + int8_t alignChar = static_cast(alignRaw & 0xffu); + + FAST_WRITE64(frame + 0x8f8u, param8); + + SET_GPR_U32(ctx, 4, frame + 0x20u); + SET_GPR_U32(ctx, 5, fmtAddr); + SET_GPR_U32(ctx, 6, frame + 0x8f8u); + vsprintf(rdram, ctx, runtime); + + const uint32_t gp = getRegU32(ctx, 28); + uint32_t defaultSclxBits = FAST_READ32(gp + static_cast(static_cast(-0x7b54))); + uint32_t defaultSclyBits = FAST_READ32(gp + static_cast(static_cast(-0x7b50))); + uint32_t defaultColour = FAST_READ32(gp + static_cast(static_cast(-0x7b4c))); + uint32_t defaultFontId = FAST_READ32(gp + static_cast(static_cast(-0x7b58))); + + std::memcpy(&ctx->f[12], &defaultSclxBits, sizeof(float)); + std::memcpy(&ctx->f[13], &defaultSclyBits, sizeof(float)); + + FAST_WRITE32(frame + 0x00u, frame + 0x20u); + FAST_WRITE32(frame + 0x08u, frame + 0x820u); + FAST_WRITE32(frame + 0x10u, frame + 0x824u); + FAST_WRITE32(frame + 0x18u, 1u); + + SET_GPR_U32(ctx, 29, frame); + SET_GPR_U32(ctx, 4, bufAddr); + SET_GPR_U32(ctx, 5, paramX); + SET_GPR_U32(ctx, 6, paramY); + SET_GPR_U32(ctx, 7, paramW); + SET_GPR_U32(ctx, 8, paramH); + SET_GPR_U32(ctx, 9, defaultColour); + SET_GPR_U32(ctx, 10, static_cast(static_cast(alignChar))); + SET_GPR_U32(ctx, 11, defaultFontId); + + sceeFontGenerateString(rdram, ctx, runtime); + + SET_GPR_U32(ctx, 29, oldSp); + ctx->pc = getRegU32(ctx, 31); + } + + void sceeFontClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static constexpr uint32_t kFontBase = 0x176148u; + static constexpr uint32_t kFontEntrySz = 0x24u; + const int fontId = static_cast(getRegU32(ctx, 4)); + const uint32_t fontOff = static_cast(fontId * static_cast(kFontEntrySz)); + uint32_t glyphPtr = 0; + if (const uint8_t *p = getConstMemPtr(rdram, kFontBase + fontOff)) + glyphPtr = *reinterpret_cast(p); + if (glyphPtr != 0u) + { + if (runtime) + { + uint32_t kernPtr = 0; + if (const uint8_t *kp = getConstMemPtr(rdram, glyphPtr + 0x2000u)) + kernPtr = *reinterpret_cast(kp); + if (kernPtr != 0u) + runtime->guestFree(kernPtr); + runtime->guestFree(glyphPtr); + } + if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff)) + *reinterpret_cast(p) = 0u; + setReturnS32(ctx, 0); + } + else + { + setReturnS32(ctx, -1); + } + } + + void sceeFontSetColour(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t gp = getRegU32(ctx, 28); + writeU32AtGp(rdram, gp, -0x7b4c, getRegU32(ctx, 4)); + } + + void sceeFontSetMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t gp = getRegU32(ctx, 28); + writeU32AtGp(rdram, gp, -0x7c98, getRegU32(ctx, 4)); + setReturnS32(ctx, 0); + } + + void sceeFontSetFont(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t gp = getRegU32(ctx, 28); + writeU32AtGp(rdram, gp, -0x7b58, getRegU32(ctx, 4)); + } + + void sceeFontSetScale(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t gp = getRegU32(ctx, 28); + uint32_t sclx_bits, scly_bits; + std::memcpy(&sclx_bits, &ctx->f[12], sizeof(float)); + std::memcpy(&scly_bits, &ctx->f[13], sizeof(float)); + writeU32AtGp(rdram, gp, -0x7b54, sclx_bits); + writeU32AtGp(rdram, gp, -0x7b50, scly_bits); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Font.h b/ps2xRuntime/src/lib/Kernel/Stubs/Font.h new file mode 100644 index 00000000..d1fa521f --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Font.h @@ -0,0 +1,17 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceeFontInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontLoadFont(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontGenerateString(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontPrintfAt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontPrintfAt2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontSetColour(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontSetMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontSetFont(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceeFontSetScale(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp new file mode 100644 index 00000000..2d1b2aaa --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp @@ -0,0 +1,1898 @@ +#include "Common.h" +#include "GS.h" +#include "ps2_log.h" +#include "runtime/ps2_gs_common.h" +#include "runtime/ps2_gs_psmct16.h" + +namespace ps2_stubs +{ + namespace + { + std::mutex g_gs_sync_v_mutex; + uint64_t g_gs_sync_v_base_tick = 0u; + std::mutex g_gs_sync_v_callback_mutex; + uint32_t g_gs_sync_v_callback_func = 0u; + uint32_t g_gs_sync_v_callback_gp = 0u; + uint32_t g_gs_sync_v_callback_sp = 0u; + uint32_t g_gs_sync_v_callback_stack_base = 0u; + uint32_t g_gs_sync_v_callback_stack_top = 0u; + uint32_t g_gs_sync_v_callback_bad_pc_logs = 0u; + struct GsDebugProbePoint + { + uint32_t x; + uint32_t y; + }; + + constexpr GsDebugProbePoint kGhostProbePoints[] = { + {220u, 176u}, + {260u, 208u}, + {320u, 208u}, + {260u, 240u}, + {320u, 240u}, + {260u, 272u}, + {320u, 272u}, + }; + + uint64_t makeClearPrim(bool useContext2) + { + return static_cast(GS_PRIM_SPRITE) | + (static_cast(useContext2 ? 1u : 0u) << 9); + } + + uint64_t makeClearRgbaq(uint32_t rgba) + { + return static_cast(rgba); + } + + uint64_t makeClearXyz(int32_t x, int32_t y) + { + return static_cast(static_cast(x << 4)) | + (static_cast(static_cast(y << 4)) << 16); + } + + void seedGsClearPacket(GsClearMem &clear, + int32_t width, + int32_t height, + uint32_t rgba, + uint32_t ztest, + bool useContext2) + { + const int32_t offX = 0x800 - (width >> 1); + const int32_t offY = 0x800 - (height >> 1); + const uint64_t clearTest = makeTest(0u); + const uint64_t restoreTest = makeTest(ztest); + const uint64_t prim = makeClearPrim(useContext2); + const uint64_t rgbaq = makeClearRgbaq(rgba); + const uint64_t xyz0 = makeClearXyz(offX, offY); + const uint64_t xyz1 = makeClearXyz(offX + width, offY + height); + const uint64_t testReg = useContext2 ? GS_REG_TEST_2 : GS_REG_TEST_1; + + clear.testa = {clearTest, testReg}; + clear.prim = {prim, GS_REG_PRIM}; + clear.rgbaq = {rgbaq, GS_REG_RGBAQ}; + clear.xyz2a = {xyz0, GS_REG_XYZ2}; + clear.xyz2b = {xyz1, GS_REG_XYZ2}; + clear.testb = {restoreTest, testReg}; + } + + bool hasSeededGsClearPacket(const GsClearMem &clear) + { + return clear.rgbaq.reg == GS_REG_RGBAQ && + clear.xyz2a.reg == GS_REG_XYZ2 && + clear.xyz2b.reg == GS_REG_XYZ2; + } + + struct GsTrailingArgs2 + { + uint32_t arg0 = 0u; + uint32_t arg1 = 0u; + }; + + struct GsTrailingArgs3 + { + uint32_t arg0 = 0u; + uint32_t arg1 = 0u; + uint32_t arg2 = 0u; + }; + + GsTrailingArgs2 decodeGsTrailingArgs2(uint8_t *rdram, R5900Context *ctx) + { + const uint32_t reg8 = getRegU32(ctx, 8); + const uint32_t reg9 = getRegU32(ctx, 9); + const uint32_t stack0 = readStackU32(rdram, ctx, 16); + const uint32_t stack1 = readStackU32(rdram, ctx, 20); + + const bool hasRegArgs = (reg8 != 0u || reg9 != 0u); + const bool hasStackArgs = (stack0 != 0u || stack1 != 0u); + if (hasRegArgs || !hasStackArgs) + { + return {reg8, reg9}; + } + + return {stack0, stack1}; + } + + GsTrailingArgs3 decodeGsTrailingArgs3(uint8_t *rdram, R5900Context *ctx) + { + const uint32_t reg8 = getRegU32(ctx, 8); + const uint32_t reg9 = getRegU32(ctx, 9); + const uint32_t reg10 = getRegU32(ctx, 10); + 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 bool hasRegArgs = (reg8 != 0u || reg9 != 0u || reg10 != 0u); + const bool hasStackArgs = (stack0 != 0u || stack1 != 0u || stack2 != 0u); + if (hasRegArgs || !hasStackArgs) + { + return {reg8, reg9, reg10}; + } + + return {stack0, stack1, stack2}; + } + + bool sampleFramebufferPixel(uint8_t *vram, + uint32_t vramSize, + uint32_t fbp, + uint32_t fbw, + uint32_t psm, + uint32_t x, + uint32_t y, + uint32_t &outPixel) + { + if (!vram || fbw == 0u) + { + return false; + } + + const uint32_t bytesPerPixel = + (psm == GS_PSM_CT16 || psm == GS_PSM_CT16S) ? 2u : (psm == GS_PSM_CT32 || psm == GS_PSM_CT24) ? 4u + : 0u; + if (bytesPerPixel == 0u) + { + return false; + } + + const uint32_t widthBlocks = (fbw != 0u) ? fbw : 1u; + const uint32_t strideBytes = fbw * 64u * bytesPerPixel; + const uint32_t baseBytes = fbp * 8192u; + uint32_t offset = baseBytes + (y * strideBytes) + (x * bytesPerPixel); + if (psm == GS_PSM_CT16) + { + offset = GSPSMCT16::addrPSMCT16(GSInternal::framePageBaseToBlock(fbp), widthBlocks, x, y); + } + else if (psm == GS_PSM_CT16S) + { + offset = GSPSMCT16::addrPSMCT16S(GSInternal::framePageBaseToBlock(fbp), widthBlocks, x, y); + } + if (offset + bytesPerPixel > vramSize) + { + return false; + } + + if (bytesPerPixel == 4u) + { + std::memcpy(&outPixel, vram + offset, sizeof(outPixel)); + if (psm == GS_PSM_CT24) + { + outPixel |= 0xFF000000u; + } + return true; + } + + uint16_t packed = 0u; + std::memcpy(&packed, vram + offset, sizeof(packed)); + const uint32_t r = ((packed >> 0) & 0x1Fu) << 3; + const uint32_t g = ((packed >> 5) & 0x1Fu) << 3; + const uint32_t b = ((packed >> 10) & 0x1Fu) << 3; + const uint32_t a = (packed & 0x8000u) ? 0x80u : 0x00u; + outPixel = r | (g << 8) | (b << 16) | (a << 24); + return true; + } + + bool sampleFrameRegPixel(PS2Runtime *runtime, + uint64_t frameReg, + uint32_t x, + uint32_t y, + uint32_t &outPixel) + { + if (!runtime) + { + return false; + } + + const uint32_t fbp = static_cast(frameReg & 0x1FFu); + const uint32_t fbw = static_cast((frameReg >> 16) & 0x3Fu); + const uint32_t psm = static_cast((frameReg >> 24) & 0x3Fu); + return sampleFramebufferPixel(runtime->memory().getGSVRAM(), PS2_GS_VRAM_SIZE, fbp, fbw, psm, x, y, outPixel); + } + + bool sampleDispFbPixel(PS2Runtime *runtime, + uint64_t dispfb, + uint32_t x, + uint32_t y, + uint32_t &outPixel) + { + if (!runtime) + { + return false; + } + + const uint32_t fbp = static_cast(dispfb & 0x1FFu); + const uint32_t fbw = static_cast((dispfb >> 9) & 0x3Fu); + const uint32_t psm = static_cast((dispfb >> 15) & 0x1Fu); + return sampleFramebufferPixel(runtime->memory().getGSVRAM(), PS2_GS_VRAM_SIZE, fbp, fbw, psm, x, y, outPixel); + } + + void logSwapProbeStage(PS2Runtime *runtime, + const char *stage, + uint32_t which, + uint64_t drawFrameReg, + uint64_t dispfb, + bool hasClearPacket) + { + static uint32_t s_swapProbeCount = 0u; + if (!runtime || s_swapProbeCount >= 24u) + { + return; + } + + PS2_IF_AGRESSIVE_LOGS({ + RUNTIME_LOG("[gs:probe] stage=" << stage + << " which=" << which + << " clear=" << static_cast(hasClearPacket ? 1u : 0u)); + + for (const auto &probe : kGhostProbePoints) + { + uint32_t page0Pixel = 0u; + uint32_t page150Pixel = 0u; + const bool havePage0 = sampleFrameRegPixel(runtime, drawFrameReg, probe.x, probe.y, page0Pixel); + const bool havePage150 = sampleDispFbPixel(runtime, dispfb, probe.x, probe.y, page150Pixel); + if (havePage0) + { + RUNTIME_LOG(" p0[" << probe.x << "," << probe.y << "]=0x" + << std::hex << page0Pixel << std::dec); + } + if (havePage150) + { + RUNTIME_LOG(" p150[" << probe.x << "," << probe.y << "]=0x" + << std::hex << page150Pixel << std::dec); + } + } + RUNTIME_LOG(std::endl); + ++s_swapProbeCount; + }); + } + + void applyGsClearPacket(PS2Runtime *runtime, const GsClearMem &clear) + { + if (!runtime || !runtime->syncCoreSubsystems() || !hasSeededGsClearPacket(clear)) + { + return; + } + + runtime->gs().writeRegister(static_cast(clear.testa.reg & 0xFFu), clear.testa.value); + runtime->gs().writeRegister(static_cast(clear.prim.reg & 0xFFu), clear.prim.value); + runtime->gs().writeRegister(static_cast(clear.rgbaq.reg & 0xFFu), clear.rgbaq.value); + runtime->gs().writeRegister(static_cast(clear.xyz2a.reg & 0xFFu), clear.xyz2a.value); + runtime->gs().writeRegister(static_cast(clear.xyz2b.reg & 0xFFu), clear.xyz2b.value); + runtime->gs().writeRegister(static_cast(clear.testb.reg & 0xFFu), clear.testb.value); + } + + void refreshPacketBuilderPendingCount(uint8_t *rdram, PS2Runtime *runtime, uint32_t stateAddr); + void writePacketBuilderCurrent(uint8_t *rdram, PS2Runtime *runtime, uint32_t stateAddr, uint32_t currentAddr); + void logVif1PacketStateOp(const char *op, + R5900Context *ctx, + uint32_t stateAddr, + uint32_t currentAddr, + uint32_t aux0, + uint32_t aux1) + { + static uint32_t s_vif1PacketOpLogCount = 0u; + if (s_vif1PacketOpLogCount >= 96u) + { + return; + } + + RUNTIME_LOG("[vif1:packet] idx=" << s_vif1PacketOpLogCount + << " op=" << op + << " pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << " state=0x" << stateAddr + << " current=0x" << currentAddr + << " aux0=0x" << aux0 + << " aux1=0x" << aux1 + << std::dec << std::endl); + ++s_vif1PacketOpLogCount; + } + + void initPacketBuilderState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t baseAddr = getRegU32(ctx, 5); + const uint32_t words[4] = {baseAddr, baseAddr, 0u, 0u}; + writeGuestBytes(rdram, + runtime, + stateAddr, + reinterpret_cast(words), + sizeof(words)); + } + + uint32_t terminatePacketBuilderState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return 0u; + } + + const uint32_t zero = 0u; + while ((currentAddr & 0xCu) != 0u) + { + writeGuestBytes(rdram, + runtime, + currentAddr, + reinterpret_cast(&zero), + sizeof(zero)); + currentAddr += 4u; + } + + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr); + writeGuestBytes(rdram, + runtime, + stateAddr + 8u, + reinterpret_cast(&zero), + sizeof(zero)); + return currentAddr; + } + + void resetPacketBuilderState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + uint32_t baseAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr + 4u, baseAddr)) + { + setReturnU32(ctx, 0u); + return; + } + + const uint32_t words[4] = {baseAddr, baseAddr, 0u, 0u}; + writeGuestBytes(rdram, + runtime, + stateAddr, + reinterpret_cast(words), + sizeof(words)); + setReturnU32(ctx, baseAddr); + } + + bool tryReadQwordFromGuest(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, uint64_t &outQword) + { + uint32_t low = 0u; + uint32_t high = 0u; + if (!tryReadWordFromGuest(rdram, runtime, addr, low) || + !tryReadWordFromGuest(rdram, runtime, addr + 4u, high)) + { + return false; + } + + outQword = static_cast(low) | (static_cast(high) << 32u); + return true; + } + + void writeGuestU32(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, uint32_t value) + { + writeGuestBytes(rdram, + runtime, + addr, + reinterpret_cast(&value), + sizeof(value)); + } + + void writeGuestU64(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, uint64_t value) + { + writeGuestBytes(rdram, + runtime, + addr, + reinterpret_cast(&value), + sizeof(value)); + } + + void writeGuestVec128(uint8_t *rdram, PS2Runtime *runtime, uint32_t addr, __m128i value) + { + alignas(16) __m128i temp = value; + writeGuestBytes(rdram, + runtime, + addr, + reinterpret_cast(&temp), + sizeof(temp)); + } + + void refreshPacketBuilderPendingCount(uint8_t *rdram, PS2Runtime *runtime, uint32_t stateAddr) + { + uint32_t currentAddr = 0u; + uint32_t pendingCountAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr) || + !tryReadWordFromGuest(rdram, runtime, stateAddr + 8u, pendingCountAddr) || + pendingCountAddr == 0u || + currentAddr <= pendingCountAddr) + { + return; + } + + uint32_t countWord = 0u; + if (!tryReadWordFromGuest(rdram, runtime, pendingCountAddr, countWord)) + { + return; + } + + const uint32_t deltaBytes = currentAddr - pendingCountAddr; + uint32_t deltaQwords = 0u; + if (deltaBytes >= 16u) + { + deltaQwords = (deltaBytes >> 4u) - 1u; + } + + countWord = (countWord & 0xFFFF0000u) | (deltaQwords & 0xFFFFu); + writeGuestU32(rdram, runtime, pendingCountAddr, countWord); + } + + void writePacketBuilderCurrent(uint8_t *rdram, PS2Runtime *runtime, uint32_t stateAddr, uint32_t currentAddr) + { + writeGuestU32(rdram, runtime, stateAddr, currentAddr); + refreshPacketBuilderPendingCount(rdram, runtime, stateAddr); + } + + uint32_t reservePacketBuilderWords(uint8_t *rdram, PS2Runtime *runtime, uint32_t stateAddr, uint32_t wordCount) + { + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return 0u; + } + + const uint32_t reservedAddr = currentAddr; + currentAddr += wordCount * 4u; + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr); + return reservedAddr; + } + + void alignPacketBuilderState(uint8_t *rdram, + PS2Runtime *runtime, + uint32_t stateAddr, + uint32_t alignMode, + uint32_t reserveWords) + { + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return; + } + + const uint32_t adjusted = (alignMode + 2u) & 31u; + const uint32_t shift = (32u - adjusted) & 31u; + const uint32_t lowMask = 0xFFFFFFFFu >> shift; + const uint32_t alignedBase = currentAddr & ~lowMask; + uint32_t targetAddr = alignedBase + (reserveWords << 2u); + if (targetAddr < currentAddr) + { + targetAddr = (targetAddr + 1u) + lowMask; + } + + const uint32_t zero = 0u; + while (currentAddr < targetAddr) + { + writeGuestU32(rdram, runtime, currentAddr, zero); + currentAddr += 4u; + } + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr); + } + + void openPacketGifTag(uint8_t *rdram, + R5900Context *ctx, + PS2Runtime *runtime, + uint32_t stateAddr, + uint32_t openAddrOffset) + { + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return; + } + + writeGuestVec128(rdram, runtime, currentAddr, GPR_VEC(ctx, 5)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 16u); + writeGuestU32(rdram, runtime, stateAddr + openAddrOffset, currentAddr); + } + + void closePacketGifTag(uint8_t *rdram, PS2Runtime *runtime, uint32_t stateAddr, uint32_t openAddrOffset) + { + uint32_t openAddr = 0u; + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr + openAddrOffset, openAddr) || + !tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr) || + openAddr == 0u) + { + return; + } + + uint64_t tagValue = 0u; + if (!tryReadQwordFromGuest(rdram, runtime, openAddr, tagValue)) + { + return; + } + + uint32_t packetQwords = ((currentAddr - openAddr) >> 3u) - 2u; + const uint32_t flag = static_cast((tagValue >> 58u) & 0x3u); + if (flag != 1u) + { + packetQwords >>= 1u; + } + if (flag != 2u) + { + uint32_t nreg = static_cast((tagValue >> 60u) & 0xFu); + if (nreg == 0u) + { + nreg = 16u; + } + packetQwords = (packetQwords + nreg - 1u) / nreg; + } + + tagValue += static_cast(packetQwords); + writeGuestU32(rdram, runtime, stateAddr + openAddrOffset, 0u); + writeGuestU64(rdram, runtime, openAddr, tagValue); + + while ((currentAddr & 0xCu) != 0u) + { + writeGuestU32(rdram, runtime, currentAddr, 0u); + currentAddr += 4u; + } + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr); + } + } + + void sceGifPkAddGsAD(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return; + } + + const uint64_t dataValue = GPR_U64(ctx, 6); + const uint64_t regValue = static_cast(getRegU32(ctx, 5)); + writeGuestU64(rdram, runtime, currentAddr, dataValue); + writeGuestU64(rdram, runtime, currentAddr + 8u, regValue); + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 16u); + } + + void sceGifPkAddGsData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return; + } + + writeGuestU64(rdram, runtime, currentAddr, GPR_U64(ctx, 5)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 8u); + } + + void sceGifPkCloseGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)ctx; + closePacketGifTag(rdram, runtime, getRegU32(ctx, 4), 12u); + } + + void sceGifPkCnt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t countValue = getRegU32(ctx, 5); + const uint32_t extraValue = getRegU32(ctx, 6); + const uint32_t tagWord = getRegU32(ctx, 7) | 0x10000000u; + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[4] = {tagWord, 0u, countValue, extraValue}; + const uint32_t nextAddr = packetAddr + 16u; + + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, nextAddr); + } + + void sceGifPkEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t countValue = getRegU32(ctx, 5); + const uint32_t extraValue = getRegU32(ctx, 6); + const uint32_t tagWord = getRegU32(ctx, 7) | 0x70000000u; + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[4] = {tagWord, countValue, extraValue, 0u}; + const uint32_t nextAddr = packetAddr + 16u; + + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, nextAddr); + } + + void sceGifPkInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + initPacketBuilderState(rdram, ctx, runtime); + } + + void sceGifPkOpenGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + openPacketGifTag(rdram, ctx, runtime, getRegU32(ctx, 4), 12u); + } + + void sceGifPkRef(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t refAddr = getRegU32(ctx, 5) & 0x9FFFFFFFu; + const uint32_t tagWord = getRegU32(ctx, 9) | getRegU32(ctx, 6) | 0x30000000u; + const uint32_t extra0 = getRegU32(ctx, 7); + const uint32_t extra1 = getRegU32(ctx, 8); + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[4] = {tagWord, refAddr, extra0, extra1}; + + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 16u); + } + + void sceGifPkRefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t dbp = getRegU32(ctx, 5) & 0xFFFFu; + const uint32_t dpsm = getRegU32(ctx, 6) & 0xFFu; + const uint32_t dbw = getRegU32(ctx, 7) & 0xFFFFu; + uint32_t dataAddr = getRegU32(ctx, 8); + uint32_t qwcRemaining = getRegU32(ctx, 9); + const uint32_t dsax = getRegU32(ctx, 10); + const uint32_t dsay = getRegU32(ctx, 11); + const uint32_t width = readStackU32(rdram, ctx, 0); + const uint32_t height = readStackU32(rdram, ctx, 8); + + // Open a 4-register A+D GIF tag and emit the GS load-image setup. + { + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[4] = {0x10000000u, 0u, 0u, 0u}; + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 16u); + + const uint64_t giftag[2] = {makeGiftagAplusD(4u), 0xEULL}; + uint32_t currentAddr = packetAddr + 16u; + writeGuestBytes(rdram, runtime, currentAddr, reinterpret_cast(giftag), sizeof(giftag)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 16u); + writeGuestU32(rdram, runtime, stateAddr + 12u, currentAddr); + + const uint64_t bitbltbuf = + (static_cast(dbp) << 32u) | + (static_cast(dbw & 0xFFu) << 48u) | + (static_cast(dpsm) << 56u); + const uint64_t trxpos = + (static_cast(dsax) << 32u) | + (static_cast(dsay) << 48u); + const uint64_t trxreg = + static_cast(width) | + (static_cast(height) << 32u); + + { + uint32_t addr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, addr)) + { + return; + } + writeGuestU64(rdram, runtime, addr, bitbltbuf); + writeGuestU64(rdram, runtime, addr + 8u, static_cast(GS_REG_BITBLTBUF)); + addr += 16u; + writeGuestU64(rdram, runtime, addr, trxpos); + writeGuestU64(rdram, runtime, addr + 8u, static_cast(GS_REG_TRXPOS)); + addr += 16u; + writeGuestU64(rdram, runtime, addr, trxreg); + writeGuestU64(rdram, runtime, addr + 8u, static_cast(GS_REG_TRXREG)); + addr += 16u; + writeGuestU64(rdram, runtime, addr, 0u); + writeGuestU64(rdram, runtime, addr + 8u, static_cast(GS_REG_TRXDIR)); + addr += 16u; + writePacketBuilderCurrent(rdram, runtime, stateAddr, addr); + closePacketGifTag(rdram, runtime, stateAddr, 12u); + } + } + + while (qwcRemaining != 0u) + { + const uint32_t chunkQwc = std::min(qwcRemaining, 32767u); + + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[4] = {0x10000000u, 0u, 0u, 0u}; + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 16u); + + const uint32_t reservedAddr = reservePacketBuilderWords(rdram, runtime, stateAddr, 4u); + const bool isLastChunk = (chunkQwc == qwcRemaining); + const uint64_t gifTag = + static_cast(chunkQwc) | + (isLastChunk ? 0x0800000000008000ULL : 0x0800000000000000ULL); + writeGuestU64(rdram, runtime, reservedAddr, gifTag); + writeGuestU64(rdram, runtime, reservedAddr + 8u, 0u); + + const uint32_t refPacketAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t refWords[4] = {0x30000000u | chunkQwc, dataAddr & 0x9FFFFFFFu, 0u, 0u}; + writeGuestBytes(rdram, + runtime, + refPacketAddr, + reinterpret_cast(refWords), + sizeof(refWords)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, refPacketAddr + 16u); + + qwcRemaining -= chunkQwc; + dataAddr += chunkQwc * 16u; + } + } + + void sceGifPkReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + resetPacketBuilderState(rdram, ctx, runtime); + } + + void sceGifPkReserve(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, reservePacketBuilderWords(rdram, runtime, getRegU32(ctx, 4), getRegU32(ctx, 5))); + } + + void sceGifPkTerminate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, terminatePacketBuilderState(rdram, ctx, runtime)); + } + + static void resetGsSyncVState() + { + std::lock_guard lock(g_gs_sync_v_mutex); + g_gs_sync_v_base_tick = ps2_syscalls::GetCurrentVSyncTick(); + } + + static int32_t getGsSyncVFieldForTick(uint64_t tick) + { + std::lock_guard lock(g_gs_sync_v_mutex); + if (tick <= g_gs_sync_v_base_tick) + { + return 0; + } + + return static_cast((tick - g_gs_sync_v_base_tick - 1u) & 1u); + } + + void resetGsSyncVCallbackState() + { + { + std::lock_guard lock(g_gs_sync_v_callback_mutex); + g_gs_sync_v_callback_func = 0u; + g_gs_sync_v_callback_gp = 0u; + g_gs_sync_v_callback_sp = 0u; + g_gs_sync_v_callback_stack_base = 0u; + g_gs_sync_v_callback_stack_top = 0u; + g_gs_sync_v_callback_bad_pc_logs = 0u; + } + resetGsSyncVState(); + } + + void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick) + { + if (!rdram || !runtime) + { + return; + } + + uint32_t callback = 0u; + uint32_t gp = 0u; + uint32_t callbackStackTop = 0u; + const uint64_t callbackTick = (tick != 0u) ? tick : ps2_syscalls::GetCurrentVSyncTick(); + { + std::lock_guard lock(g_gs_sync_v_callback_mutex); + callback = g_gs_sync_v_callback_func; + gp = g_gs_sync_v_callback_gp; + callbackStackTop = g_gs_sync_v_callback_stack_top; + if (callback == 0u) + { + return; + } + } + + if (!runtime->hasFunction(callback)) + { + static uint32_t s_missingCallbackLogCount = 0u; + if (s_missingCallbackLogCount < 32u) + { + std::cerr << "[sceGsSyncVCallback:missing] cb=0x" << std::hex << callback + << " gp=0x" << gp + << " tick=0x" << callbackTick + << std::dec << std::endl; + ++s_missingCallbackLogCount; + } + return; + } + + if (callbackStackTop == 0u) + { + constexpr uint32_t kCallbackStackSize = 0x4000u; + const uint32_t stackTop = runtime->reserveAsyncCallbackStack(kCallbackStackSize, 16u); + if (stackTop != 0u) + { + std::lock_guard lock(g_gs_sync_v_callback_mutex); + if (g_gs_sync_v_callback_stack_top == 0u) + { + g_gs_sync_v_callback_stack_base = stackTop - (kCallbackStackSize - 0x10u); + g_gs_sync_v_callback_stack_top = stackTop; + } + callbackStackTop = g_gs_sync_v_callback_stack_top; + } + } + + try + { + R5900Context callbackCtx{}; + SET_GPR_U32(&callbackCtx, 28, gp); + SET_GPR_U32(&callbackCtx, 29, (callbackStackTop != 0u) ? callbackStackTop : (PS2_RAM_SIZE - 0x10u)); + SET_GPR_U32(&callbackCtx, 31, 0u); + SET_GPR_U32(&callbackCtx, 4, static_cast(callbackTick)); + callbackCtx.pc = callback; + + static uint32_t s_dispatchLogCount = 0u; + const bool shouldLogDispatch = (s_dispatchLogCount < 64u); + if (shouldLogDispatch) + { + RUNTIME_LOG("[sceGsSyncVCallback:dispatch] cb=0x" << std::hex << callback + << " gp=0x" << gp + << " sp=0x" << getRegU32(&callbackCtx, 29) + << " tick=0x" << callbackTick + << std::dec << std::endl); + } + + uint32_t steps = 0u; + while (callbackCtx.pc != 0u && !runtime->isStopRequested() && steps < 1024u) + { + if (!runtime->hasFunction(callbackCtx.pc)) + { + if (g_gs_sync_v_callback_bad_pc_logs < 16u) + { + std::cerr << "[sceGsSyncVCallback:bad-pc] pc=0x" << std::hex << callbackCtx.pc + << " ra=0x" << getRegU32(&callbackCtx, 31) + << " sp=0x" << getRegU32(&callbackCtx, 29) + << " gp=0x" << getRegU32(&callbackCtx, 28) + << std::dec << std::endl; + ++g_gs_sync_v_callback_bad_pc_logs; + } + callbackCtx.pc = 0u; + break; + } + + auto step = runtime->lookupFunction(callbackCtx.pc); + if (!step) + { + break; + } + ++steps; + step(rdram, &callbackCtx, runtime); + } + + if (shouldLogDispatch) + { + RUNTIME_LOG("[sceGsSyncVCallback:return] cb=0x" << std::hex << callback + << " finalPc=0x" << callbackCtx.pc + << " ra=0x" << getRegU32(&callbackCtx, 31) + << " steps=0x" << steps + << std::dec << std::endl); + ++s_dispatchLogCount; + } + } + catch (const std::exception &e) + { + static uint32_t warnCount = 0u; + if (warnCount < 8u) + { + std::cerr << "[sceGsSyncVCallback] callback exception: " << e.what() << std::endl; + ++warnCount; + } + } + } + + void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t imgAddr = getRegU32(ctx, 4); + uint32_t srcAddr = getRegU32(ctx, 5); + + GsImageMem img{}; + if (!runtime || !runtime->syncCoreSubsystems() || !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); + const uint32_t totalImageBytes = rowBytes * static_cast(img.height); + const uint32_t headerQwc = 6u; + const uint32_t imageQwc = (totalImageBytes + 15u) / 16u; + const uint32_t totalQwc = headerQwc + imageQwc; + + uint32_t pktAddr = runtime->guestMalloc(totalQwc * 16u, 16u); + if (pktAddr == 0) + { + setReturnS32(ctx, -1); + return; + } + + uint8_t *pkt = getMemPtr(rdram, pktAddr); + const uint8_t *src = getConstMemPtr(rdram, srcAddr); + if (!pkt || !src) + { + runtime->guestFree(pktAddr); + setReturnS32(ctx, -1); + return; + } + + uint32_t dbp = (static_cast(img.vram_addr) * 2048u) / 256u; + uint32_t dsax = static_cast(img.x); + uint32_t dsay = static_cast(img.y); + + // Full messy + uint64_t *q = reinterpret_cast(pkt); + q[0] = makeGiftagAplusD(4u); + q[1] = 0xEULL; + q[2] = (static_cast(img.psm & 0x3Fu) << 24) | (static_cast(1u) << 16) | + (static_cast(dbp & 0x3FFFu) << 32) | (static_cast(fbw & 0x3Fu) << 48) | + (static_cast(img.psm & 0x3Fu) << 56); + q[3] = 0x50ULL; + q[4] = (static_cast(dsay & 0x7FFu) << 48) | (static_cast(dsax & 0x7FFu) << 32); + q[5] = 0x51ULL; + q[6] = (static_cast(img.height) << 32) | static_cast(img.width); + q[7] = 0x52ULL; + q[8] = 0ULL; + q[9] = 0x53ULL; + q[10] = (static_cast(2) << 58) | (static_cast(imageQwc) & 0x7FFF) | + (1ULL << 15); + q[11] = 0ULL; + + std::memcpy(pkt + headerQwc * 16u, src, totalImageBytes); + + constexpr uint32_t GIF_CHANNEL = 0x1000A000; + constexpr uint32_t CHCR_STR_MODE0 = 0x101u; + auto &mem = runtime->memory(); + mem.writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); + mem.writeIORegister(GIF_CHANNEL + 0x20u, totalQwc & 0xFFFFu); + mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); + mem.processPendingTransfers(); + runtime->guestFree(pktAddr); + + setReturnS32(ctx, 0); + } + + void sceGsExecStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t imgAddr = getRegU32(ctx, 4); + uint32_t dstAddr = getRegU32(ctx, 5); + + GsImageMem img{}; + if (!runtime || !runtime->syncCoreSubsystems() || !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); + const uint32_t totalImageBytes = rowBytes * static_cast(img.height); + + uint8_t *dst = getMemPtr(rdram, dstAddr); + if (!dst) + { + setReturnS32(ctx, -1); + return; + } + + uint32_t sbp = (static_cast(img.vram_addr) * 2048u) / 256u; + uint64_t bitbltbuf = (static_cast(sbp & 0x3FFFu) << 0) | + (static_cast(fbw & 0x3Fu) << 16) | + (static_cast(img.psm & 0x3Fu) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(0u) << 56); + uint64_t trxpos = (static_cast(img.x & 0x7FFu) << 0) | + (static_cast(img.y & 0x7FFu) << 16) | + (static_cast(0u) << 32) | + (static_cast(0u) << 48); + uint64_t trxreg = static_cast(img.height) << 32 | static_cast(img.width); + + uint32_t pktAddr = runtime->guestMalloc(80u, 16u); + if (pktAddr == 0) + { + setReturnS32(ctx, -1); + return; + } + + uint8_t *pkt = getMemPtr(rdram, pktAddr); + if (!pkt) + { + runtime->guestFree(pktAddr); + setReturnS32(ctx, -1); + return; + } + + uint64_t *q = reinterpret_cast(pkt); + q[0] = makeGiftagAplusD(4u); + q[1] = 0xEULL; + q[2] = bitbltbuf; + q[3] = 0x50ULL; + q[4] = trxpos; + q[5] = 0x51ULL; + q[6] = trxreg; + q[7] = 0x52ULL; + q[8] = 1ULL; + q[9] = 0x53ULL; + + constexpr uint32_t GIF_CHANNEL = 0x1000A000; + constexpr uint32_t CHCR_STR_MODE0 = 0x101u; + auto &mem = runtime->memory(); + mem.writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); + mem.writeIORegister(GIF_CHANNEL + 0x20u, 5u); + mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); + mem.processPendingTransfers(); + + runtime->gs().consumeLocalToHostBytes(dst, totalImageBytes); + runtime->guestFree(pktAddr); + + setReturnS32(ctx, 0); + } + + void sceGsGetGParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t addr = writeGsGParamToScratch(runtime); + setReturnU32(ctx, addr); + } + + void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t envAddr = getRegU32(ctx, 4); + GsDispEnvMem env{}; + if (!readGsDispEnv(rdram, envAddr, env)) + { + setReturnS32(ctx, -1); + return; + } + applyGsDispEnv(runtime, env); + setReturnS32(ctx, 0); + } + + void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t envAddr = getRegU32(ctx, 4); + GsRegPairMem pairs[8]{}; + if (!readGsRegPairs(rdram, envAddr, pairs, 8u)) + { + setReturnS32(ctx, -1); + return; + } + applyGsRegPairs(runtime, pairs, 8u); + setReturnS32(ctx, 0); + } + + void sceGsResetGraph(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + if (runtime && !runtime->syncCoreSubsystems()) + { + setReturnS32(ctx, -1); + return; + } + + g_gparam.interlace = static_cast(interlace & 0x1); + g_gparam.omode = static_cast(omode & 0xFF); + g_gparam.ffmode = static_cast(ffmode & 0x1); + writeGsGParamToScratch(runtime); + resetGsSyncVState(); + + uint64_t pmode = makePmode(1, 0, 0, 0, 0, 0x80); + uint64_t smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1); + uint64_t dispfb = makeDispFb(0, 10, 0, 0, 0); + uint64_t display = makeDisplay(0, 0, 0, 0, 639, 447); + uint64_t bgcolor = 0ULL; + + if (runtime) + { + uint32_t pktAddr = runtime->guestMalloc(128u, 16u); + if (pktAddr != 0u) + { + uint8_t *pkt = getMemPtr(rdram, pktAddr); + if (pkt) + { + uint64_t *q = reinterpret_cast(pkt); + q[0] = makeGiftagAplusD(7u); + q[1] = 0xEULL; + q[2] = pmode; + q[3] = 0x41ULL; + q[4] = smode2; + q[5] = 0x42ULL; + q[6] = dispfb; + q[7] = 0x59ULL; + q[8] = display; + q[9] = 0x5aULL; + q[10] = dispfb; + q[11] = 0x5bULL; + q[12] = display; + q[13] = 0x5cULL; + q[14] = bgcolor; + q[15] = 0x5fULL; + constexpr uint32_t GIF_CHANNEL = 0x1000A000; + constexpr uint32_t CHCR_STR_MODE0 = 0x101u; + auto &mem = runtime->memory(); + mem.writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); + mem.writeIORegister(GIF_CHANNEL + 0x20u, 8u); + mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); + mem.processPendingTransfers(); + runtime->guestFree(pktAddr); + } + else + { + runtime->guestFree(pktAddr); + } + } + } + } + + setReturnS32(ctx, 0); + } + + void sceGsResetPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)ctx; + (void)runtime; + setReturnS32(ctx, 0); + } + + void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t envAddr = getRegU32(ctx, 4); + uint32_t psm = getRegU32(ctx, 5); + uint32_t w = getRegU32(ctx, 6); + uint32_t h = getRegU32(ctx, 7); + const GsTrailingArgs3 trailing = decodeGsTrailingArgs3(rdram, ctx); + const uint32_t ztest = trailing.arg0; + const uint32_t zpsm = trailing.arg1; + const uint32_t clear = trailing.arg2; + + if (w == 0u) + { + w = 640u; + } + if (h == 0u) + { + h = 448u; + } + + const uint32_t fbw = std::max(1u, (w + 63u) / 64u); + const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u); + const uint64_t smode2 = + (static_cast(g_gparam.interlace & 0x1u) << 0) | + (static_cast(g_gparam.ffmode & 0x1u) << 1); + const uint64_t display = makeDisplay(636u, 32u, 0u, 0u, w - 1u, h - 1u); + + const int32_t drawWidth = static_cast(w); + const int32_t drawHeight = static_cast(h); + + uint32_t zbufAddr = 0u; + { + R5900Context temp = *ctx; + sceGszbufaddr(rdram, &temp, runtime); + zbufAddr = getRegU32(&temp, 2); + } + + const uint32_t fbp1 = zbufAddr; + const uint64_t dispfb0 = makeDispFb(fbp1, fbw, psm, 0u, 0u); + const uint64_t dispfb1 = makeDispFb(0u, fbw, psm, 0u, 0u); + + GsDBuffDcMem db{}; + db.disp[0].pmode = pmode; + db.disp[0].smode2 = smode2; + db.disp[0].dispfb = dispfb0; + db.disp[0].display = display; + db.disp[0].bgcolor = 0u; + db.disp[1] = db.disp[0]; + db.disp[1].dispfb = dispfb1; + + const bool seedClear = clear != 0u; + db.giftag0 = {makeGiftagAplusD(seedClear ? 22u : 16u), 0xEULL}; + seedGsDrawEnv1(db.draw01, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); + seedGsDrawEnv2(db.draw02, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); + db.giftag1 = db.giftag0; + seedGsDrawEnv1(db.draw11, drawWidth, drawHeight, fbp1, fbw, psm, zbufAddr, zpsm, ztest, false); + seedGsDrawEnv2(db.draw12, drawWidth, drawHeight, fbp1, fbw, psm, zbufAddr, zpsm, ztest, false); + if (seedClear) + { + seedGsClearPacket(db.clear0, drawWidth, drawHeight, 0u, ztest, false); + seedGsClearPacket(db.clear1, drawWidth, drawHeight, 0u, ztest, true); + } + + if (!writeGsDBuffDc(rdram, envAddr, db)) + { + setReturnS32(ctx, -1); + return; + } + setReturnS32(ctx, 0); + } + + void sceGsSetDefDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t envAddr = getRegU32(ctx, 4); + uint32_t psm = getRegU32(ctx, 5); + uint32_t w = getRegU32(ctx, 6); + uint32_t h = getRegU32(ctx, 7); + const uint32_t ztest = readStackU32(rdram, ctx, 16); + const uint32_t zpsm = readStackU32(rdram, ctx, 20); + const uint32_t clear = readStackU32(rdram, ctx, 24); + (void)clear; + + if (w == 0u) + { + w = 640u; + } + if (h == 0u) + { + h = 448u; + } + + const uint32_t fbw = std::max(1u, (w + 63u) / 64u); + const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u); + const uint64_t smode2 = + (static_cast(g_gparam.interlace & 0x1u) << 0) | + (static_cast(g_gparam.ffmode & 0x1u) << 1); + const uint64_t dispfb = makeDispFb(0u, fbw, psm, 0u, 0u); + const uint64_t display = makeDisplay(636u, 32u, 0u, 0u, w - 1u, h - 1u); + + const int32_t drawWidth = static_cast(w); + const int32_t drawHeight = static_cast(h); + + uint32_t zbufAddr = 0u; + { + R5900Context temp = *ctx; + sceGszbufaddr(rdram, &temp, runtime); + zbufAddr = getRegU32(&temp, 2); + } + + GsDBuffMem db{}; + db.disp[0].pmode = pmode; + db.disp[0].smode2 = smode2; + db.disp[0].dispfb = dispfb; + db.disp[0].display = display; + db.disp[0].bgcolor = 0u; + db.disp[1] = db.disp[0]; + + db.giftag0 = {makeGiftagAplusD(14u), 0x0E0E0E0E0E0E0E0EULL}; + seedGsDrawEnv1(db.draw0, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); + db.giftag1 = db.giftag0; + seedGsDrawEnv1(db.draw1, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); + + if (!writeGsDBuff(rdram, envAddr, db)) + { + setReturnS32(ctx, -1); + return; + } + setReturnS32(ctx, 0); + } + + void sceGsSetDefDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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); + const GsTrailingArgs2 trailing = decodeGsTrailingArgs2(rdram, ctx); + uint32_t dx = trailing.arg0; + uint32_t dy = trailing.arg1; + + 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) + { + uint32_t envAddr = getRegU32(ctx, 4); + uint32_t param_2 = getRegU32(ctx, 5); + int32_t w = static_cast(static_cast(getRegU32(ctx, 6) & 0xFFFF)); + int32_t h = static_cast(static_cast(getRegU32(ctx, 7) & 0xFFFF)); + const GsTrailingArgs2 trailing = decodeGsTrailingArgs2(rdram, ctx); + uint32_t param_5 = trailing.arg0; + uint32_t param_6 = trailing.arg1; + + if (w <= 0) + w = 640; + if (h <= 0) + h = 448; + + uint32_t psm = param_2 & 0xFU; + uint32_t fbw = ((static_cast(w) + 63u) >> 6) & 0x3FU; + sceGszbufaddr(rdram, ctx, runtime); + int32_t zbuf = static_cast(static_cast(getRegU32(ctx, 2) & 0xFFFF)); + + GsDrawEnv1Mem env{}; + seedGsDrawEnv1(env, + w, + h, + 0u, + fbw, + psm, + static_cast(zbuf), + param_6 & 0xFu, + param_5 & 0x3u, + (param_2 & 2u) != 0u); + + uint8_t *const ptr = getMemPtr(rdram, envAddr); + if (!ptr) + { + setReturnS32(ctx, 8); + return; + } + std::memcpy(ptr, &env, sizeof(env)); + + setReturnS32(ctx, 8); + } + + void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t envAddr = getRegU32(ctx, 4); + uint32_t param_2 = getRegU32(ctx, 5); + int32_t w = static_cast(static_cast(getRegU32(ctx, 6) & 0xFFFF)); + int32_t h = static_cast(static_cast(getRegU32(ctx, 7) & 0xFFFF)); + const GsTrailingArgs2 trailing = decodeGsTrailingArgs2(rdram, ctx); + uint32_t param_5 = trailing.arg0; + uint32_t param_6 = trailing.arg1; + + if (w <= 0) + w = 640; + if (h <= 0) + h = 448; + + uint32_t psm = param_2 & 0xFU; + uint32_t fbw = ((static_cast(w) + 63u) >> 6) & 0x3FU; + sceGszbufaddr(rdram, ctx, runtime); + int32_t zbuf = static_cast(static_cast(getRegU32(ctx, 2) & 0xFFFF)); + + GsDrawEnv2Mem env{}; + seedGsDrawEnv2(env, + w, + h, + 0u, + fbw, + psm, + static_cast(zbuf), + param_6 & 0xFu, + param_5 & 0x3u, + (param_2 & 2u) != 0u); + + uint8_t *const ptr = getMemPtr(rdram, envAddr); + if (!ptr) + { + setReturnS32(ctx, 8); + return; + } + + std::memcpy(ptr, &env, sizeof(env)); + setReturnS32(ctx, 8); + } + + void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + sceGsSetDefLoadImage(rdram, ctx, runtime); + } + + void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t envAddr = getRegU32(ctx, 4); + const uint32_t which = getRegU32(ctx, 5) & 1u; + + GsDBuffDcMem db{}; + if (!runtime || !readGsDBuffDc(rdram, envAddr, db)) + { + setReturnS32(ctx, -1); + return; + } + + const bool hasClearPacket = (which == 0u) ? hasSeededGsClearPacket(db.clear0) + : hasSeededGsClearPacket(db.clear1); + const uint64_t debugDrawFrameReg = (which == 0u) ? db.draw01.frame1.value + : db.draw11.frame1.value; + + applyGsDispEnv(runtime, db.disp[which]); + static uint32_t s_swapDbuffLogCount = 0u; + if (s_swapDbuffLogCount < 32u) + { + const uint32_t dispFbp = static_cast(db.disp[which].dispfb & 0x1FFu); + const uint32_t clearContext = (which == 0u) + ? static_cast((db.clear0.prim.value >> 9) & 0x1u) + : static_cast((db.clear1.prim.value >> 9) & 0x1u); + RUNTIME_LOG("[gs:swapdbuff] which=" << which + << " env=0x" << std::hex << envAddr + << " dispfb=0x" << db.disp[which].dispfb + << " display=0x" << db.disp[which].display + << " pmode=0x" << db.disp[which].pmode + << " dispFbp=" << dispFbp + << " clearCtxt=" << clearContext + << std::dec << std::endl); + ++s_swapDbuffLogCount; + } + logSwapProbeStage(runtime, "swap-pre", which, debugDrawFrameReg, db.disp[which].dispfb, hasClearPacket); + if (which == 0u) + { + applyGsRegPairs(runtime, reinterpret_cast(&db.draw01), 8u); + applyGsRegPairs(runtime, reinterpret_cast(&db.draw02), 8u); + if (hasSeededGsClearPacket(db.clear0)) + { + const uint32_t clearContext = static_cast((db.clear0.prim.value >> 9) & 0x1u); + runtime->gs().clearFramebufferContext(clearContext, static_cast(db.clear0.rgbaq.value)); + logSwapProbeStage(runtime, "swap-post-clear", which, db.draw01.frame1.value, db.disp[which].dispfb, true); + } + applyGsClearPacket(runtime, db.clear0); + logSwapProbeStage(runtime, "swap-post", which, db.draw01.frame1.value, db.disp[which].dispfb, hasClearPacket); + } + else + { + applyGsRegPairs(runtime, reinterpret_cast(&db.draw11), 8u); + applyGsRegPairs(runtime, reinterpret_cast(&db.draw12), 8u); + if (hasSeededGsClearPacket(db.clear1)) + { + const uint32_t clearContext = static_cast((db.clear1.prim.value >> 9) & 0x1u); + runtime->gs().clearFramebufferContext(clearContext, static_cast(db.clear1.rgbaq.value)); + logSwapProbeStage(runtime, "swap-post-clear", which, db.draw11.frame1.value, db.disp[which].dispfb, true); + } + applyGsClearPacket(runtime, db.clear1); + logSwapProbeStage(runtime, "swap-post", which, db.draw11.frame1.value, db.disp[which].dispfb, hasClearPacket); + } + + setReturnS32(ctx, static_cast(which ^ 1u)); + } + + void sceGsSwapDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t envAddr = getRegU32(ctx, 4); + const uint32_t which = getRegU32(ctx, 5) & 1u; + + GsDBuffMem db{}; + if (!runtime || !readGsDBuff(rdram, envAddr, db)) + { + setReturnS32(ctx, -1); + return; + } + + applyGsDispEnv(runtime, db.disp[which]); + if (which == 0u) + { + applyGsRegPairs(runtime, reinterpret_cast(&db.draw0), 8u); + } + else + { + applyGsRegPairs(runtime, reinterpret_cast(&db.draw1), 8u); + } + + setReturnS32(ctx, static_cast(which ^ 1u)); + } + + void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int32_t mode = static_cast(getRegU32(ctx, 4)); + auto &mem = runtime->memory(); + + if (mode == 0) + { + mem.processPendingTransfers(); + + uint32_t count = 0; + constexpr uint32_t kTimeout = 0x1000000; + + while ((mem.readIORegister(0x10009000) & 0x100) != 0) + { + if (++count > kTimeout) + { + setReturnS32(ctx, -1); + return; + } + } + + while ((mem.readIORegister(0x1000A000) & 0x100) != 0) + { + if (++count > kTimeout) + { + setReturnS32(ctx, -1); + return; + } + } + + while ((mem.readIORegister(0x10003C00) & 0x1F000003) != 0) + { + if (++count > kTimeout) + { + setReturnS32(ctx, -1); + return; + } + } + + while ((mem.readIORegister(0x10003020) & 0xC00) != 0) + { + if (++count > kTimeout) + { + setReturnS32(ctx, -1); + return; + } + } + + setReturnS32(ctx, 0); + } + else + { + uint32_t result = 0; + + if ((mem.readIORegister(0x10009000) & 0x100) != 0) + result |= 1; + if ((mem.readIORegister(0x1000A000) & 0x100) != 0) + result |= 2; + if ((mem.readIORegister(0x10003C00) & 0x1F000003) != 0) + result |= 4; + if ((mem.readIORegister(0x10003020) & 0xC00) != 0) + result |= 0x10; + + setReturnS32(ctx, result); + } + } + + void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint64_t tick = ps2_syscalls::WaitForNextVSyncTick(rdram, runtime); + if (g_gparam.interlace != 0u) + { + setReturnS32(ctx, getGsSyncVFieldForTick(tick)); + return; + } + + setReturnS32(ctx, 1); + } + + void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t newCallback = getRegU32(ctx, 4); + const uint32_t callerPc = ctx ? ctx->pc : 0u; + const uint32_t callerRa = ctx ? getRegU32(ctx, 31) : 0u; + const uint32_t gp = getRegU32(ctx, 28); + const uint32_t sp = getRegU32(ctx, 29); + + uint32_t oldCallback = 0u; + { + std::lock_guard lock(g_gs_sync_v_callback_mutex); + oldCallback = g_gs_sync_v_callback_func; + g_gs_sync_v_callback_func = newCallback; + if (newCallback != 0u) + { + g_gs_sync_v_callback_gp = gp; + g_gs_sync_v_callback_sp = sp; + } + } + + static uint32_t s_syncVCallbackLogCount = 0u; + if (s_syncVCallbackLogCount < 128u) + { + RUNTIME_LOG("[sceGsSyncVCallback:set] new=0x" << std::hex << newCallback + << " old=0x" << oldCallback + << " callerPc=0x" << callerPc + << " callerRa=0x" << callerRa + << " gp=0x" << gp + << " sp=0x" << sp + << std::dec << std::endl); + ++s_syncVCallbackLogCount; + } + + if (newCallback != 0u) + { + ps2_syscalls::EnsureVSyncWorkerRunning(rdram, runtime); + } + + setReturnU32(ctx, oldCallback); + } + + void sceGszbufaddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + uint32_t param_1 = getRegU32(ctx, 4); + int32_t w = static_cast(static_cast(getRegU32(ctx, 6) & 0xFFFF)); + int32_t h = static_cast(static_cast(getRegU32(ctx, 7) & 0xFFFF)); + + int32_t width_blocks = (w + 63) >> 6; + if (w + 63 < 0) + width_blocks = (w + 126) >> 6; + + int32_t height_blocks; + if ((param_1 & 2) != 0) + { + int32_t v = (h + 63) >> 6; + if (h + 63 < 0) + v = (h + 126) >> 6; + height_blocks = v; + } + else + { + int32_t v = (h + 31) >> 5; + if (h + 31 < 0) + v = (h + 62) >> 5; + height_blocks = v; + } + + int32_t product = width_blocks * height_blocks; + + uint64_t gparam_val = 0; + if (runtime) + { + uint8_t *scratch = runtime->memory().getScratchpad(); + if (scratch) + { + std::memcpy(&gparam_val, scratch + 0x100, sizeof(gparam_val)); + } + } + if ((gparam_val & 0xFFFF0000FFFFULL) == 1ULL) + product = (product * 0x10000) >> 16; + else + product = (product * 0x20000) >> 16; + + setReturnS32(ctx, product); + } + + void Ps2SwapDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + if (logCount < 8) + { + RUNTIME_LOG("ps2_stub Ps2SwapDBuff"); + ++logCount; + } + setReturnS32(ctx, 0); + } + + void sceVif1PkAddGsAD(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return; + } + + const uint64_t dataValue = GPR_U64(ctx, 6); + const uint32_t words[4] = { + static_cast(dataValue & 0xFFFFFFFFu), + static_cast(dataValue >> 32u), + getRegU32(ctx, 5), + 0u, + }; + writeGuestBytes(rdram, + runtime, + currentAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 16u); + } + + void sceVif1PkAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + alignPacketBuilderState(rdram, + runtime, + getRegU32(ctx, 4), + getRegU32(ctx, 5), + getRegU32(ctx, 6)); + } + + void sceVif1PkCall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t refAddr = getRegU32(ctx, 5) & 0x9FFFFFFFu; + const uint32_t tagWord = getRegU32(ctx, 6) | 0x50000000u; + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[2] = {tagWord, refAddr}; + + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 8u); + writeGuestU32(rdram, runtime, stateAddr + 12u, 0u); + } + + void sceVif1PkCloseDirectCode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + uint32_t currentAddr = 0u; + uint32_t openAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr) || + !tryReadWordFromGuest(rdram, runtime, stateAddr + 12u, openAddr) || + openAddr == 0u) + { + return; + } + + const uint32_t currentMinusTag = currentAddr - 4u; + const uint32_t wordCount = (currentMinusTag - openAddr) >> 2u; + const uint32_t qwordCount = wordCount >> 2u; + uint32_t tagWord = 0u; + if (!tryReadWordFromGuest(rdram, runtime, openAddr, tagWord)) + { + return; + } + + logVif1PacketStateOp("close-direct", ctx, stateAddr, currentAddr, openAddr, qwordCount); + + tagWord += qwordCount; + writeGuestU32(rdram, runtime, stateAddr + 12u, 0u); + writeGuestU32(rdram, runtime, openAddr, tagWord); + } + + void sceVif1PkCloseGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)ctx; + closePacketGifTag(rdram, runtime, getRegU32(ctx, 4), 20u); + } + + void sceVif1PkCnt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t tagWord = getRegU32(ctx, 5) | 0x10000000u; + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[2] = {tagWord, 0u}; + + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writeGuestU32(rdram, runtime, stateAddr + 12u, 0u); + writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 8u); + } + + void sceVif1PkEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t tagWord = getRegU32(ctx, 5) | 0x70000000u; + const uint32_t packetAddr = terminatePacketBuilderState(rdram, ctx, runtime); + const uint32_t words[2] = {tagWord, 0u}; + + writeGuestU32(rdram, runtime, stateAddr + 8u, packetAddr); + writeGuestBytes(rdram, + runtime, + packetAddr, + reinterpret_cast(words), + sizeof(words)); + writeGuestU32(rdram, runtime, stateAddr + 12u, 0u); + writePacketBuilderCurrent(rdram, runtime, stateAddr, packetAddr + 8u); + } + + void sceVif1PkInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + initPacketBuilderState(rdram, ctx, runtime); + writeGuestU32(rdram, runtime, getRegU32(ctx, 4) + 20u, 0u); + } + + void sceVif1PkOpenDirectCode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + alignPacketBuilderState(rdram, runtime, stateAddr, 2u, 3u); + + uint32_t currentAddr = 0u; + if (!tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr)) + { + return; + } + + const uint32_t tagWord = (getRegU32(ctx, 5) != 0u) ? 0xD0000000u : 0x50000000u; + writeGuestU32(rdram, runtime, currentAddr, tagWord); + writePacketBuilderCurrent(rdram, runtime, stateAddr, currentAddr + 4u); + writeGuestU32(rdram, runtime, stateAddr + 12u, currentAddr); + logVif1PacketStateOp("open-direct", ctx, stateAddr, currentAddr + 4u, currentAddr, tagWord); + } + + void sceVif1PkOpenGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + openPacketGifTag(rdram, ctx, runtime, getRegU32(ctx, 4), 20u); + } + + void sceVif1PkReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + resetPacketBuilderState(rdram, ctx, runtime); + writeGuestU32(rdram, runtime, getRegU32(ctx, 4) + 20u, 0u); + } + + void sceVif1PkReserve(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t stateAddr = getRegU32(ctx, 4); + const uint32_t wordCount = getRegU32(ctx, 5); + uint32_t currentAddr = 0u; + tryReadWordFromGuest(rdram, runtime, stateAddr, currentAddr); + const uint32_t reservedAddr = reservePacketBuilderWords(rdram, runtime, stateAddr, wordCount); + logVif1PacketStateOp("reserve", ctx, stateAddr, currentAddr, reservedAddr, wordCount); + setReturnU32(ctx, reservedAddr); + } + + void sceVif1PkTerminate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, terminatePacketBuilderState(rdram, ctx, runtime)); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/GS.h b/ps2xRuntime/src/lib/Kernel/Stubs/GS.h new file mode 100644 index 00000000..8cc2e377 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/GS.h @@ -0,0 +1,56 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void resetGsSyncVCallbackState(); + void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick); + void sceGifPkAddGsAD(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkAddGsData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkCloseGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkCnt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkOpenGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkRef(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkRefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkReserve(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGifPkTerminate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsExecStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsGetGParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsResetGraph(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsResetPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSetDefStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSwapDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceGszbufaddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void Ps2SwapDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkAddGsAD(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkCall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkCloseDirectCode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkCloseGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkCnt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkOpenDirectCode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkOpenGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkReserve(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVif1PkTerminate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl b/ps2xRuntime/src/lib/Kernel/Stubs/Helpers/Support.h similarity index 85% rename from ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl rename to ps2xRuntime/src/lib/Kernel/Stubs/Helpers/Support.h index d09bf1cb..0b746cdb 100644 --- a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Helpers/Support.h @@ -1,6 +1,5 @@ -#ifndef PS2_CD_REMAP_IDX_TO_AFS -#define PS2_CD_REMAP_IDX_TO_AFS 1 -#endif +#include +#include namespace { @@ -17,12 +16,10 @@ namespace std::unordered_map g_cdFilesByKey; std::unordered_map g_cdLeafIndex; + std::unordered_map g_cdLoosePathIndex; std::filesystem::path g_cdLeafIndexRoot; bool g_cdLeafIndexBuilt = false; uint32_t g_nextPseudoLbn = kCdPseudoLbnStart; - std::filesystem::path g_cdAutoImagePath; - std::filesystem::path g_cdAutoImageRoot; - bool g_cdAutoImageSearched = false; std::filesystem::path g_cdImageSizePath; uint64_t g_cdImageSizeBytes = 0; bool g_cdImageSizeValid = false; @@ -109,167 +106,83 @@ namespace return path; } - std::filesystem::path getCdRootPath() + std::string normalizeCdLooseNumericKey(std::string value) { - 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(); - } - - bool hasCdImageExtension(const std::filesystem::path &path) - { - const std::string ext = toLowerAscii(path.extension().string()); - return ext == ".iso" || ext == ".bin" || ext == ".img" || ext == ".mdf" || ext == ".nrg"; - } - - bool trySelectBestDiscImageFromDirectory(const std::filesystem::path &dir, - std::filesystem::path &pathOut) - { - std::error_code ec; - if (!std::filesystem::exists(dir, ec) || ec || !std::filesystem::is_directory(dir, ec)) - { - return false; - } - - std::filesystem::path bestPath; - uint64_t bestSize = 0; - for (const auto &entry : std::filesystem::directory_iterator( - dir, std::filesystem::directory_options::skip_permission_denied, ec)) + value = toLowerAscii(stripIsoVersionSuffix(normalizePathSeparators(std::move(value)))); + std::string normalized; + normalized.reserve(value.size()); + for (std::size_t i = 0; i < value.size();) { - if (ec) - { - break; - } - - if (!entry.is_regular_file()) - { - continue; - } - if (!hasCdImageExtension(entry.path())) + if (!std::isdigit(static_cast(value[i]))) { + normalized.push_back(value[i]); + ++i; continue; } - std::error_code sizeEc; - const uint64_t size = static_cast(entry.file_size(sizeEc)); - if (sizeEc || size < (64ull * 1024ull * 1024ull)) + std::size_t end = i + 1; + while (end < value.size() && std::isdigit(static_cast(value[end]))) { - continue; + ++end; } - if (size > bestSize) + std::size_t firstNonZero = i; + while (firstNonZero + 1 < end && value[firstNonZero] == '0') { - bestSize = size; - bestPath = entry.path(); + ++firstNonZero; } - } - if (bestPath.empty()) - { - return false; + normalized.append(value, firstNonZero, end - firstNonZero); + i = end; } - pathOut = bestPath; - return true; + return normalized; } - std::filesystem::path autoDetectCdImagePath() + std::string cdLoosePathKeyFromRelative(const std::filesystem::path &relative) { - const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); - std::vector roots; - - const std::filesystem::path cdRoot = getCdRootPath(); - if (!cdRoot.empty()) + const std::string normalized = normalizeCdPathNoPrefix(relative.generic_string()); + if (normalized.empty()) { - roots.push_back(cdRoot); - std::filesystem::path parent = cdRoot; - for (int i = 0; i < 4; ++i) - { - parent = parent.parent_path(); - if (parent.empty()) - { - break; - } - roots.push_back(parent); - } + throw std::runtime_error("cdLoosePathKeyFromRelative: normalized path is empty"); } - if (!paths.hostRoot.empty()) - { - roots.push_back(paths.hostRoot); - } - if (!paths.elfDirectory.empty()) - { - roots.push_back(paths.elfDirectory); - } + const std::filesystem::path relPath(normalized); + std::string parent = toLowerAscii(normalizePathSeparators(relPath.parent_path().generic_string())); + const std::string leaf = normalizeCdLooseNumericKey(relPath.filename().string()); - std::filesystem::path bestPath; - uint64_t bestSize = 0; - std::unordered_set seenRoots; - for (const std::filesystem::path &root : roots) + if (parent.empty()) { - if (root.empty()) - { - continue; - } - - const std::string key = toLowerAscii(root.lexically_normal().string()); - if (!seenRoots.emplace(key).second) - { - continue; - } - - std::filesystem::path candidate; - if (!trySelectBestDiscImageFromDirectory(root, candidate)) - { - continue; - } - - std::error_code sizeEc; - const uint64_t size = static_cast(std::filesystem::file_size(candidate, sizeEc)); - if (sizeEc || size <= bestSize) - { - continue; - } - - bestSize = size; - bestPath = candidate; + return leaf; } + return parent + "/" + leaf; + } - if (!bestPath.empty()) - { - std::cout << "[CD] Auto-detected disc image: " << bestPath.string() << std::endl; - } - return bestPath; + std::string cdLoosePathKey(const std::string &ps2Path) + { + return cdLoosePathKeyFromRelative(std::filesystem::path(normalizeCdPathNoPrefix(ps2Path))); } - std::filesystem::path getCdImagePath() + std::filesystem::path getCdRootPath() { const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); - if (!paths.cdImage.empty()) + if (!paths.cdRoot.empty()) { - return paths.cdImage; + return paths.cdRoot; } - - const std::filesystem::path cdRoot = getCdRootPath(); - if (!g_cdAutoImageSearched || g_cdAutoImageRoot != cdRoot) + if (!paths.elfDirectory.empty()) { - g_cdAutoImageRoot = cdRoot; - g_cdAutoImagePath = autoDetectCdImagePath(); - g_cdAutoImageSearched = true; + return paths.elfDirectory; } - return g_cdAutoImagePath; + 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; } bool tryGetCdImageTotalSectors(uint64_t &totalSectorsOut) @@ -375,6 +288,7 @@ namespace } g_cdLeafIndex.clear(); + g_cdLoosePathIndex.clear(); g_cdLeafIndexRoot = root; g_cdLeafIndexBuilt = true; @@ -398,6 +312,17 @@ namespace const std::string leaf = toLowerAscii(entry.path().filename().string()); g_cdLeafIndex.emplace(leaf, entry.path()); + + std::error_code relEc; + const std::filesystem::path relative = std::filesystem::relative(entry.path(), root, relEc); + if (!relEc) + { + const std::string looseKey = cdLoosePathKeyFromRelative(relative); + if (!looseKey.empty()) + { + g_cdLoosePathIndex.emplace(looseKey, entry.path()); + } + } } } @@ -442,8 +367,18 @@ namespace } else { - g_lastCdError = -1; - return false; + const std::string looseKey = cdLoosePathKey(ps2Path); + auto looseIt = g_cdLoosePathIndex.find(looseKey); + if (looseIt != g_cdLoosePathIndex.end()) + { + path = looseIt->second; + ec.clear(); + } + else + { + g_lastCdError = -1; + return false; + } } } } @@ -584,73 +519,6 @@ namespace 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; @@ -925,13 +793,13 @@ namespace private: uint32_t readWordAtSlot(uint32_t slotIndex) const { - if (slotIndex < 4u) + if (slotIndex < 8u) { - // slot0..slot3 -> a0..a3 (r4..r7) + // EE calls use eight integer argument registers (a0-a3, t0-t3 / r4-r11). return getRegU32(m_ctx, 4 + static_cast(slotIndex)); } - const uint32_t stackIndex = slotIndex - 4u; + const uint32_t stackIndex = slotIndex - 8u; const uint32_t stackAddr = m_stackBase + stackIndex * 4u; uint32_t value = 0; (void)tryReadWordFromGuest(m_rdram, m_runtime, stackAddr, value); @@ -1006,15 +874,20 @@ namespace int parsedWidth = -1; int parsedPrecision = -1; + bool widthSpecified = false; + bool precisionSpecified = false; + std::string parsedFlags; while (*p && std::strchr("-+ #0", *p)) { + parsedFlags.push_back(*p); ++p; } if (*p == '*') { parsedWidth = static_cast(nextU32()); + widthSpecified = true; ++p; } else @@ -1022,6 +895,7 @@ namespace if (*p && std::isdigit(static_cast(*p))) { parsedWidth = 0; + widthSpecified = true; } while (*p && std::isdigit(static_cast(*p))) { @@ -1033,6 +907,7 @@ namespace if (*p == '.') { ++p; + precisionSpecified = true; if (*p == '*') { parsedPrecision = static_cast(nextU32()); @@ -1052,7 +927,6 @@ namespace { parsedPrecision = -1; } - (void)parsedWidth; enum class LengthMod { @@ -1121,6 +995,51 @@ namespace break; } + auto buildHostSpec = [&](char spec, const char *lengthOverride = nullptr) -> std::string + { + std::string specText; + specText.reserve(32); + specText.push_back('%'); + + if (widthSpecified && parsedWidth < 0 && + parsedFlags.find('-') == std::string::npos) + { + specText.push_back('-'); + } + + specText.append(parsedFlags); + if (widthSpecified) + { + const int hostWidth = (parsedWidth < 0) ? -parsedWidth : parsedWidth; + specText.append(std::to_string(hostWidth)); + } + if (precisionSpecified) + { + specText.push_back('.'); + specText.append(std::to_string(std::max(parsedPrecision, 0))); + } + if (lengthOverride != nullptr) + { + specText.append(lengthOverride); + } + specText.push_back(spec); + return specText; + }; + + auto appendFormatted = [&](const std::string &specText, auto value) -> bool + { + const int needed = std::snprintf(nullptr, 0, specText.c_str(), value); + if (needed < 0) + { + return false; + } + + std::string chunk(static_cast(needed), '\0'); + std::snprintf(chunk.data(), chunk.size() + 1u, specText.c_str(), value); + out.append(chunk); + return true; + }; + const bool use64Integer = (length == LengthMod::LL || length == LengthMod::J); auto readUnsignedInteger = [&]() -> uint64_t { @@ -1141,59 +1060,72 @@ namespace case 's': { const uint32_t strAddr = nextU32(); - if (strAddr == 0) + const char *text = "(null)"; + std::string ownedText; + if (strAddr != 0u) { - out.append("(null)"); + ownedText = readString(strAddr); + text = ownedText.c_str(); } - else + if (!appendFormatted(buildHostSpec(spec), text)) { - std::string str = readString(strAddr); - if (parsedPrecision >= 0 && - str.size() > static_cast(parsedPrecision)) - { - str.resize(static_cast(parsedPrecision)); - } - out.append(str); + out.append(text); } break; } case 'c': { - const char ch = static_cast(nextU32() & 0xFF); - out.push_back(ch); + const int ch = static_cast(nextU32() & 0xFFu); + if (!appendFormatted(buildHostSpec(spec), ch)) + { + out.push_back(static_cast(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') + const long long value = static_cast(readSignedInteger()); + if (!appendFormatted(buildHostSpec(spec, "ll"), value)) { - ss.setf(std::ios::uppercase); + out.append(std::to_string(value)); } - ss << std::hex << readUnsignedInteger(); - out.append(ss.str()); break; } + case 'u': + case 'x': + case 'X': case 'o': { - std::ostringstream ss; - ss << std::oct << readUnsignedInteger(); - out.append(ss.str()); + const unsigned long long value = static_cast(readUnsignedInteger()); + if (!appendFormatted(buildHostSpec(spec, "ll"), value)) + { + std::ostringstream ss; + if (spec == 'o') + { + ss << std::oct << value; + } + else + { + if (spec == 'X') + { + ss.setf(std::ios::uppercase); + } + ss << std::hex << value; + } + out.append(ss.str()); + } break; } case 'p': { - std::ostringstream ss; - ss << "0x" << std::hex << nextU32(); - out.append(ss.str()); + const uint32_t ptrValue = nextU32(); + if (!appendFormatted(buildHostSpec(spec), reinterpret_cast(static_cast(ptrValue)))) + { + std::ostringstream ss; + ss << "0x" << std::hex << ptrValue; + out.append(ss.str()); + } break; } case 'f': @@ -1208,9 +1140,17 @@ namespace 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); + if (length == LengthMod::BigL) + { + if (!appendFormatted(buildHostSpec(spec, "L"), static_cast(value))) + { + out.append(std::to_string(value)); + } + } + else if (!appendFormatted(buildHostSpec(spec), value)) + { + out.append(std::to_string(value)); + } break; } case 'n': @@ -1418,11 +1358,32 @@ namespace g_dmaPendingPolls[channelBase] = 1; if (g_dmaStubLogCount < kMaxDmaStubLogs) { - std::cout << "[sceDmaSend] ch=0x" << std::hex << channelBase + RUNTIME_LOG("[sceDmaSend] ch=0x" << std::hex << channelBase << " madr=0x" << madr << " qwc=0x" << qwc << " tadr=0x" << tadr - << " chcr=0x" << chcr << std::dec << std::endl; + << " chcr=0x" << chcr << std::dec << std::endl); + + if (!preferNormalCount && (channelBase == 0x10009000u || channelBase == 0x1000A000u)) + { + if (const uint8_t *tagPtr = getConstMemPtr(rdram, tadr)) + { + uint64_t tagLo = 0u; + std::memcpy(&tagLo, tagPtr, sizeof(tagLo)); + uint32_t w2 = 0u; + uint32_t w3 = 0u; + std::memcpy(&w2, tagPtr + 8u, sizeof(w2)); + std::memcpy(&w3, tagPtr + 12u, sizeof(w3)); + RUNTIME_LOG("[sceDmaSend:head] ch=0x" << std::hex << channelBase + << " tagQwc=0x" << static_cast(tagLo & 0xFFFFu) + << " id=0x" << static_cast((tagLo >> 28u) & 0x7u) + << " irq=0x" << static_cast((tagLo >> 31u) & 0x1u) + << " addr=0x" << static_cast((tagLo >> 32u) & 0x7FFFFFFFu) + << " w2=0x" << w2 + << " w3=0x" << w3 + << std::dec << std::endl); + } + } ++g_dmaStubLogCount; } @@ -1860,19 +1821,21 @@ namespace static void applyGsDispEnv(PS2Runtime *runtime, const GsDispEnvMem &env) { - if (!runtime) + if (!runtime || !runtime->syncCoreSubsystems()) return; auto ®s = runtime->memory().gs(); regs.pmode = env.pmode; regs.smode2 = env.smode2; regs.dispfb1 = env.dispfb; regs.display1 = env.display; + regs.dispfb2 = env.dispfb; + regs.display2 = env.display; regs.bgcolor = env.bgcolor; } static void applyGsRegPairs(PS2Runtime *runtime, const GsRegPairMem *pairs, size_t pairCount) { - if (!runtime || !pairs) + if (!runtime || !pairs || !runtime->syncCoreSubsystems()) return; for (size_t i = 0; i < pairCount; ++i) { diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/IPU.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/IPU.cpp new file mode 100644 index 00000000..a589216e --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/IPU.cpp @@ -0,0 +1,94 @@ +#include "Common.h" +#include "IPU.h" + +namespace ps2_stubs +{ + void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static constexpr uint32_t REG_IPU_CTRL = 0x10002010u; + static constexpr uint32_t REG_IPU_CMD = 0x10002000u; + static constexpr uint32_t REG_IPU_IN_FIFO = 0x10007010u; + static constexpr uint32_t IQVAL_BASE = 0x1721e0u; + static constexpr uint32_t VQVAL_BASE = 0x172230u; + static constexpr uint32_t SETD4_CHCR_ENTRY = 0x126428u; + + if (!runtime) + return; + + if (!runtime->memory().getRDRAM()) + { + if (!runtime->memory().initialize()) + { + setReturnS32(ctx, -1); + return; + } + } + + if (!runtime->syncCoreSubsystems()) + { + setReturnS32(ctx, -1); + return; + } + + PS2Memory &mem = runtime->memory(); + + if (runtime->hasFunction(SETD4_CHCR_ENTRY)) + { + auto setD4 = runtime->lookupFunction(SETD4_CHCR_ENTRY); + ctx->r[4] = _mm_set_epi64x(0, 1); + { + PS2Runtime::GuestExecutionScope guestExecution(runtime); + setD4(rdram, ctx, runtime); + } + } + + mem.write32(REG_IPU_CTRL, 0x40000000u); + mem.write32(REG_IPU_CMD, 0u); + + __m128i v; + v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x00u); + mem.write128(REG_IPU_IN_FIFO, v); + v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x10u); + mem.write128(REG_IPU_IN_FIFO, v); + v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x20u); + mem.write128(REG_IPU_IN_FIFO, v); + v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x30u); + mem.write128(REG_IPU_IN_FIFO, v); + v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x40u); + mem.write128(REG_IPU_IN_FIFO, v); + mem.write128(REG_IPU_IN_FIFO, v); + mem.write128(REG_IPU_IN_FIFO, v); + mem.write128(REG_IPU_IN_FIFO, v); + + mem.write32(REG_IPU_CMD, 0x50000000u); + mem.write32(REG_IPU_CMD, 0x58000000u); + + v = runtime->Load128(rdram, ctx, VQVAL_BASE + 0x00u); + mem.write128(REG_IPU_IN_FIFO, v); + v = runtime->Load128(rdram, ctx, VQVAL_BASE + 0x10u); + mem.write128(REG_IPU_IN_FIFO, v); + + mem.write32(REG_IPU_CMD, 0x60000000u); + mem.write32(REG_IPU_CMD, 0x90000000u); + + mem.write32(REG_IPU_CTRL, 0x40000000u); + mem.write32(REG_IPU_CMD, 0u); + + setReturnS32(ctx, 0); + } + + void sceIpuRestartDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceIpuStopDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceIpuSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/IPU.h b/ps2xRuntime/src/lib/Kernel/Stubs/IPU.h new file mode 100644 index 00000000..17d902ff --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/IPU.h @@ -0,0 +1,11 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceIpuRestartDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceIpuStopDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceIpuSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/LibC.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/LibC.cpp new file mode 100644 index 00000000..253540e0 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/LibC.cpp @@ -0,0 +1,1132 @@ +#include "Common.h" +#include "LibC.h" +#include "ps2_log.h" + +namespace ps2_stubs +{ + namespace + { + uint32_t sanitizeMemTransferSize(uint32_t size, const char *op) + { + constexpr uint32_t kMaxTransfer = PS2_RAM_SIZE; + if (size <= kMaxTransfer) + { + return size; + } + + static std::mutex s_warnMutex; + static std::unordered_map s_warnCounts; + uint32_t warnCount = 0u; + { + std::lock_guard lock(s_warnMutex); + warnCount = ++s_warnCounts[op ? op : "memop"]; + } + if (warnCount <= 16u) + { + std::cerr << "[" << (op ? op : "memop") << "] size clamp from 0x" + << std::hex << size << " to 0x" << kMaxTransfer + << std::dec << std::endl; + } + return kMaxTransfer; + } + + uint32_t guestContiguousBytes(uint32_t guestAddr) + { + uint32_t offset = 0u; + bool scratch = false; + if (!ps2ResolveGuestPointer(guestAddr, offset, scratch)) + { + return 0u; + } + if (scratch) + { + return (offset < PS2_SCRATCHPAD_SIZE) ? (PS2_SCRATCHPAD_SIZE - offset) : 0u; + } + return (offset < PS2_RAM_SIZE) ? (PS2_RAM_SIZE - offset) : 0u; + } + } + + void malloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + const uint32_t guestAddr = getRegU32(ctx, 4); // $a0 + if (runtime && guestAddr != 0u) + { + runtime->guestFree(guestAddr); + } + } + + void calloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + 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) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + uint32_t srcAddr = getRegU32(ctx, 5); // $a1 + uint32_t size = getRegU32(ctx, 6); // $a2 + size = sanitizeMemTransferSize(size, "memcpy"); + + uint32_t copied = 0u; + uint32_t curDst = destAddr; + uint32_t curSrc = srcAddr; + while (copied < size) + { + uint8_t *hostDest = getMemPtr(rdram, curDst); + const uint8_t *hostSrc = getConstMemPtr(rdram, curSrc); + if (!hostDest || !hostSrc) + { + break; + } + + uint32_t chunk = size - copied; + chunk = std::min(chunk, guestContiguousBytes(curDst)); + chunk = std::min(chunk, guestContiguousBytes(curSrc)); + if (chunk == 0u) + { + break; + } + + ::memcpy(hostDest, hostSrc, chunk); + copied += chunk; + curDst += chunk; + curSrc += chunk; + } + + if (copied != 0u) + { + ps2TraceGuestRangeWrite(rdram, destAddr, copied, "memcpy", ctx); + } + + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void memset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + int value = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value) + uint32_t size = getRegU32(ctx, 6); // $a2 + size = sanitizeMemTransferSize(size, "memset"); + + uint32_t written = 0u; + uint32_t curDst = destAddr; + while (written < size) + { + uint8_t *hostDest = getMemPtr(rdram, curDst); + if (!hostDest) + { + break; + } + + uint32_t chunk = size - written; + chunk = std::min(chunk, guestContiguousBytes(curDst)); + if (chunk == 0u) + { + break; + } + + ::memset(hostDest, value, chunk); + written += chunk; + curDst += chunk; + } + + if (written != 0u) + { + ps2TraceGuestRangeWrite(rdram, destAddr, written, "memset", ctx); + } + + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void memmove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + uint32_t srcAddr = getRegU32(ctx, 5); // $a1 + uint32_t size = getRegU32(ctx, 6); // $a2 + size = sanitizeMemTransferSize(size, "memmove"); + + uint32_t copied = 0u; + std::vector tmp; + tmp.reserve(size); + for (uint32_t i = 0u; i < size; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + if (!src) + { + break; + } + tmp.push_back(*src); + } + + for (uint32_t i = 0u; i < static_cast(tmp.size()); ++i) + { + uint8_t *dst = getMemPtr(rdram, destAddr + i); + if (!dst) + { + break; + } + *dst = tmp[i]; + ++copied; + } + + if (copied != 0u) + { + ps2TraceGuestRangeWrite(rdram, destAddr, copied, "memmove", ctx); + } + + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void memcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t ptr1Addr = getRegU32(ctx, 4); // $a0 + uint32_t ptr2Addr = getRegU32(ctx, 5); // $a1 + uint32_t size = getRegU32(ctx, 6); // $a2 + size = sanitizeMemTransferSize(size, "memcmp"); + int result = 0; + + for (uint32_t i = 0u; i < size; ++i) + { + const uint8_t *lhs = getConstMemPtr(rdram, ptr1Addr + i); + const uint8_t *rhs = getConstMemPtr(rdram, ptr2Addr + i); + if (!lhs || !rhs) + { + result = (!lhs && !rhs) ? 0 : (lhs ? 1 : -1); + break; + } + if (*lhs != *rhs) + { + result = static_cast(*lhs) - static_cast(*rhs); + break; + } + } + setReturnS32(ctx, result); + } + + void strcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + uint32_t srcAddr = getRegU32(ctx, 5); // $a1 + + char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); + const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); + + if (hostDest && hostSrc) + { + ::strcpy(hostDest, hostSrc); + ps2TraceGuestRangeWrite(rdram, destAddr, static_cast(::strlen(hostSrc) + 1u), "strcpy", ctx); + } + else + { + std::cerr << "strcpy error: Invalid address provided." + << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" + << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec + << std::endl; + } + + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void strncpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + uint32_t srcAddr = getRegU32(ctx, 5); // $a1 + uint32_t size = getRegU32(ctx, 6); // $a2 + + char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); + const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); + + if (hostDest && hostSrc) + { + ::strncpy(hostDest, hostSrc, size); + ps2TraceGuestRangeWrite(rdram, destAddr, size, "strncpy", ctx); + } + else + { + std::cerr << "strncpy error: Invalid address provided." + << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" + << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec + << std::endl; + } + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void strlen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t strAddr = getRegU32(ctx, 4); // $a0 + const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); + size_t len = 0; + + if (hostStr) + { + len = ::strlen(hostStr); + } + else + { + std::cerr << "strlen error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; + } + setReturnU32(ctx, (uint32_t)len); + } + + void strcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t str1Addr = getRegU32(ctx, 4); // $a0 + uint32_t str2Addr = getRegU32(ctx, 5); // $a1 + + const char *hostStr1 = reinterpret_cast(getConstMemPtr(rdram, str1Addr)); + const char *hostStr2 = reinterpret_cast(getConstMemPtr(rdram, str2Addr)); + int result = 0; + + if (hostStr1 && hostStr2) + { + result = ::strcmp(hostStr1, hostStr2); + } + else + { + std::cerr << "strcmp error: Invalid address provided." + << " Str1: 0x" << std::hex << str1Addr << " (host ptr valid: " << (hostStr1 != nullptr) << ")" + << ", Str2: 0x" << str2Addr << " (host ptr valid: " << (hostStr2 != nullptr) << ")" << std::dec + << std::endl; + // Return non-zero on error, consistent with memcmp error handling + result = (hostStr1 == nullptr) - (hostStr2 == nullptr); + if (result == 0 && hostStr1 == nullptr) + result = 1; // Both null -> treat as different? Or 0? Let's say different. + } + setReturnS32(ctx, result); + } + + void strncmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t str1Addr = getRegU32(ctx, 4); // $a0 + uint32_t str2Addr = getRegU32(ctx, 5); // $a1 + uint32_t size = getRegU32(ctx, 6); // $a2 + + const char *hostStr1 = reinterpret_cast(getConstMemPtr(rdram, str1Addr)); + const char *hostStr2 = reinterpret_cast(getConstMemPtr(rdram, str2Addr)); + int result = 0; + + if (hostStr1 && hostStr2) + { + result = ::strncmp(hostStr1, hostStr2, size); + } + else + { + std::cerr << "strncmp error: Invalid address provided." + << " Str1: 0x" << std::hex << str1Addr << " (host ptr valid: " << (hostStr1 != nullptr) << ")" + << ", Str2: 0x" << str2Addr << " (host ptr valid: " << (hostStr2 != nullptr) << ")" << std::dec + << std::endl; + result = (hostStr1 == nullptr) - (hostStr2 == nullptr); + if (result == 0 && hostStr1 == nullptr) + result = 1; // Both null -> different + } + setReturnS32(ctx, result); + } + + void strcat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + uint32_t srcAddr = getRegU32(ctx, 5); // $a1 + + char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); + const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); + + if (hostDest && hostSrc) + { + ::strcat(hostDest, hostSrc); + } + else + { + std::cerr << "strcat error: Invalid address provided." + << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" + << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec + << std::endl; + } + + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void strncat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t destAddr = getRegU32(ctx, 4); // $a0 + uint32_t srcAddr = getRegU32(ctx, 5); // $a1 + uint32_t size = getRegU32(ctx, 6); // $a2 + + char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); + const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); + + if (hostDest && hostSrc) + { + ::strncat(hostDest, hostSrc, size); + } + else + { + std::cerr << "strncat error: Invalid address provided." + << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" + << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec + << std::endl; + } + + // returns dest pointer ($v0 = $a0) + ctx->r[2] = ctx->r[4]; + } + + void strchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t strAddr = getRegU32(ctx, 4); // $a0 + int char_code = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value) + + const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); + char *foundPtr = nullptr; + uint32_t resultAddr = 0; + + if (hostStr) + { + foundPtr = ::strchr(const_cast(hostStr), char_code); + if (foundPtr) + { + resultAddr = hostPtrToPs2Addr(rdram, foundPtr); + } + } + else + { + std::cerr << "strchr error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; + } + + // returns PS2 address or 0 (NULL) + setReturnU32(ctx, resultAddr); + } + + void strrchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t strAddr = getRegU32(ctx, 4); // $a0 + int char_code = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value) + + const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); + char *foundPtr = nullptr; + uint32_t resultAddr = 0; + + if (hostStr) + { + foundPtr = ::strrchr(const_cast(hostStr), char_code); // Use const_cast carefully + if (foundPtr) + { + resultAddr = hostPtrToPs2Addr(rdram, foundPtr); + } + } + else + { + std::cerr << "strrchr error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; + } + + // returns PS2 address or 0 (NULL) + setReturnU32(ctx, resultAddr); + } + + void strstr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t haystackAddr = getRegU32(ctx, 4); // $a0 + uint32_t needleAddr = getRegU32(ctx, 5); // $a1 + + const char *hostHaystack = reinterpret_cast(getConstMemPtr(rdram, haystackAddr)); + const char *hostNeedle = reinterpret_cast(getConstMemPtr(rdram, needleAddr)); + char *foundPtr = nullptr; + uint32_t resultAddr = 0; + + if (hostHaystack && hostNeedle) + { + foundPtr = ::strstr(const_cast(hostHaystack), hostNeedle); + if (foundPtr) + { + resultAddr = hostPtrToPs2Addr(rdram, foundPtr); + } + } + else + { + std::cerr << "strstr error: Invalid address provided." + << " Haystack: 0x" << std::hex << haystackAddr << " (host ptr valid: " << (hostHaystack != nullptr) << ")" + << ", Needle: 0x" << needleAddr << " (host ptr valid: " << (hostNeedle != nullptr) << ")" << std::dec + << std::endl; + } + + // returns PS2 address or 0 (NULL) + setReturnU32(ctx, resultAddr); + } + + void printf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t format_addr = getRegU32(ctx, 4); // $a0 + 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(), 1); + if (rendered.size() > 2048) + { + rendered.resize(2048); + } + PS2_IF_AGRESSIVE_LOGS({ + const std::string logLine = sanitizeForLog(rendered); + uint32_t count = 0; + { + std::lock_guard lock(g_printfLogMutex); + count = ++g_printfLogCount; + } + if (count <= kMaxPrintfLogs) + { + RUNTIME_LOG("PS2 printf: " << logLine); + RUNTIME_LOG(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 error: Invalid format string address provided: 0x" << std::hex << format_addr << std::dec << std::endl; + } + + // returns the number of characters written, or negative on error. + setReturnS32(ctx, ret); + } + + void sprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t str_addr = getRegU32(ctx, 4); // $a0 + uint32_t format_addr = getRegU32(ctx, 5); // $a1 + constexpr size_t kSafeSprintfBytes = 256u; // Keep guest stack temporaries from being overwritten. + + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); + int ret = -1; + + if (format_addr != 0) + { + 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); + RUNTIME_LOG("[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() >= kSafeSprintfBytes) + { + rendered.resize(kSafeSprintfBytes - 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 format address provided." + << " Dest: 0x" << std::hex << str_addr + << ", Format: 0x" << format_addr << std::dec + << std::endl; + } + + // returns the number of characters written (excluding null), or negative on error. + setReturnS32(ctx, ret); + } + + void snprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t str_addr = getRegU32(ctx, 4); // $a0 + size_t size = getRegU32(ctx, 5); // $a1 + uint32_t format_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 = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 3); + ret = static_cast(rendered.size()); + + 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 + << ", Format: 0x" << format_addr << std::dec + << ", Size: " << size << std::endl; + } + + // returns the number of characters that *would* have been written + // if size was large enough (excluding null), or negative on error. + setReturnS32(ctx, ret); + } + + void puts(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t strAddr = getRegU32(ctx, 4); // $a0 + const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); + int result = EOF; + + if (hostStr) + { + result = std::puts(hostStr); // std::puts adds a newline + std::fflush(stdout); // Ensure output appears + } + else + { + std::cerr << "puts error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; + } + + // returns non-negative on success, EOF on error. + setReturnS32(ctx, result >= 0 ? 0 : -1); // PS2 might expect 0/-1 rather than EOF + } + + void fopen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + uint32_t modeAddr = getRegU32(ctx, 5); // $a1 + + const char *hostPath = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + const char *hostMode = reinterpret_cast(getConstMemPtr(rdram, modeAddr)); + uint32_t file_handle = 0; + + if (hostPath && hostMode) + { + // TODO: Add translation for PS2 paths like mc0:, host:, cdrom:, etc. + // treating as direct host path + RUNTIME_LOG("ps2_stub fopen: path='" << hostPath << "', mode='" << hostMode << "'"); + FILE *fp = ::fopen(hostPath, hostMode); + if (fp) + { + std::lock_guard lock(g_file_mutex); + file_handle = generate_file_handle(); + g_file_map[file_handle] = fp; + RUNTIME_LOG(" -> handle=0x" << std::hex << file_handle << std::dec); + } + else + { + std::cerr << "ps2_stub fopen error: Failed to open '" << hostPath << "' with mode '" << hostMode << "'. Error: " << strerror(errno) << std::endl; + } + } + else + { + std::cerr << "fopen error: Invalid address provided for path or mode." + << " Path: 0x" << std::hex << pathAddr << " (host ptr valid: " << (hostPath != nullptr) << ")" + << ", Mode: 0x" << modeAddr << " (host ptr valid: " << (hostMode != nullptr) << ")" << std::dec + << std::endl; + } + // returns a file handle (non-zero) on success, or NULL (0) on error. + setReturnU32(ctx, file_handle); + } + + void fclose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t file_handle = getRegU32(ctx, 4); // $a0 + int ret = EOF; // Default to error + + if (file_handle != 0) + { + std::lock_guard lock(g_file_mutex); + auto it = g_file_map.find(file_handle); + if (it != g_file_map.end()) + { + FILE *fp = it->second; + ret = ::fclose(fp); + g_file_map.erase(it); + } + else + { + std::cerr << "ps2_stub fclose error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; + } + } + else + { + // Closing NULL handle in Standard C defines this as no-op + ret = 0; + } + + // returns 0 on success, EOF on error. + setReturnS32(ctx, ret); + } + + void fread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t ptrAddr = getRegU32(ctx, 4); // $a0 (buffer) + uint32_t size = getRegU32(ctx, 5); // $a1 (element size) + uint32_t count = getRegU32(ctx, 6); // $a2 (number of elements) + uint32_t file_handle = getRegU32(ctx, 7); // $a3 (file handle) + size_t items_read = 0; + + uint8_t *hostPtr = getMemPtr(rdram, ptrAddr); + FILE *fp = get_file_ptr(file_handle); + + if (hostPtr && fp && size > 0 && count > 0) + { + items_read = ::fread(hostPtr, size, count, fp); + } + else + { + std::cerr << "fread error: Invalid arguments." + << " Ptr: 0x" << std::hex << ptrAddr << " (host ptr valid: " << (hostPtr != nullptr) << ")" + << ", Handle: 0x" << file_handle << " (file valid: " << (fp != nullptr) << ")" << std::dec + << ", Size: " << size << ", Count: " << count << std::endl; + } + // returns the number of items successfully read. + setReturnU32(ctx, (uint32_t)items_read); + } + + void fwrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t ptrAddr = getRegU32(ctx, 4); // $a0 (buffer) + uint32_t size = getRegU32(ctx, 5); // $a1 (element size) + uint32_t count = getRegU32(ctx, 6); // $a2 (number of elements) + uint32_t file_handle = getRegU32(ctx, 7); // $a3 (file handle) + size_t items_written = 0; + + const uint8_t *hostPtr = getConstMemPtr(rdram, ptrAddr); + FILE *fp = get_file_ptr(file_handle); + + if (hostPtr && fp && size > 0 && count > 0) + { + items_written = ::fwrite(hostPtr, size, count, fp); + } + else + { + std::cerr << "fwrite error: Invalid arguments." + << " Ptr: 0x" << std::hex << ptrAddr << " (host ptr valid: " << (hostPtr != nullptr) << ")" + << ", Handle: 0x" << file_handle << " (file valid: " << (fp != nullptr) << ")" << std::dec + << ", Size: " << size << ", Count: " << count << std::endl; + } + // returns the number of items successfully written. + setReturnU32(ctx, (uint32_t)items_written); + } + + void fprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t file_handle = getRegU32(ctx, 4); // $a0 + uint32_t format_addr = getRegU32(ctx, 5); // $a1 + 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 = 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 << std::dec + << std::endl; + } + + // returns the number of characters written, or negative on error. + setReturnS32(ctx, ret); + } + + void fseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t file_handle = getRegU32(ctx, 4); // $a0 + long offset = (long)getRegU32(ctx, 5); // $a1 (Note: might need 64-bit for large files?) + int whence = (int)getRegU32(ctx, 6); // $a2 (SEEK_SET, SEEK_CUR, SEEK_END) + int ret = -1; // Default error + + FILE *fp = get_file_ptr(file_handle); + + if (fp) + { + // Ensure whence is valid (0, 1, 2) + if (whence >= 0 && whence <= 2) + { + ret = ::fseek(fp, offset, whence); + } + else + { + std::cerr << "fseek error: Invalid whence value: " << whence << std::endl; + } + } + else + { + std::cerr << "fseek error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; + } + + // returns 0 on success, non-zero on error. + setReturnS32(ctx, ret); + } + + void ftell(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t file_handle = getRegU32(ctx, 4); // $a0 + long ret = -1L; + + FILE *fp = get_file_ptr(file_handle); + + if (fp) + { + ret = ::ftell(fp); + } + else + { + std::cerr << "ftell error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; + } + + // returns the current position, or -1L on error. + if (ret > 0xFFFFFFFFL || ret < 0) + { + setReturnS32(ctx, -1); + } + else + { + setReturnU32(ctx, (uint32_t)ret); + } + } + + void fflush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t file_handle = getRegU32(ctx, 4); // $a0 + int ret = EOF; // Default error + + // If handle is 0 fflush flushes *all* output streams. + if (file_handle == 0) + { + ret = ::fflush(NULL); + } + else + { + FILE *fp = get_file_ptr(file_handle); + if (fp) + { + ret = ::fflush(fp); + } + else + { + std::cerr << "fflush error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; + } + } + // returns 0 on success, EOF on error. + setReturnS32(ctx, ret); + } + + void sqrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::sqrtf(arg); + } + + void sin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + 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]; + ctx->f[0] = ::tanf(arg); + } + + void atan2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float y = ctx->f[12]; + float x = ctx->f[14]; + ctx->f[0] = ::atan2f(y, x); + } + + void pow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float base = ctx->f[12]; + float exp = ctx->f[14]; + ctx->f[0] = ::powf(base, exp); + } + + void exp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::expf(arg); + } + + void log(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::logf(arg); + } + + void log10(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::log10f(arg); + } + + void ceil(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::ceilf(arg); + } + + void floor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::floorf(arg); + } + + void fabs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float arg = ctx->f[12]; + ctx->f[0] = ::fabsf(arg); + } + void abs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t value = static_cast(getRegU32(ctx, 4)); + if (value == std::numeric_limits::min()) + { + setReturnS32(ctx, std::numeric_limits::max()); + return; + } + setReturnS32(ctx, value < 0 ? -value : value); + } + + void atan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + float in = ctx ? ctx->f[12] : 0.0f; + if (in == 0.0f) + { + uint32_t raw = getRegU32(ctx, 4); + std::memcpy(&in, &raw, sizeof(in)); + } + const float out = std::atan(in); + if (ctx) + { + ctx->f[0] = out; + } + + uint32_t outRaw = 0u; + std::memcpy(&outRaw, &out, sizeof(outRaw)); + setReturnU32(ctx, outRaw); + } + + void memchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t srcAddr = getRegU32(ctx, 4); + const uint8_t needle = static_cast(getRegU32(ctx, 5) & 0xFFu); + const uint32_t size = getRegU32(ctx, 6); + + for (uint32_t i = 0; i < size; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + if (!src) + { + break; + } + if (*src == needle) + { + setReturnU32(ctx, srcAddr + i); + return; + } + } + + setReturnU32(ctx, 0u); + } + + void rand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, std::rand() & 0x7FFF); + } + + void srand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + std::srand(getRegU32(ctx, 4)); + setReturnS32(ctx, 0); + } + + void strcasecmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t lhsAddr = getRegU32(ctx, 4); + const uint32_t rhsAddr = getRegU32(ctx, 5); + const std::string lhs = readPs2CStringBounded(rdram, runtime, lhsAddr, 1024); + const std::string rhs = readPs2CStringBounded(rdram, runtime, rhsAddr, 1024); + + const size_t n = std::min(lhs.size(), rhs.size()); + for (size_t i = 0; i < n; ++i) + { + const int a = std::tolower(static_cast(lhs[i])); + const int b = std::tolower(static_cast(rhs[i])); + if (a != b) + { + setReturnS32(ctx, a - b); + return; + } + } + + setReturnS32(ctx, static_cast(lhs.size()) - static_cast(rhs.size())); + } + + void vfprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + 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 + constexpr size_t kSafeVsprintfBytes = 256u; // Keep guest stack temporaries from being overwritten. + 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 (rendered.size() >= kSafeVsprintfBytes) + { + rendered.resize(kSafeVsprintfBytes - 1); + } + 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); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/LibC.h b/ps2xRuntime/src/lib/Kernel/Stubs/LibC.h new file mode 100644 index 00000000..01792bfe --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/LibC.h @@ -0,0 +1,60 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void malloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void free(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void calloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void realloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void memcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void memset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void memmove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void memcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strncpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strlen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strncmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strcat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strncat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strrchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strstr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void printf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void snprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void puts(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fopen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fclose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fwrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ftell(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fflush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sqrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void __kernel_sinf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void cos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void __kernel_cosf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void __ieee754_rem_pio2f(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void tan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void atan2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void pow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void exp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void log(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void log10(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ceil(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void floor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fabs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void abs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void atan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void memchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void rand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void srand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void strcasecmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void vfprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void vsprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/MPEG.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/MPEG.cpp new file mode 100644 index 00000000..d06f5a6e --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/MPEG.cpp @@ -0,0 +1,421 @@ +#include "Common.h" +#include "MPEG.h" + +namespace ps2_stubs +{ + namespace + { + struct MpegRegisteredCallback + { + uint32_t type = 0u; + uint32_t func = 0u; + uint32_t data = 0u; + uint32_t handle = 0u; + }; + + struct MpegPlaybackState + { + uint32_t picturesServed = 0u; + }; + + struct MpegStubState + { + bool initialized = false; + uint32_t nextCallbackHandle = 1u; + std::unordered_map> callbacksByMpeg; + std::unordered_map playbackByMpeg; + PS2MpegCompatLayout compat; + }; + + std::mutex g_mpeg_stub_mutex; + MpegStubState g_mpeg_stub_state; + + constexpr uint32_t kStubMovieWidth = 320u; + constexpr uint32_t kStubMovieHeight = 240u; + + uint32_t mpegCompatSyntheticFrames(const PS2MpegCompatLayout &layout) + { + return layout.syntheticFramesBeforeEnd != 0u ? layout.syntheticFramesBeforeEnd : 1u; + } + + MpegPlaybackState &getPlaybackState(uint32_t mpegAddr) + { + return g_mpeg_stub_state.playbackByMpeg[mpegAddr]; + } + + void resetMpegStubStateUnlocked() + { + const PS2MpegCompatLayout compat = g_mpeg_stub_state.compat; + g_mpeg_stub_state.initialized = false; + g_mpeg_stub_state.nextCallbackHandle = 1u; + g_mpeg_stub_state.callbacksByMpeg.clear(); + g_mpeg_stub_state.playbackByMpeg.clear(); + g_mpeg_stub_state.compat = compat; + } + } + + void setMpegCompatLayout(const PS2MpegCompatLayout &layout) + { + std::lock_guard lock(g_mpeg_stub_mutex); + g_mpeg_stub_state.compat = layout; + } + + void clearMpegCompatLayout() + { + std::lock_guard lock(g_mpeg_stub_mutex); + g_mpeg_stub_state.compat = {}; + } + + void resetMpegStubState() + { + std::lock_guard lock(g_mpeg_stub_mutex); + resetMpegStubStateUnlocked(); + } + + void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegFlush", rdram, ctx, runtime); + } + + void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegAddBs", rdram, ctx, runtime); + } + + void sceMpegAddCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t mpegAddr = getRegU32(ctx, 4); + const uint32_t callbackType = getRegU32(ctx, 5); + const uint32_t callbackFunc = getRegU32(ctx, 6); + const uint32_t callbackData = getRegU32(ctx, 7); + + std::lock_guard lock(g_mpeg_stub_mutex); + g_mpeg_stub_state.initialized = true; + (void)getPlaybackState(mpegAddr); + + const uint32_t handle = g_mpeg_stub_state.nextCallbackHandle++; + g_mpeg_stub_state.callbacksByMpeg[mpegAddr].push_back( + MpegRegisteredCallback{callbackType, callbackFunc, callbackData, handle}); + + setReturnU32(ctx, handle); + } + + void sceMpegAddStrCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnU32(ctx, 0u); + } + + void sceMpegClearRefBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)ctx; + (void)runtime; + static const uint32_t kRefGlobalAddrs[] = { + 0x171800u, 0x17180Cu, 0x171818u, 0x171804u, 0x171810u, 0x17181Cu}; + for (uint32_t addr : kRefGlobalAddrs) + { + uint8_t *p = getMemPtr(rdram, addr); + if (!p) + continue; + uint32_t ptr = *reinterpret_cast(p); + if (ptr != 0u) + { + uint8_t *q = getMemPtr(rdram, ptr + 0x28u); + if (q) + *reinterpret_cast(q) = 0u; + } + } + setReturnU32(ctx, 1u); + } + + static void mpegGuestWrite32(uint8_t *rdram, uint32_t addr, uint32_t value) + { + if (uint8_t *p = getMemPtr(rdram, addr)) + *reinterpret_cast(p) = value; + } + static void mpegGuestWrite64(uint8_t *rdram, uint32_t addr, uint64_t value) + { + if (uint8_t *p = getMemPtr(rdram, addr)) + { + *reinterpret_cast(p) = static_cast(value); + *reinterpret_cast(p + 4) = static_cast(value >> 32); + } + } + + void sceMpegCreate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t param_1 = getRegU32(ctx, 4); // a0 + const uint32_t param_2 = getRegU32(ctx, 5); // a1 + const uint32_t param_3 = getRegU32(ctx, 6); // a2 + + const uint32_t uVar3 = (param_2 + 3u) & 0xFFFFFFFCu; + const int32_t iVar2_signed = static_cast(param_3) - static_cast(uVar3 - param_2); + + if (iVar2_signed <= 0x117) + { + setReturnU32(ctx, 0u); + return; + } + + { + std::lock_guard lock(g_mpeg_stub_mutex); + getPlaybackState(param_1) = {}; + } + + const uint32_t puVar4 = uVar3 + 0x108u; + const uint32_t innerSize = static_cast(iVar2_signed) - 0x118u; + + mpegGuestWrite32(rdram, param_1 + 0x40, uVar3); + + const uint32_t a1_init = uVar3 + 0x118u; + mpegGuestWrite32(rdram, puVar4 + 0x0, a1_init); + mpegGuestWrite32(rdram, puVar4 + 0x4, innerSize); + mpegGuestWrite32(rdram, puVar4 + 0x8, a1_init); + mpegGuestWrite32(rdram, puVar4 + 0xC, a1_init); + + const uint32_t allocResult = runtime ? runtime->guestMalloc(0x600, 8u) : (uVar3 + 0x200u); + mpegGuestWrite32(rdram, uVar3 + 0x44, allocResult); + + // param_1[0..2] = 0; param_1[4..0xe] = 0xffffffff/0 as per decompilation + mpegGuestWrite32(rdram, param_1 + 0x00, 0); + mpegGuestWrite32(rdram, param_1 + 0x04, 0); + mpegGuestWrite32(rdram, param_1 + 0x08, 0); + mpegGuestWrite64(rdram, param_1 + 0x10, 0xFFFFFFFFFFFFFFFFULL); + mpegGuestWrite64(rdram, param_1 + 0x18, 0xFFFFFFFFFFFFFFFFULL); + mpegGuestWrite64(rdram, param_1 + 0x20, 0); + mpegGuestWrite64(rdram, param_1 + 0x28, 0xFFFFFFFFFFFFFFFFULL); + mpegGuestWrite64(rdram, param_1 + 0x30, 0xFFFFFFFFFFFFFFFFULL); + mpegGuestWrite64(rdram, param_1 + 0x38, 0); + + static const unsigned s_zeroOffsets[] = { + 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0, 0xE4, 0xE8, 0xF8, + 0x0C, 0x14, 0x2C, 0x34, 0x3C, + 0x48, 0xFC, 0x100, 0x104, 0x70, 0x90, 0xAC}; + for (unsigned off : s_zeroOffsets) + mpegGuestWrite32(rdram, uVar3 + off, 0u); + mpegGuestWrite64(rdram, uVar3 + 0x78, 0); + mpegGuestWrite64(rdram, uVar3 + 0x88, 0); + + mpegGuestWrite64(rdram, uVar3 + 0xF0, 0xFFFFFFFFFFFFFFFFULL); + mpegGuestWrite32(rdram, uVar3 + 0x1C, 0x1209F8u); + mpegGuestWrite32(rdram, uVar3 + 0x24, 0x120A08u); + mpegGuestWrite32(rdram, uVar3 + 0xB0, 1u); + mpegGuestWrite32(rdram, uVar3 + 0x9C, 0xFFFFFFFFu); + mpegGuestWrite32(rdram, uVar3 + 0x80, 0xFFFFFFFFu); + mpegGuestWrite32(rdram, uVar3 + 0x94, 0xFFFFFFFFu); + mpegGuestWrite32(rdram, uVar3 + 0x98, 0xFFFFFFFFu); + + mpegGuestWrite32(rdram, 0x1717BCu, param_1); + + static const uint32_t s_refValues[] = { + 0x171A50u, 0x171C58u, 0x171CC0u, 0x171D28u, 0x171D90u, + 0x171AB8u, 0x171B20u, 0x171B88u, 0x171BF0u}; + for (unsigned i = 0; i < 9u; ++i) + mpegGuestWrite32(rdram, 0x171800u + i * 4u, s_refValues[i]); + + uint32_t setDynamicRet = a1_init; + if (uint8_t *p = getMemPtr(rdram, puVar4 + 8)) + setDynamicRet = *reinterpret_cast(p); + mpegGuestWrite32(rdram, puVar4 + 12, setDynamicRet); + + setReturnU32(ctx, setDynamicRet); + } + + void sceMpegDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t mpegAddr = getRegU32(ctx, 4); + std::lock_guard lock(g_mpeg_stub_mutex); + g_mpeg_stub_state.callbacksByMpeg.erase(mpegAddr); + g_mpeg_stub_state.playbackByMpeg.erase(mpegAddr); + setReturnU32(ctx, 0u); + } + + void sceMpegDemuxPss(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegDemuxPss", rdram, ctx, runtime); + } + + void sceMpegDemuxPssRing(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t availableBytes = getRegU32(ctx, 6); + setReturnS32(ctx, static_cast(availableBytes)); + } + + void sceMpegDispCenterOffX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegDispCenterOffX", rdram, ctx, runtime); + } + + void sceMpegDispCenterOffY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegDispCenterOffY", rdram, ctx, runtime); + } + + void sceMpegDispHeight(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegDispHeight", rdram, ctx, runtime); + } + + void sceMpegDispWidth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegDispWidth", rdram, ctx, runtime); + } + + void sceMpegGetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegGetDecodeMode", rdram, ctx, runtime); + } + + void sceMpegGetPicture(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + const uint32_t mpegAddr = getRegU32(ctx, 4); + uint32_t picturesServed = 0u; + PS2MpegCompatLayout compat{}; + { + std::lock_guard lock(g_mpeg_stub_mutex); + MpegPlaybackState &playback = getPlaybackState(mpegAddr); + mpegGuestWrite32(rdram, mpegAddr + 0x00u, kStubMovieWidth); + mpegGuestWrite32(rdram, mpegAddr + 0x04u, kStubMovieHeight); + mpegGuestWrite32(rdram, mpegAddr + 0x08u, playback.picturesServed); + picturesServed = playback.picturesServed; + compat = g_mpeg_stub_state.compat; + playback.picturesServed += 1u; + } + + if (uint8_t *base = getMemPtr(rdram, mpegAddr)) + { + const uint32_t iVar1 = *reinterpret_cast(base + 0x40); + if (uint8_t *inner = getMemPtr(rdram, iVar1)) + { + *reinterpret_cast(inner + 0xb0) = 1; + *reinterpret_cast(inner + 0xd8) = (getRegU32(ctx, 5) & 0x0FFFFFFFu) | 0x20000000u; + *reinterpret_cast(inner + 0xe4) = getRegU32(ctx, 6); + *reinterpret_cast(inner + 0xdc) = 0; + *reinterpret_cast(inner + 0xe0) = 0; + } + } + + if (compat.matchesMpegObject(mpegAddr) && + compat.hasFinishTargets() && + (picturesServed + 1u) >= mpegCompatSyntheticFrames(compat)) + { + // No decoder yet: synthesize a safe frame so the guest can + // initialize its movie presentation path, then mark playback finished. + if (compat.videoStateAddr != 0u) + { + mpegGuestWrite32(rdram, compat.videoStateAddr, compat.finishedVideoStateValue); + } + if (compat.movieStateAddr != 0u) + { + mpegGuestWrite32(rdram, compat.movieStateAddr, compat.finishedMovieStateValue); + } + } + + setReturnU32(ctx, 0u); + } + + void sceMpegGetPictureRAW8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegGetPictureRAW8", rdram, ctx, runtime); + } + + void sceMpegGetPictureRAW8xy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegGetPictureRAW8xy", rdram, ctx, runtime); + } + + void sceMpegInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + std::lock_guard lock(g_mpeg_stub_mutex); + resetMpegStubStateUnlocked(); + g_mpeg_stub_state.initialized = true; + setReturnU32(ctx, 0u); + } + + void sceMpegIsEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + const uint32_t mpegAddr = getRegU32(ctx, 4); + + std::lock_guard lock(g_mpeg_stub_mutex); + g_mpeg_stub_state.initialized = true; + const MpegPlaybackState &playback = getPlaybackState(mpegAddr); + if (g_mpeg_stub_state.compat.matchesMpegObject(mpegAddr)) + { + setReturnS32(ctx, playback.picturesServed >= mpegCompatSyntheticFrames(g_mpeg_stub_state.compat) ? 1 : 0); + return; + } + + // Generic fallback: keep decode threads alive until a game-specific path + // decides to stop playback. + setReturnS32(ctx, 0); + } + + void sceMpegIsRefBuffEmpty(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegIsRefBuffEmpty", rdram, ctx, runtime); + } + + void sceMpegReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + const uint32_t param_1 = getRegU32(ctx, 4); + { + std::lock_guard lock(g_mpeg_stub_mutex); + g_mpeg_stub_state.playbackByMpeg[param_1] = {}; + } + uint8_t *base = getMemPtr(rdram, param_1); + if (!base) + { + return; + } + uint32_t inner = *reinterpret_cast(base + 0x40); + if (inner == 0u) + return; + mpegGuestWrite32(rdram, param_1 + 0x00u, 0u); + mpegGuestWrite32(rdram, param_1 + 0x04u, 0u); + mpegGuestWrite32(rdram, param_1 + 0x08u, 0u); + mpegGuestWrite32(rdram, inner + 0x00, 0u); + mpegGuestWrite32(rdram, inner + 0x04, 0u); + mpegGuestWrite32(rdram, inner + 0x08, 0u); + mpegGuestWrite32(rdram, param_1 + 0x08, 0u); + mpegGuestWrite32(rdram, inner + 0x80, 0xFFFFFFFFu); + mpegGuestWrite32(rdram, inner + 0xAC, 0u); + mpegGuestWrite32(rdram, 0x171904u, 0u); + } + + void sceMpegResetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegResetDefaultPtsGap", rdram, ctx, runtime); + } + + void sceMpegSetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegSetDecodeMode", rdram, ctx, runtime); + } + + void sceMpegSetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegSetDefaultPtsGap", rdram, ctx, runtime); + } + + void sceMpegSetImageBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceMpegSetImageBuff", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/MPEG.h b/ps2xRuntime/src/lib/Kernel/Stubs/MPEG.h new file mode 100644 index 00000000..a77dacc5 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/MPEG.h @@ -0,0 +1,33 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void resetMpegStubState(); + void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegAddCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegAddStrCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegClearRefBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegCreate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDemuxPss(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDemuxPssRing(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDispCenterOffX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDispCenterOffY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDispHeight(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegDispWidth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegGetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegGetPicture(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegGetPictureRAW8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegGetPictureRAW8xy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegIsEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegIsRefBuffEmpty(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegResetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegSetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegSetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMpegSetImageBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/MemoryCard.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/MemoryCard.cpp new file mode 100644 index 00000000..1f82c353 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/MemoryCard.cpp @@ -0,0 +1,1461 @@ +#include "Common.h" +#include "MemoryCard.h" + +namespace ps2_stubs +{ + namespace + { + constexpr int32_t kMcCmdGetInfo = 0x01; + constexpr int32_t kMcCmdOpen = 0x02; + constexpr int32_t kMcCmdClose = 0x03; + constexpr int32_t kMcCmdSeek = 0x04; + constexpr int32_t kMcCmdRead = 0x05; + constexpr int32_t kMcCmdWrite = 0x06; + constexpr int32_t kMcCmdFlush = 0x0A; + constexpr int32_t kMcCmdMkdir = 0x0B; + constexpr int32_t kMcCmdChdir = 0x0C; + constexpr int32_t kMcCmdGetDir = 0x0D; + constexpr int32_t kMcCmdSetFileInfo = 0x0E; + constexpr int32_t kMcCmdDelete = 0x0F; + constexpr int32_t kMcCmdFormat = 0x10; + constexpr int32_t kMcCmdUnformat = 0x11; + constexpr int32_t kMcCmdGetEntSpace = 0x12; + constexpr int32_t kMcCmdRename = 0x13; + + constexpr int32_t kMcResultSucceed = 0; + constexpr int32_t kMcResultChangedCard = -1; + constexpr int32_t kMcResultNoFormat = -2; + constexpr int32_t kMcResultNoEntry = -4; + constexpr int32_t kMcResultDeniedPermit = -5; + constexpr int32_t kMcResultNotEmpty = -6; + constexpr int32_t kMcResultUpLimitHandle = -7; + + constexpr int32_t kMcTypePs2 = 2; + constexpr int32_t kMcFormatted = 1; + constexpr int32_t kMcUnformatted = 0; + constexpr int32_t kMcFreeClusters = 0x2000; + constexpr size_t kMcMaxPathLen = 1024; + constexpr size_t kMcMaxOpenFiles = 32; + + constexpr uint16_t kMcAttrReadable = 0x0001; + constexpr uint16_t kMcAttrWriteable = 0x0002; + constexpr uint16_t kMcAttrFile = 0x0010; + constexpr uint16_t kMcAttrSubdir = 0x0020; + constexpr uint16_t kMcAttrClosed = 0x0080; + constexpr uint16_t kMcAttrExists = 0x8000; + + struct SceMcStDateTime + { + uint8_t Resv2 = 0; + uint8_t Sec = 0; + uint8_t Min = 0; + uint8_t Hour = 0; + uint8_t Day = 0; + uint8_t Month = 0; + uint16_t Year = 0; + }; + + struct SceMcTblGetDir + { + SceMcStDateTime _Create{}; + SceMcStDateTime _Modify{}; + uint32_t FileSizeByte = 0; + uint16_t AttrFile = 0; + uint16_t Reserve1 = 0; + uint32_t Reserve2 = 0; + uint32_t PdaAplNo = 0; + char EntryName[32]{}; + }; + + static_assert(sizeof(SceMcTblGetDir) == 64, "sceMcTblGetDir size mismatch"); + + struct McOpenFile + { + FILE *file = nullptr; + int32_t port = 0; + std::filesystem::path hostPath; + }; + + struct McPortState + { + std::string currentDir = "/"; + bool formatted = true; + }; + + std::mutex g_mcStateMutex; + int32_t g_mcNextFd = 1; + int32_t g_mcLastCmd = 0; + int32_t g_mcLastResult = 0; + std::unordered_map g_mcFiles; + std::array g_mcPorts{}; + int32_t g_cvMcFileCursor = 0; + constexpr int32_t kCvMcFreeCapacityBytes = 0x01000000; + constexpr int32_t kCvMcSaveCapacityBytes = 0x00080000; + constexpr int32_t kCvMcConfigCapacityBytes = 0x00008000; + constexpr int32_t kCvMcIconCapacityBytes = 0x00004000; + + bool isValidMcPortSlot(int32_t port, int32_t slot) + { + return port >= 0 && port < static_cast(g_mcPorts.size()) && slot == 0; + } + + std::filesystem::path getMcRootPath(int32_t port) + { + const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths(); + std::filesystem::path root = paths.mcRoot; + if (root.empty()) + { + if (!paths.elfDirectory.empty()) + { + root = paths.elfDirectory / "mc0"; + } + else + { + std::error_code ec; + const std::filesystem::path cwd = std::filesystem::current_path(ec); + root = ec ? std::filesystem::path("mc0") : (cwd / "mc0"); + } + } + + root = root.lexically_normal(); + if (port <= 0) + { + return root; + } + + const std::filesystem::path parent = root.parent_path(); + const std::string leaf = root.filename().string(); + const std::string lowerLeaf = toLowerAscii(leaf); + if (lowerLeaf == "mc0") + { + return (parent / "mc1").lexically_normal(); + } + if (leaf.empty()) + { + return (root / "mc1").lexically_normal(); + } + + return (parent / (leaf + "_slot" + std::to_string(port))).lexically_normal(); + } + + void ensureMcRootExists(int32_t port) + { + std::error_code ec; + std::filesystem::create_directories(getMcRootPath(port), ec); + } + + std::vector splitMcPathComponents(const std::string &value) + { + std::vector parts; + std::string current; + for (char c : value) + { + if (c == '/' || c == '\\') + { + if (!current.empty()) + { + parts.push_back(current); + current.clear(); + } + } + else + { + current.push_back(c); + } + } + + if (!current.empty()) + { + parts.push_back(current); + } + + return parts; + } + + std::string joinMcPathComponents(const std::vector &parts) + { + if (parts.empty()) + { + return "/"; + } + + std::string joined = "/"; + for (size_t i = 0; i < parts.size(); ++i) + { + if (i != 0u) + { + joined.push_back('/'); + } + joined.append(parts[i]); + } + return joined; + } + + std::string normalizeGuestMcPathLocked(int32_t port, std::string path) + { + std::replace(path.begin(), path.end(), '\\', '/'); + const std::string lower = toLowerAscii(path); + if (lower.rfind("mc0:", 0) == 0 || lower.rfind("mc1:", 0) == 0) + { + path = path.substr(4); + } + + const bool absolute = !path.empty() && path.front() == '/'; + std::vector parts; + if (!absolute && port >= 0 && port < static_cast(g_mcPorts.size())) + { + parts = splitMcPathComponents(g_mcPorts[static_cast(port)].currentDir); + } + + for (const std::string &part : splitMcPathComponents(path)) + { + if (part.empty() || part == ".") + { + continue; + } + + if (part == "..") + { + if (!parts.empty()) + { + parts.pop_back(); + } + continue; + } + + parts.push_back(part); + } + + return joinMcPathComponents(parts); + } + + std::filesystem::path guestMcPathToHostPath(int32_t port, const std::string &guestPath) + { + std::filesystem::path resolved = getMcRootPath(port); + if (guestPath.size() > 1u) + { + resolved /= std::filesystem::path(guestPath.substr(1)); + } + return resolved.lexically_normal(); + } + + bool localtimeSafeMc(const std::time_t *value, std::tm *out) + { +#ifdef _WIN32 + return localtime_s(out, value) == 0; +#else + return localtime_r(value, out) != nullptr; +#endif + } + + std::time_t fileTimeToTimeTMc(std::filesystem::file_time_type value) + { + const auto systemTime = std::chrono::time_point_cast( + value - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()); + return std::chrono::system_clock::to_time_t(systemTime); + } + + void writeMcCString(uint8_t *rdram, uint32_t addr, const std::string &value) + { + if (addr == 0u) + { + return; + } + + uint8_t *dst = getMemPtr(rdram, addr); + if (!dst) + { + return; + } + + std::memcpy(dst, value.c_str(), value.size() + 1u); + } + + void writeMcDateTime(SceMcStDateTime &out, std::time_t value) + { + std::tm tm{}; + if (!localtimeSafeMc(&value, &tm)) + { + std::memset(&out, 0, sizeof(out)); + return; + } + + out.Resv2 = 0; + out.Sec = static_cast(tm.tm_sec); + out.Min = static_cast(tm.tm_min); + out.Hour = static_cast(tm.tm_hour); + out.Day = static_cast(tm.tm_mday); + out.Month = static_cast(tm.tm_mon + 1); + out.Year = static_cast(tm.tm_year + 1900); + } + + void fillMcDirTableEntry(SceMcTblGetDir &entry, + const std::string &name, + bool isDirectory, + uint32_t sizeBytes, + std::time_t modifiedTime) + { + std::memset(&entry, 0, sizeof(entry)); + writeMcDateTime(entry._Create, modifiedTime); + writeMcDateTime(entry._Modify, modifiedTime); + entry.FileSizeByte = isDirectory ? 0u : sizeBytes; + entry.AttrFile = static_cast(kMcAttrReadable | + kMcAttrWriteable | + (isDirectory ? kMcAttrSubdir : kMcAttrFile) | + kMcAttrClosed | + kMcAttrExists); + std::strncpy(entry.EntryName, name.c_str(), sizeof(entry.EntryName) - 1u); + entry.EntryName[sizeof(entry.EntryName) - 1u] = '\0'; + } + + bool wildcardMatch(const std::string &pattern, const std::string &value) + { + size_t patternPos = 0u; + size_t valuePos = 0u; + size_t starPos = std::string::npos; + size_t matchPos = 0u; + + while (valuePos < value.size()) + { + if (patternPos < pattern.size() && + (pattern[patternPos] == '?' || pattern[patternPos] == value[valuePos])) + { + ++patternPos; + ++valuePos; + } + else if (patternPos < pattern.size() && pattern[patternPos] == '*') + { + starPos = patternPos++; + matchPos = valuePos; + } + else if (starPos != std::string::npos) + { + patternPos = starPos + 1u; + valuePos = ++matchPos; + } + else + { + return false; + } + } + + while (patternPos < pattern.size() && pattern[patternPos] == '*') + { + ++patternPos; + } + + return patternPos == pattern.size(); + } + + void setMcCommandResultLocked(int32_t cmd, int32_t result) + { + g_mcLastCmd = cmd; + g_mcLastResult = result; + } + + void closeMcFilesLocked() + { + for (auto &[fd, openFile] : g_mcFiles) + { + if (openFile.file) + { + std::fclose(openFile.file); + openFile.file = nullptr; + } + } + g_mcFiles.clear(); + } + + void closeMcFilesForPortLocked(int32_t port) + { + for (auto it = g_mcFiles.begin(); it != g_mcFiles.end();) + { + if (it->second.port == port) + { + if (it->second.file) + { + std::fclose(it->second.file); + } + it = g_mcFiles.erase(it); + } + else + { + ++it; + } + } + } + + int32_t allocateMcFdLocked(FILE *file, int32_t port, const std::filesystem::path &hostPath) + { + if (!file) + { + return kMcResultDeniedPermit; + } + if (g_mcFiles.size() >= kMcMaxOpenFiles) + { + return kMcResultUpLimitHandle; + } + + for (int attempt = 0; attempt < 0x10000; ++attempt) + { + if (g_mcNextFd <= 0) + { + g_mcNextFd = 1; + } + + const int32_t fd = g_mcNextFd++; + if (g_mcFiles.find(fd) != g_mcFiles.end()) + { + continue; + } + + g_mcFiles.emplace(fd, McOpenFile{file, port, hostPath}); + return fd; + } + + return kMcResultUpLimitHandle; + } + + FILE *openMcHostFile(const std::filesystem::path &hostPath, uint32_t flags) + { + const uint32_t access = flags & PS2_FIO_O_RDWR; + const bool read = (access == PS2_FIO_O_RDONLY) || (access == PS2_FIO_O_RDWR); + const bool write = (access == PS2_FIO_O_WRONLY) || (access == PS2_FIO_O_RDWR); + const bool append = (flags & PS2_FIO_O_APPEND) != 0u; + const bool create = (flags & PS2_FIO_O_CREAT) != 0u; + const bool truncate = (flags & PS2_FIO_O_TRUNC) != 0u; + + std::error_code ec; + const bool exists = std::filesystem::exists(hostPath, ec) && !ec; + + const char *mode = "rb"; + if (read && write) + { + if (append) + { + mode = exists ? "a+b" : "w+b"; + } + else if (truncate || (create && !exists)) + { + mode = "w+b"; + } + else + { + mode = "r+b"; + } + } + else if (write) + { + if (append) + { + mode = exists ? "ab" : "wb"; + } + else if (truncate || (create && !exists)) + { + mode = "wb"; + } + else + { + mode = "r+b"; + } + } + + return std::fopen(hostPath.string().c_str(), mode); + } + } + + void sceMcChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceMcChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const uint32_t pathAddr = getRegU32(ctx, 6); + const uint32_t currentDirAddr = getRegU32(ctx, 7); + const std::string requestedDir = + (pathAddr != 0u) ? readPs2CStringBounded(rdram, pathAddr, kMcMaxPathLen) : std::string{}; + + std::string currentDir = "/"; + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + currentDir = state.currentDir; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + ensureMcRootExists(port); + const std::string resolvedDir = + requestedDir.empty() ? state.currentDir : normalizeGuestMcPathLocked(port, requestedDir); + const std::filesystem::path hostDir = guestMcPathToHostPath(port, resolvedDir); + std::error_code ec; + if (std::filesystem::exists(hostDir, ec) && !ec && + std::filesystem::is_directory(hostDir, ec)) + { + state.currentDir = resolvedDir; + currentDir = resolvedDir; + result = kMcResultSucceed; + } + } + } + + setMcCommandResultLocked(kMcCmdChdir, result); + } + + writeMcCString(rdram, currentDirAddr, currentDir); + setReturnS32(ctx, 0); + } + + void sceMcClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t fd = static_cast(getRegU32(ctx, 4)); + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + auto it = g_mcFiles.find(fd); + if (it != g_mcFiles.end()) + { + if (!it->second.file || std::fclose(it->second.file) == 0) + { + result = kMcResultSucceed; + } + g_mcFiles.erase(it); + } + setMcCommandResultLocked(kMcCmdClose, result); + } + setReturnS32(ctx, 0); + } + + void sceMcDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const std::string path = readPs2CStringBounded(rdram, getRegU32(ctx, 6), kMcMaxPathLen); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + const std::string guestPath = normalizeGuestMcPathLocked(port, path); + if (guestPath != "/") + { + const std::filesystem::path hostPath = guestMcPathToHostPath(port, guestPath); + std::error_code ec; + if (std::filesystem::exists(hostPath, ec) && !ec) + { + if (std::filesystem::is_directory(hostPath, ec) && + !std::filesystem::is_empty(hostPath, ec)) + { + result = kMcResultNotEmpty; + } + else if (std::filesystem::remove(hostPath, ec) && !ec) + { + result = kMcResultSucceed; + } + else + { + result = kMcResultDeniedPermit; + } + } + } + } + } + + setMcCommandResultLocked(kMcCmdDelete, result); + } + setReturnS32(ctx, 0); + } + + void sceMcEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + { + std::lock_guard lock(g_mcStateMutex); + closeMcFilesLocked(); + g_mcNextFd = 1; + g_mcLastCmd = 0; + g_mcLastResult = 0; + for (McPortState &state : g_mcPorts) + { + state.currentDir = "/"; + } + } + + // libmc teardown is just a local state reset in this immediate runtime model. + setReturnS32(ctx, 0); + } + + void sceMcFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t fd = static_cast(getRegU32(ctx, 4)); + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + auto it = g_mcFiles.find(fd); + if (it != g_mcFiles.end() && it->second.file) + { + result = (std::fflush(it->second.file) == 0) ? kMcResultSucceed : kMcResultDeniedPermit; + } + setMcCommandResultLocked(kMcCmdFlush, result); + } + setReturnS32(ctx, 0); + } + + void sceMcFormat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + closeMcFilesForPortLocked(port); + const std::filesystem::path root = getMcRootPath(port); + std::error_code ec; + std::filesystem::remove_all(root, ec); + ec.clear(); + std::filesystem::create_directories(root, ec); + if (!ec) + { + McPortState &state = g_mcPorts[static_cast(port)]; + state.currentDir = "/"; + state.formatted = true; + result = kMcResultSucceed; + } + } + + setMcCommandResultLocked(kMcCmdFormat, result); + } + setReturnS32(ctx, 0); + } + + void sceMcGetDir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const std::string rawPath = readPs2CStringBounded(rdram, getRegU32(ctx, 6), kMcMaxPathLen); + const int32_t maxEntries = static_cast(readStackU32(rdram, ctx, 16)); + const uint32_t tableAddr = readStackU32(rdram, ctx, 20); + + std::vector entries; + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + ensureMcRootExists(port); + const std::string guestQuery = + normalizeGuestMcPathLocked(port, rawPath.empty() ? "." : rawPath); + const bool hasWildcard = + guestQuery.find('*') != std::string::npos || guestQuery.find('?') != std::string::npos; + + const std::filesystem::path queryRel = + (guestQuery.size() > 1u) ? std::filesystem::path(guestQuery.substr(1)) : std::filesystem::path{}; + + std::filesystem::path parentRel; + std::string pattern; + if (hasWildcard) + { + parentRel = queryRel.parent_path(); + pattern = queryRel.filename().string(); + } + else + { + const std::filesystem::path queryHostPath = guestMcPathToHostPath(port, guestQuery); + std::error_code queryEc; + if (std::filesystem::exists(queryHostPath, queryEc) && !queryEc && + std::filesystem::is_directory(queryHostPath, queryEc)) + { + parentRel = queryRel; + pattern = "*"; + } + else + { + parentRel = queryRel.parent_path(); + pattern = queryRel.filename().string(); + } + } + + if (pattern.empty()) + { + pattern = "*"; + } + + std::filesystem::path hostDir = getMcRootPath(port); + if (!parentRel.empty()) + { + hostDir /= parentRel; + } + hostDir = hostDir.lexically_normal(); + + std::error_code ec; + if (std::filesystem::exists(hostDir, ec) && !ec && + std::filesystem::is_directory(hostDir, ec)) + { + const std::time_t now = std::time(nullptr); + auto appendSpecial = [&](const std::string &name) + { + if (!wildcardMatch(pattern, name)) + { + return; + } + SceMcTblGetDir entry{}; + fillMcDirTableEntry(entry, name, true, 0u, now); + entries.push_back(entry); + }; + + appendSpecial("."); + appendSpecial(".."); + + std::vector dirEntries; + for (const auto &entry : std::filesystem::directory_iterator( + hostDir, std::filesystem::directory_options::skip_permission_denied, ec)) + { + if (ec) + { + break; + } + dirEntries.push_back(entry); + } + + std::sort(dirEntries.begin(), dirEntries.end(), + [](const std::filesystem::directory_entry &lhs, + const std::filesystem::directory_entry &rhs) + { + return toLowerAscii(lhs.path().filename().string()) < + toLowerAscii(rhs.path().filename().string()); + }); + + for (const auto &entry : dirEntries) + { + const std::string name = entry.path().filename().string(); + if (!wildcardMatch(pattern, name)) + { + continue; + } + + std::error_code entryEc; + const bool isDirectory = entry.is_directory(entryEc) && !entryEc; + const uint32_t sizeBytes = + isDirectory ? 0u : static_cast(entry.file_size(entryEc)); + entryEc.clear(); + const std::time_t modifiedTime = fileTimeToTimeTMc(entry.last_write_time(entryEc)); + SceMcTblGetDir tableEntry{}; + fillMcDirTableEntry(tableEntry, + name, + isDirectory, + sizeBytes, + entryEc ? now : modifiedTime); + entries.push_back(tableEntry); + } + + const size_t entryCount = + std::min(entries.size(), maxEntries > 0 ? static_cast(maxEntries) : 0u); + if (entryCount == 0u || tableAddr == 0u) + { + result = static_cast(entryCount); + } + else if (uint8_t *dst = getMemPtr(rdram, tableAddr)) + { + std::memcpy(dst, entries.data(), entryCount * sizeof(SceMcTblGetDir)); + result = static_cast(entryCount); + } + else + { + result = kMcResultDeniedPermit; + } + } + } + } + + setMcCommandResultLocked(kMcCmdGetDir, result); + } + setReturnS32(ctx, 0); + } + + void sceMcGetEntSpace(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1024); + } + + void sceMcGetInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const uint32_t typePtr = getRegU32(ctx, 6); + const uint32_t freePtr = getRegU32(ctx, 7); + const uint32_t formatPtr = readStackU32(rdram, ctx, 16); + + int32_t cardType = 0; + int32_t freeBlocks = 0; + int32_t format = kMcUnformatted; + int32_t result = kMcResultNoEntry; + + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + cardType = kMcTypePs2; + freeBlocks = state.formatted ? kMcFreeClusters : 0; + format = state.formatted ? kMcFormatted : kMcUnformatted; + result = state.formatted ? kMcResultSucceed : kMcResultNoFormat; + } + + setMcCommandResultLocked(kMcCmdGetInfo, result); + } + + if (typePtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, typePtr)) + { + std::memcpy(out, &cardType, sizeof(cardType)); + } + } + if (freePtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, freePtr)) + { + std::memcpy(out, &freeBlocks, sizeof(freeBlocks)); + } + } + if (formatPtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, formatPtr)) + { + std::memcpy(out, &format, sizeof(format)); + } + } + + setReturnS32(ctx, 0); + } + + void sceMcGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceMcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + { + std::lock_guard lock(g_mcStateMutex); + closeMcFilesLocked(); + g_mcNextFd = 1; + g_mcLastCmd = 0; + g_mcLastResult = 0; + for (McPortState &state : g_mcPorts) + { + state.currentDir = "/"; + state.formatted = true; + } + } + ensureMcRootExists(0); + ensureMcRootExists(1); + setReturnS32(ctx, 0); + } + + void sceMcMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const std::string path = readPs2CStringBounded(rdram, getRegU32(ctx, 6), kMcMaxPathLen); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + ensureMcRootExists(port); + const std::string guestPath = normalizeGuestMcPathLocked(port, path); + const std::filesystem::path hostPath = guestMcPathToHostPath(port, guestPath); + std::error_code ec; + if (std::filesystem::exists(hostPath, ec) && !ec) + { + result = std::filesystem::is_directory(hostPath, ec) ? kMcResultSucceed : kMcResultDeniedPermit; + } + else if (std::filesystem::create_directory(hostPath, ec) && !ec) + { + result = kMcResultSucceed; + } + else + { + result = std::filesystem::exists(hostPath.parent_path(), ec) && !ec + ? kMcResultDeniedPermit + : kMcResultNoEntry; + } + } + } + + setMcCommandResultLocked(kMcCmdMkdir, result); + } + setReturnS32(ctx, 0); + } + + void sceMcOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const std::string path = readPs2CStringBounded(rdram, getRegU32(ctx, 6), kMcMaxPathLen); + const uint32_t flags = getRegU32(ctx, 7); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + const std::string guestPath = normalizeGuestMcPathLocked(port, path); + const std::filesystem::path hostPath = guestMcPathToHostPath(port, guestPath); + std::error_code ec; + const bool create = (flags & PS2_FIO_O_CREAT) != 0u; + const bool exists = std::filesystem::exists(hostPath, ec) && !ec; + if (guestPath == "/") + { + result = kMcResultDeniedPermit; + } + else if (exists && std::filesystem::is_directory(hostPath, ec)) + { + result = kMcResultDeniedPermit; + } + else if (!exists && !create) + { + result = kMcResultNoEntry; + } + else if (!std::filesystem::exists(hostPath.parent_path(), ec) || ec) + { + result = kMcResultNoEntry; + } + else + { + FILE *file = openMcHostFile(hostPath, flags); + if (!file) + { + result = exists ? kMcResultDeniedPermit : kMcResultNoEntry; + } + else + { + result = allocateMcFdLocked(file, port, hostPath); + if (result < 0) + { + std::fclose(file); + } + } + } + } + } + setMcCommandResultLocked(kMcCmdOpen, result); + } + setReturnS32(ctx, 0); + } + + void sceMcRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t fd = static_cast(getRegU32(ctx, 4)); + const uint32_t dstAddr = getRegU32(ctx, 5); + const int32_t size = static_cast(getRegU32(ctx, 6)); + uint8_t *dst = (size > 0) ? getMemPtr(rdram, dstAddr) : nullptr; + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + auto it = g_mcFiles.find(fd); + if (size <= 0) + { + result = 0; + } + else if (it == g_mcFiles.end() || !it->second.file) + { + result = kMcResultNoEntry; + } + else if (!dst) + { + result = kMcResultDeniedPermit; + } + else + { + const size_t bytesRead = std::fread(dst, 1u, static_cast(size), it->second.file); + result = std::ferror(it->second.file) ? kMcResultDeniedPermit : static_cast(bytesRead); + if (std::ferror(it->second.file)) + { + std::clearerr(it->second.file); + } + } + + setMcCommandResultLocked(kMcCmdRead, result); + } + setReturnS32(ctx, 0); + } + + void sceMcRename(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const std::string oldPath = readPs2CStringBounded(rdram, getRegU32(ctx, 6), kMcMaxPathLen); + const std::string newPath = readPs2CStringBounded(rdram, getRegU32(ctx, 7), kMcMaxPathLen); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + const std::filesystem::path oldHostPath = + guestMcPathToHostPath(port, normalizeGuestMcPathLocked(port, oldPath)); + const std::filesystem::path newHostPath = + guestMcPathToHostPath(port, normalizeGuestMcPathLocked(port, newPath)); + std::error_code ec; + if (std::filesystem::exists(oldHostPath, ec) && !ec && + std::filesystem::exists(newHostPath.parent_path(), ec) && !ec) + { + std::filesystem::rename(oldHostPath, newHostPath, ec); + result = ec ? kMcResultDeniedPermit : kMcResultSucceed; + } + } + } + + setMcCommandResultLocked(kMcCmdRename, result); + } + setReturnS32(ctx, 0); + } + + void sceMcSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t fd = static_cast(getRegU32(ctx, 4)); + const int32_t offset = static_cast(getRegU32(ctx, 5)); + const int32_t origin = static_cast(getRegU32(ctx, 6)); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + auto it = g_mcFiles.find(fd); + if (it != g_mcFiles.end() && it->second.file) + { + int whence = SEEK_SET; + if (origin == PS2_FIO_SEEK_CUR) + { + whence = SEEK_CUR; + } + else if (origin == PS2_FIO_SEEK_END) + { + whence = SEEK_END; + } + + if (std::fseek(it->second.file, offset, whence) == 0) + { + const long position = std::ftell(it->second.file); + result = (position >= 0) ? static_cast(position) : kMcResultDeniedPermit; + } + else + { + result = kMcResultDeniedPermit; + } + } + + setMcCommandResultLocked(kMcCmdSeek, result); + } + setReturnS32(ctx, 0); + } + + void sceMcSetFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + const std::string path = readPs2CStringBounded(rdram, getRegU32(ctx, 6), kMcMaxPathLen); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + McPortState &state = g_mcPorts[static_cast(port)]; + if (!state.formatted) + { + result = kMcResultNoFormat; + } + else + { + const std::filesystem::path hostPath = + guestMcPathToHostPath(port, normalizeGuestMcPathLocked(port, path)); + std::error_code ec; + if (std::filesystem::exists(hostPath, ec) && !ec) + { + result = kMcResultSucceed; + } + } + } + + setMcCommandResultLocked(kMcCmdSetFileInfo, result); + } + setReturnS32(ctx, 0); + } + + void sceMcSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cmdPtr = getRegU32(ctx, 5); + const uint32_t resultPtr = getRegU32(ctx, 6); + int32_t cmd = 0; + int32_t result = 0; + { + std::lock_guard lock(g_mcStateMutex); + cmd = g_mcLastCmd; + result = g_mcLastResult; + } + + if (cmdPtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, cmdPtr)) + { + std::memcpy(out, &cmd, sizeof(cmd)); + } + } + if (resultPtr != 0u) + { + if (uint8_t *out = getMemPtr(rdram, resultPtr)) + { + std::memcpy(out, &result, sizeof(result)); + } + } + + // 1 = command finished in this runtime's immediate model. + setReturnS32(ctx, 1); + } + + void sceMcUnformat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t port = static_cast(getRegU32(ctx, 4)); + const int32_t slot = static_cast(getRegU32(ctx, 5)); + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + if (isValidMcPortSlot(port, slot)) + { + closeMcFilesForPortLocked(port); + const std::filesystem::path root = getMcRootPath(port); + std::error_code ec; + std::filesystem::remove_all(root, ec); + ec.clear(); + std::filesystem::create_directories(root, ec); + if (!ec) + { + McPortState &state = g_mcPorts[static_cast(port)]; + state.currentDir = "/"; + state.formatted = false; + result = kMcResultSucceed; + } + } + + setMcCommandResultLocked(kMcCmdUnformat, result); + } + setReturnS32(ctx, 0); + } + + void sceMcWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t fd = static_cast(getRegU32(ctx, 4)); + const uint32_t srcAddr = getRegU32(ctx, 5); + const int32_t size = static_cast(getRegU32(ctx, 6)); + const uint8_t *src = (size > 0) ? getConstMemPtr(rdram, srcAddr) : nullptr; + + int32_t result = kMcResultNoEntry; + { + std::lock_guard lock(g_mcStateMutex); + auto it = g_mcFiles.find(fd); + if (size <= 0) + { + result = 0; + } + else if (it == g_mcFiles.end() || !it->second.file) + { + result = kMcResultNoEntry; + } + else if (!src) + { + result = kMcResultDeniedPermit; + } + else + { + const size_t bytesWritten = std::fwrite(src, 1u, static_cast(size), it->second.file); + result = std::ferror(it->second.file) ? kMcResultDeniedPermit : static_cast(bytesWritten); + if (!std::ferror(it->second.file)) + { + std::fflush(it->second.file); + } + else + { + std::clearerr(it->second.file); + } + } + + setMcCommandResultLocked(kMcCmdWrite, result); + } + setReturnS32(ctx, 0); + } + + void mcCallMessageTypeSe(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcCheckReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCheckReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCheckWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCheckWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCreateConfigInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCreateFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCreateIconInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcCreateSaveFileInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcDispFileName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcDispFileNumber(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcDisplayFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcDisplaySelectFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcDisplaySelectFileInfoMesCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcDispWindowCurSol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcDispWindowFoundtion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mceGetInfoApdx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mceIntrReadFixAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mceStorePwd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcGetConfigCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, kCvMcConfigCapacityBytes); + } + + void mcGetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, g_cvMcFileCursor); + } + + void mcGetFreeCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, kCvMcFreeCapacityBytes); + } + + void mcGetIconCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, kCvMcIconCapacityBytes); + } + + void mcGetIconFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, kCvMcIconCapacityBytes); + } + + void mcGetPortSelectDirInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcGetSaveFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, kCvMcSaveCapacityBytes); + } + + void mcGetStringEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t strAddr = getRegU32(ctx, 4); + const std::string value = readPs2CStringBounded(rdram, runtime, strAddr, 1024); + setReturnU32(ctx, strAddr + static_cast(value.size())); + } + + void mcMoveFileSelectWindowCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t delta = static_cast(getRegU32(ctx, 5)); + g_cvMcFileCursor += delta; + g_cvMcFileCursor = std::clamp(g_cvMcFileCursor, -1, 15); + setReturnS32(ctx, 0); + } + + void mcNewCreateConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcNewCreateIcon(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcNewCreateSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcReadIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcSelectFileInfoInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + g_cvMcFileCursor = 0; + setReturnS32(ctx, 1); + } + + void mcSelectSaveFileCheck(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcSetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + g_cvMcFileCursor = static_cast(getRegU32(ctx, 5)); + g_cvMcFileCursor = std::clamp(g_cvMcFileCursor, -1, 15); + setReturnS32(ctx, 0); + } + + void mcSetFileSelectWindowCursolInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + g_cvMcFileCursor = 0; + setReturnS32(ctx, 0); + } + + void mcSetStringSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcSetTyepWriteMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void mcWriteIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void mcWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/MemoryCard.h b/ps2xRuntime/src/lib/Kernel/Stubs/MemoryCard.h new file mode 100644 index 00000000..8cb953d3 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/MemoryCard.h @@ -0,0 +1,71 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceMcChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcFormat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcGetDir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcGetEntSpace(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcGetInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcRename(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcSetFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcUnformat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceMcWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCallMessageTypeSe(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCheckReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCheckReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCheckWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCheckWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCreateConfigInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCreateFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCreateIconInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcCreateSaveFileInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDispFileName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDispFileNumber(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDisplayFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDisplaySelectFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDisplaySelectFileInfoMesCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDispWindowCurSol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcDispWindowFoundtion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mceGetInfoApdx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mceIntrReadFixAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mceStorePwd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetConfigCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetFreeCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetIconCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetIconFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetPortSelectDirInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetSaveFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcGetStringEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcMoveFileSelectWindowCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcNewCreateConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcNewCreateIcon(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcNewCreateSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcReadIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcSelectFileInfoInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcSelectSaveFileCheck(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcSetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcSetFileSelectWindowCursolInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcSetStringSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcSetTyepWriteMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcWriteIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void mcWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Pad.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/Pad.cpp new file mode 100644 index 00000000..170ffd04 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Pad.cpp @@ -0,0 +1,759 @@ +#include "Common.h" +#include "Pad.h" + +namespace ps2_stubs +{ + namespace + { + constexpr uint8_t kPadModeDigital = 0x41; + constexpr uint8_t kPadModeDualShock = 0x73; + constexpr uint8_t kPadAnalogCenter = 0x80; + constexpr int32_t kPadTypeDigital = 4; + constexpr int32_t kPadTypeDualShock = 7; + constexpr int32_t kPadStateDisconnected = 0; + constexpr int32_t kPadStateStable = 6; + constexpr size_t kPadPortCount = 2; + constexpr size_t kPadSlotCount = 1; + + constexpr uint16_t kPadBtnSelect = 1u << 0; + constexpr uint16_t kPadBtnL3 = 1u << 1; + constexpr uint16_t kPadBtnR3 = 1u << 2; + constexpr uint16_t kPadBtnStart = 1u << 3; + constexpr uint16_t kPadBtnUp = 1u << 4; + constexpr uint16_t kPadBtnRight = 1u << 5; + constexpr uint16_t kPadBtnDown = 1u << 6; + constexpr uint16_t kPadBtnLeft = 1u << 7; + constexpr uint16_t kPadBtnL2 = 1u << 8; + constexpr uint16_t kPadBtnR2 = 1u << 9; + constexpr uint16_t kPadBtnL1 = 1u << 10; + constexpr uint16_t kPadBtnR1 = 1u << 11; + constexpr uint16_t kPadBtnTriangle = 1u << 12; + constexpr uint16_t kPadBtnCircle = 1u << 13; + constexpr uint16_t kPadBtnCross = 1u << 14; + constexpr uint16_t kPadBtnSquare = 1u << 15; + + struct PadInputState + { + uint16_t buttons = 0xFFFF; // active-low + uint8_t rx = kPadAnalogCenter; + uint8_t ry = kPadAnalogCenter; + uint8_t lx = kPadAnalogCenter; + uint8_t ly = kPadAnalogCenter; + }; + + struct PadPortState + { + bool open = false; + bool analogMode = true; + bool pressureEnabled = false; + uint16_t buttonMask = 0xFFFFu; + uint32_t dmaAddr = 0u; + uint32_t reqState = 0u; + }; + + std::mutex g_padOverrideMutex; + std::mutex g_padStateMutex; + bool g_padOverrideEnabled = false; + PadInputState g_padOverrideState{}; + PadPortState g_padPorts[kPadPortCount]{}; + int g_padReadLogCount = 0; + + uint8_t axisToByte(float axis) + { + axis = std::clamp(axis, -1.0f, 1.0f); + const float mapped = (axis + 1.0f) * 127.5f; + return static_cast(std::lround(mapped)); + } + + void setButton(PadInputState &state, uint16_t mask, bool pressed) + { + if (pressed) + { + state.buttons = static_cast(state.buttons & ~mask); + } + } + + int findFirstGamepad() + { + for (int i = 0; i < 4; ++i) + { + if (IsGamepadAvailable(i)) + { + return i; + } + } + return -1; + } + + void applyGamepadState(PadInputState &state) + { + if (!IsWindowReady()) + { + return; + } + + const int gamepad = findFirstGamepad(); + if (gamepad < 0) + { + return; + } + + // Raylib mapping (PS2 -> raylib buttons/axes): + // D-Pad -> LEFT_FACE_*, Cross/Circle/Square/Triangle -> RIGHT_FACE_* + // L1/R1 -> TRIGGER_1, L2/R2 -> TRIGGER_2, L3/R3 -> THUMB + // Select/Start -> MIDDLE_LEFT/MIDDLE_RIGHT + state.lx = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_X)); + state.ly = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_Y)); + state.rx = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_X)); + state.ry = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_Y)); + + setButton(state, kPadBtnUp, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)); + setButton(state, kPadBtnDown, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)); + setButton(state, kPadBtnLeft, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)); + setButton(state, kPadBtnRight, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)); + + setButton(state, kPadBtnCross, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)); + setButton(state, kPadBtnCircle, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)); + setButton(state, kPadBtnSquare, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)); + setButton(state, kPadBtnTriangle, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)); + + setButton(state, kPadBtnL1, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)); + setButton(state, kPadBtnR1, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)); + setButton(state, kPadBtnL2, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_2)); + setButton(state, kPadBtnR2, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_2)); + + setButton(state, kPadBtnL3, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)); + setButton(state, kPadBtnR3, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)); + + setButton(state, kPadBtnSelect, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)); + setButton(state, kPadBtnStart, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)); + } + + void applyKeyboardState(PadInputState &state, bool allowAnalog) + { + if (!IsWindowReady()) + { + return; + } + + // Keyboard mapping (PS2 -> keys): + // D-Pad: arrows, Square/Cross/Circle/Triangle: Z/X/C/V + // L1/R1: Q/E, L2/R2: 1/3, Start/Select: Enter/RightShift + // L3/R3: LeftCtrl/RightCtrl, Analog left: WASD + setButton(state, kPadBtnUp, IsKeyDown(KEY_UP)); + setButton(state, kPadBtnDown, IsKeyDown(KEY_DOWN)); + setButton(state, kPadBtnLeft, IsKeyDown(KEY_LEFT)); + setButton(state, kPadBtnRight, IsKeyDown(KEY_RIGHT)); + + setButton(state, kPadBtnSquare, IsKeyDown(KEY_Z)); + setButton(state, kPadBtnCross, IsKeyDown(KEY_X)); + setButton(state, kPadBtnCircle, IsKeyDown(KEY_C)); + setButton(state, kPadBtnTriangle, IsKeyDown(KEY_V)); + + setButton(state, kPadBtnL1, IsKeyDown(KEY_Q)); + setButton(state, kPadBtnR1, IsKeyDown(KEY_E)); + setButton(state, kPadBtnL2, IsKeyDown(KEY_ONE)); + setButton(state, kPadBtnR2, IsKeyDown(KEY_THREE)); + + setButton(state, kPadBtnStart, IsKeyDown(KEY_ENTER)); + setButton(state, kPadBtnSelect, IsKeyDown(KEY_RIGHT_SHIFT)); + setButton(state, kPadBtnL3, IsKeyDown(KEY_LEFT_CONTROL)); + setButton(state, kPadBtnR3, IsKeyDown(KEY_RIGHT_CONTROL)); + + if (!allowAnalog) + { + return; + } + + float ax = 0.0f; + float ay = 0.0f; + if (IsKeyDown(KEY_D)) + ax += 1.0f; + if (IsKeyDown(KEY_A)) + ax -= 1.0f; + if (IsKeyDown(KEY_S)) + ay += 1.0f; + if (IsKeyDown(KEY_W)) + ay -= 1.0f; + + if (ax != 0.0f || ay != 0.0f) + { + state.lx = axisToByte(ax); + state.ly = axisToByte(ay); + } + } + + void resetPadStateLocked() + { + for (PadPortState &portState : g_padPorts) + { + portState = PadPortState{}; + } + } + + PadPortState *lookupPadPortStateLocked(int port, int slot) + { + if (port < 0 || port >= static_cast(kPadPortCount)) + { + return nullptr; + } + if (slot < 0 || slot >= static_cast(kPadSlotCount)) + { + return nullptr; + } + return &g_padPorts[port]; + } + + void initializePadPortLocked(PadPortState &portState, uint32_t dmaAddr) + { + portState.open = true; + portState.analogMode = true; + portState.pressureEnabled = false; + portState.buttonMask = 0xFFFFu; + portState.dmaAddr = dmaAddr; + portState.reqState = 0u; + } + + uint8_t pressureValue(const PadInputState &state, const PadPortState &portState, uint16_t mask) + { + if (!portState.pressureEnabled) + { + return 0u; + } + if ((portState.buttonMask & mask) == 0u) + { + return 0u; + } + return ((state.buttons & mask) == 0u) ? 0xFFu : 0u; + } + + void fillPadStatus(uint8_t *data, const PadInputState &state, const PadPortState &portState) + { + std::memset(data, 0, 32); + data[1] = portState.analogMode ? kPadModeDualShock : kPadModeDigital; + data[2] = static_cast(state.buttons & 0xFFu); + data[3] = static_cast((state.buttons >> 8) & 0xFFu); + data[4] = state.rx; + data[5] = state.ry; + data[6] = state.lx; + data[7] = state.ly; + data[8] = pressureValue(state, portState, kPadBtnRight); + data[9] = pressureValue(state, portState, kPadBtnLeft); + data[10] = pressureValue(state, portState, kPadBtnUp); + data[11] = pressureValue(state, portState, kPadBtnDown); + data[12] = pressureValue(state, portState, kPadBtnTriangle); + data[13] = pressureValue(state, portState, kPadBtnCircle); + data[14] = pressureValue(state, portState, kPadBtnCross); + data[15] = pressureValue(state, portState, kPadBtnSquare); + data[16] = pressureValue(state, portState, kPadBtnL1); + data[17] = pressureValue(state, portState, kPadBtnL2); + data[18] = pressureValue(state, portState, kPadBtnR1); + data[19] = pressureValue(state, portState, kPadBtnR2); + } + + bool readPadPortData(int port, int slot, PS2Runtime *runtime, uint8_t *outData) + { + if (!outData) + { + return false; + } + + PadPortState portState; + { + std::lock_guard lock(g_padStateMutex); + const PadPortState *sharedPortState = lookupPadPortStateLocked(port, slot); + if (!sharedPortState || !sharedPortState->open) + { + return false; + } + portState = *sharedPortState; + } + + PadInputState state; + bool useOverride = false; + { + std::lock_guard lock(g_padOverrideMutex); + if (g_padOverrideEnabled) + { + state = g_padOverrideState; + useOverride = true; + } + } + + if (!useOverride) + { + uint8_t backendData[32]{}; + if (runtime && runtime->padBackend().readState(port, slot, backendData, sizeof(backendData))) + { + state.buttons = static_cast(backendData[2] | (backendData[3] << 8)); + state.rx = backendData[4]; + state.ry = backendData[5]; + state.lx = backendData[6]; + state.ly = backendData[7]; + } + else + { + applyGamepadState(state); + applyKeyboardState(state, portState.analogMode); + } + } + + fillPadStatus(outData, state, portState); + + return true; + } + } + + void PadSyncCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + { + std::lock_guard lock(g_padStateMutex); + resetPadStateLocked(); + } + setReturnS32(ctx, 1); + } + + void scePadEnterPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState || !portState->open) + { + setReturnS32(ctx, 0); + return; + } + + portState->pressureEnabled = true; + portState->reqState = 0u; + setReturnS32(ctx, 1); + } + + void scePadExitPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState || !portState->open) + { + setReturnS32(ctx, 0); + return; + } + + portState->pressureEnabled = false; + portState->reqState = 0u; + setReturnS32(ctx, 1); + } + + void scePadGetButtonMask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + const uint16_t mask = portState ? portState->buttonMask : 0xFFFFu; + setReturnS32(ctx, static_cast(mask)); + } + + void scePadGetDmaStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + const uint32_t dmaAddr = portState ? portState->dmaAddr : getRegU32(ctx, 6); + setReturnU32(ctx, dmaAddr); + } + + void scePadGetFrameCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + static std::atomic frameCount{0}; + setReturnU32(ctx, frameCount++); + } + + void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + // Arbitrary non-zero module version. + setReturnS32(ctx, 0x0200); + } + + void scePadGetPortMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, 2); + } + + void scePadGetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + setReturnS32(ctx, static_cast(portState ? portState->reqState : 0u)); + } + + void scePadGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + setReturnS32(ctx, (portState && portState->open) ? kPadStateStable : kPadStateDisconnected); + } + + void scePadInfoAct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int32_t act = static_cast(getRegU32(ctx, 6)); + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState || !portState->open) + { + setReturnS32(ctx, 0); + return; + } + + if (act < 0) + { + setReturnS32(ctx, 2); // small + large motors + return; + } + setReturnS32(ctx, (act < 2) ? 1 : 0); + } + + void scePadInfoComb(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + // No combined modes reported. + setReturnS32(ctx, 0); + } + + void scePadInfoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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 + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState || !portState->open) + { + setReturnS32(ctx, 0); + return; + } + + const int32_t currentId = portState->analogMode ? kPadTypeDualShock : kPadTypeDigital; + switch (infoMode) + { + case 1: // PAD_MODECURID + setReturnS32(ctx, currentId); + return; + case 2: // PAD_MODECUREXID + setReturnS32(ctx, currentId); + 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, currentId); + } + else + { + setReturnS32(ctx, 0); + } + return; + default: + setReturnS32(ctx, 0); + return; + } + } + + void scePadInfoPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + const PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + setReturnS32(ctx, (portState && portState->open) ? 1 : 0); + } + + void scePadInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + { + std::lock_guard lock(g_padStateMutex); + resetPadStateLocked(); + } + setReturnS32(ctx, 1); + } + + void scePadInit2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + scePadInit(rdram, ctx, runtime); + } + + void scePadPortClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState) + { + setReturnS32(ctx, 0); + return; + } + + portState->open = false; + portState->pressureEnabled = false; + portState->reqState = 0u; + setReturnS32(ctx, 1); + } + + void scePadPortOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + const uint32_t dmaAddr = getRegU32(ctx, 6); + uint8_t *dmaStr = getMemPtr(rdram, dmaAddr); + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState || (dmaAddr != 0u && !dmaStr)) + { + setReturnS32(ctx, 0); + return; + } + + portState->open = true; + portState->analogMode = true; + portState->pressureEnabled = false; + portState->buttonMask = 0xFFFFu; + portState->dmaAddr = dmaAddr; + portState->reqState = 0u; + if (dmaStr) + { + std::memset(dmaStr, 0, 32); + } + setReturnS32(ctx, 1); + } + + void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int port = static_cast(getRegU32(ctx, 4)); + const int slot = static_cast(getRegU32(ctx, 5)); + const uint32_t dataAddr = getRegU32(ctx, 6); + uint8_t *data = getMemPtr(rdram, dataAddr); + if (!data) + { + setReturnS32(ctx, 0); + return; + } + + if (!readPadPortData(port, slot, runtime, data)) + { + setReturnS32(ctx, 0); + return; + } + + if (g_padReadLogCount < 48) + { + const int gamepad = findFirstGamepad(); + const bool gamepadStartPressed = + (gamepad >= 0) && IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT); + const bool startPressed = (data[2] != 0xFFu || data[3] != 0xFFu || + IsKeyDown(KEY_ENTER) || gamepadStartPressed); + if (startPressed) + { + const uint32_t guestButtons = + (static_cast(static_cast(data[2] ^ 0xFFu)) << 8) | + static_cast(static_cast(data[3] ^ 0xFFu)); + std::printf("[padread] port=%d slot=%d data2=0x%02x data3=0x%02x guestButtons=0x%04x enter=%d gamepadStart=%d\n", + port, slot, data[2], data[3], guestButtons, + IsKeyDown(KEY_ENTER) ? 1 : 0, gamepadStartPressed ? 1 : 0); + ++g_padReadLogCount; + } + } + + setReturnS32(ctx, 1); + } + + void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + const uint32_t state = getRegU32(ctx, 4); + const uint32_t strAddr = getRegU32(ctx, 5); + char *buf = reinterpret_cast(getMemPtr(rdram, strAddr)); + if (!buf) + { + setReturnS32(ctx, -1); + return; + } + + const char *text = (state == 0) ? "COMPLETE" : "BUSY"; + std::strncpy(buf, text, 31); + buf[31] = '\0'; + setReturnS32(ctx, 0); + } + + void scePadSetActAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); + } + + void scePadSetActDirect(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); + } + + void scePadSetButtonInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (portState && portState->open) + { + portState->buttonMask = static_cast(getRegU32(ctx, 6)); + portState->reqState = 0u; + } + setReturnS32(ctx, 1); + } + + void scePadSetMainMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (!portState || !portState->open) + { + setReturnS32(ctx, 0); + return; + } + + portState->analogMode = (getRegU32(ctx, 6) != 0u); + portState->reqState = 0u; + setReturnS32(ctx, 1); + } + + void scePadSetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + std::lock_guard lock(g_padStateMutex); + PadPortState *portState = lookupPadPortStateLocked(static_cast(getRegU32(ctx, 4)), + static_cast(getRegU32(ctx, 5))); + if (portState && portState->open) + { + portState->reqState = static_cast(getRegU32(ctx, 6) ? 1u : 0u); + } + setReturnS32(ctx, 1); + } + + void scePadSetVrefParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, 1); + } + + void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, 0); + } + + void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + const uint32_t state = getRegU32(ctx, 4); + const uint32_t strAddr = getRegU32(ctx, 5); + char *buf = reinterpret_cast(getMemPtr(rdram, strAddr)); + if (!buf) + { + setReturnS32(ctx, -1); + return; + } + + const char *text = "UNKNOWN"; + if (state == 6) + { + text = "STABLE"; + } + else if (state == 1) + { + text = "FINDPAD"; + } + else if (state == 0) + { + text = "DISCONNECTED"; + } + + std::strncpy(buf, text, 31); + buf[31] = '\0'; + setReturnS32(ctx, 0); + } + + void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry) + { + std::lock_guard lock(g_padOverrideMutex); + g_padOverrideEnabled = true; + g_padOverrideState.buttons = buttons; + g_padOverrideState.lx = lx; + g_padOverrideState.ly = ly; + g_padOverrideState.rx = rx; + g_padOverrideState.ry = ry; + } + + void clearPadOverrideState() + { + std::lock_guard lock(g_padOverrideMutex); + g_padOverrideEnabled = false; + g_padOverrideState = PadInputState{}; + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Pad.h b/ps2xRuntime/src/lib/Kernel/Stubs/Pad.h new file mode 100644 index 00000000..5c284d12 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Pad.h @@ -0,0 +1,39 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void PadSyncCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadEnterPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadExitPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetButtonMask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetDmaStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetFrameCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetPortMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadGetState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadInfoAct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadInfoComb(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadInfoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadInfoPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadInit2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadPortClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadPortOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetActAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetActDirect(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetButtonInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetMainMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetVrefParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry); + void clearPadOverrideState(); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/RPC.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/RPC.cpp new file mode 100644 index 00000000..8c3a02d6 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/RPC.cpp @@ -0,0 +1,20 @@ +#include "Common.h" +#include "RPC.h" + +namespace ps2_stubs +{ + void sceRpcFreePacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceRpcFreePacket", rdram, ctx, runtime); + } + + void sceRpcGetFPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceRpcGetFPacket", rdram, ctx, runtime); + } + + void sceRpcGetFPacket2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceRpcGetFPacket2", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/RPC.h b/ps2xRuntime/src/lib/Kernel/Stubs/RPC.h new file mode 100644 index 00000000..abc4e23d --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/RPC.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceRpcFreePacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceRpcGetFPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceRpcGetFPacket2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp new file mode 100644 index 00000000..780591db --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp @@ -0,0 +1,789 @@ +#include "Common.h" +#include "SIF.h" +#include "../Syscalls/RPC.h" + +#include + +namespace ps2_stubs +{ + void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSifCmdIntrHdlr", rdram, ctx, runtime); + } + + void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifLoadModule(rdram, ctx, runtime); + } + + void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t srcAddr = getRegU32(ctx, 7); // $a3 + const uint32_t dstAddr = readStackU32(rdram, ctx, 16); + const uint32_t size = readStackU32(rdram, ctx, 20); + if (size != 0u && srcAddr != 0u && dstAddr != 0u) + { + for (uint32_t i = 0; i < size; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + uint8_t *dst = getMemPtr(rdram, dstAddr + i); + if (!src || !dst) + { + break; + } + *dst = *src; + } + } + + setReturnS32(ctx, 1); + } + + namespace + { + struct Ps2SifDmaTransfer + { + uint32_t src = 0; + uint32_t dest = 0; + int32_t size = 0; + int32_t attr = 0; + }; + static_assert(sizeof(Ps2SifDmaTransfer) == 16u, "Unexpected SIF DMA descriptor size"); + + std::mutex g_sifDmaTransferMutex; + uint32_t g_nextSifDmaTransferId = 1u; + std::mutex g_sifCmdStateMutex; + std::mutex g_sifHeapMutex; + std::unordered_map g_sifRegs; + std::unordered_map g_sifSregs; + std::unordered_map g_sifCmdHandlers; + std::map g_sifHeapAllocations; + uint32_t g_sifCmdBuffer = 0u; + uint32_t g_sifSysCmdBuffer = 0u; + bool g_sifCmdInitialized = false; + uint32_t g_sifGetRegLogCount = 0u; + uint32_t g_sifSetRegLogCount = 0u; + + constexpr uint32_t kSifRegBootStatus = 0x4u; + constexpr uint32_t kSifRegMainAddr = 0x80000000u; + constexpr uint32_t kSifRegSubAddr = 0x80000001u; + constexpr uint32_t kSifRegMsCom = 0x80000002u; + constexpr uint32_t kSifBootReadyMask = 0x00020000u; + + void seedDefaultSifRegsLocked() + { + g_sifRegs.clear(); + g_sifSregs.clear(); + g_sifCmdHandlers.clear(); + g_sifCmdBuffer = 0u; + g_sifSysCmdBuffer = 0u; + g_sifCmdInitialized = false; + g_sifGetRegLogCount = 0u; + g_sifSetRegLogCount = 0u; + + g_sifRegs[kSifRegBootStatus] = kSifBootReadyMask; + g_sifRegs[kSifRegMainAddr] = 0u; + g_sifRegs[kSifRegSubAddr] = 0u; + g_sifRegs[kSifRegMsCom] = 0u; + } + + bool shouldTraceSifReg(uint32_t reg) + { + switch (reg) + { + case 0x2u: + case 0x4u: + case 0x80000000u: + case 0x80000001u: + case 0x80000002u: + return true; + default: + return false; + } + } + + struct SifStateInitializer + { + SifStateInitializer() + { + std::lock_guard lock(g_sifCmdStateMutex); + seedDefaultSifRegsLocked(); + } + } g_sifStateInitializer; + + uint32_t allocateSifDmaTransferId() + { + std::lock_guard lock(g_sifDmaTransferMutex); + uint32_t id = g_nextSifDmaTransferId++; + if (id == 0u) + { + id = g_nextSifDmaTransferId++; + } + return id; + } + + uint32_t alignIopHeapSize(uint32_t size) + { + return (size + (kIopHeapAlign - 1u)) & ~(kIopHeapAlign - 1u); + } + + uint32_t allocateSifHeapBlock(uint32_t requestSize) + { + const uint32_t alignedSize = alignIopHeapSize(requestSize); + if (alignedSize == 0u) + { + return 0u; + } + + std::lock_guard lock(g_sifHeapMutex); + uint32_t candidate = kIopHeapBase; + for (const auto &[addr, size] : g_sifHeapAllocations) + { + if (candidate + alignedSize <= addr) + { + break; + } + + const uint32_t blockEnd = alignIopHeapSize(addr + size); + if (blockEnd > candidate) + { + candidate = blockEnd; + } + } + + if (candidate < kIopHeapBase || candidate + alignedSize > kIopHeapLimit) + { + return 0u; + } + + g_sifHeapAllocations[candidate] = alignedSize; + g_iopHeapNext = candidate + alignedSize; + return candidate; + } + + bool freeSifHeapBlock(uint32_t addr) + { + std::lock_guard lock(g_sifHeapMutex); + const auto it = g_sifHeapAllocations.find(addr); + if (it == g_sifHeapAllocations.end()) + { + return false; + } + + g_sifHeapAllocations.erase(it); + if (g_sifHeapAllocations.empty()) + { + g_iopHeapNext = kIopHeapBase; + } + return true; + } + + void resetSifHeapState() + { + std::lock_guard lock(g_sifHeapMutex); + g_sifHeapAllocations.clear(); + g_iopHeapNext = kIopHeapBase; + } + + bool isCopyableGuestAddress(uint32_t addr) + { + if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) + { + return true; + } + + if (addr < 0x20000000u) + { + return true; + } + + if (addr >= 0x20000000u && addr < 0x40000000u) + { + return true; + } + + if (addr >= 0x80000000u && addr < 0xC0000000u) + { + return true; + } + + return false; + } + + bool canCopyGuestByteRange(const uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes) + { + if (!rdram) + { + return false; + } + + if (sizeBytes == 0u) + { + return true; + } + + for (uint32_t i = 0u; i < sizeBytes; ++i) + { + const uint32_t srcByteAddr = srcAddr + i; + const uint32_t dstByteAddr = dstAddr + i; + + if (!isCopyableGuestAddress(srcByteAddr) || !isCopyableGuestAddress(dstByteAddr)) + { + return false; + } + + const uint8_t *src = getConstMemPtr(rdram, srcByteAddr); + const uint8_t *dst = getConstMemPtr(rdram, dstByteAddr); + if (!src || !dst) + { + return false; + } + } + + return true; + } + + bool copyGuestByteRange(uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes) + { + if (!canCopyGuestByteRange(rdram, dstAddr, srcAddr, sizeBytes)) + { + return false; + } + + if (sizeBytes == 0u) + { + return true; + } + + const uint64_t srcBegin = srcAddr; + const uint64_t srcEnd = srcBegin + static_cast(sizeBytes); + const uint64_t dstBegin = dstAddr; + const bool copyBackward = (dstBegin > srcBegin) && (dstBegin < srcEnd); + + if (copyBackward) + { + for (uint32_t i = sizeBytes; i > 0u; --i) + { + const uint32_t index = i - 1u; + const uint8_t *src = getConstMemPtr(rdram, srcAddr + index); + uint8_t *dst = getMemPtr(rdram, dstAddr + index); + if (!src || !dst) + { + return false; + } + *dst = *src; + } + return true; + } + + for (uint32_t i = 0; i < sizeBytes; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + uint8_t *dst = getMemPtr(rdram, dstAddr + i); + if (!src || !dst) + { + return false; + } + *dst = *src; + } + return true; + } + } + + void resetSifState() + { + std::lock_guard lock(g_sifCmdStateMutex); + seedDefaultSifRegsLocked(); + resetSifHeapState(); + } + + void sceSifAddCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cid = getRegU32(ctx, 4); + const uint32_t handler = getRegU32(ctx, 5); + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdHandlers[cid] = handler; + setReturnS32(ctx, 0); + } + + void sceSifAllocIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t reqSize = getRegU32(ctx, 4); + setReturnU32(ctx, allocateSifHeapBlock(reqSize)); + } + + void sceSifAllocSysMemory(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t size = getRegU32(ctx, 5); + setReturnU32(ctx, allocateSifHeapBlock(size)); + } + + void sceSifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifBindRpc(rdram, ctx, runtime); + } + + void sceSifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifCheckStatRpc(rdram, ctx, runtime); + } + + void sceSifDmaStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + (void)getRegU32(ctx, 4); // trid + + // Transfers are applied immediately by sceSifSetDma in this runtime. + setReturnS32(ctx, -1); + } + + void sceSifExecRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifExitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + std::lock_guard lock(g_sifCmdStateMutex); + seedDefaultSifRegsLocked(); + setReturnS32(ctx, 0); + } + + void sceSifExitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifFreeIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t addr = getRegU32(ctx, 4); + setReturnS32(ctx, freeSifHeapBlock(addr) ? 0 : -1); + } + + void sceSifFreeSysMemory(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + + const uint32_t addr = getRegU32(ctx, 4); + setReturnS32(ctx, freeSifHeapBlock(addr) ? 0 : -1); + } + + void sceSifGetDataTable(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + std::lock_guard lock(g_sifCmdStateMutex); + setReturnU32(ctx, g_sifCmdBuffer); + } + + void sceSifGetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, getRegU32(ctx, 4)); + } + + void sceSifGetNextRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifGetOtherData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + + const uint32_t rdAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + const uint32_t dstAddr = getRegU32(ctx, 6); + const int32_t sizeSigned = static_cast(getRegU32(ctx, 7)); + + if (sizeSigned <= 0) + { + setReturnS32(ctx, 0); + return; + } + + const uint32_t size = static_cast(sizeSigned); + if (size > PS2_RAM_SIZE) + { + static uint32_t warnCount = 0; + if (warnCount < 32u) + { + std::cerr << "sceSifGetOtherData rejected oversized transfer size=0x" + << std::hex << size << std::dec << std::endl; + ++warnCount; + } + setReturnS32(ctx, -1); + return; + } + + ps2_syscalls::prepareSoundDriverStatusTransfer(rdram, srcAddr, size); + + if (!copyGuestByteRange(rdram, dstAddr, srcAddr, size)) + { + static uint32_t warnCount = 0; + if (warnCount < 32u) + { + std::cerr << "sceSifGetOtherData copy failed src=0x" << std::hex << srcAddr + << " dst=0x" << dstAddr + << " size=0x" << size + << std::dec << std::endl; + ++warnCount; + } + setReturnS32(ctx, -1); + return; + } + + // SifRpcReceiveData_t keeps src/dest/size at offsets 0x10/0x14/0x18. + if (uint8_t *rd = getMemPtr(rdram, rdAddr)) + { + std::memcpy(rd + 0x10u, &srcAddr, sizeof(srcAddr)); + std::memcpy(rd + 0x14u, &dstAddr, sizeof(dstAddr)); + std::memcpy(rd + 0x18u, &size, sizeof(size)); + } + + ps2_syscalls::finalizeSoundDriverStatusTransfer(rdram, srcAddr, dstAddr, size); + + setReturnS32(ctx, 0); + } + + void sceSifGetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t reg = getRegU32(ctx, 4); + uint32_t value = 0u; + bool shouldLog = false; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifRegs.find(reg); + if (it != g_sifRegs.end()) + { + value = it->second; + } + shouldLog = shouldTraceSifReg(reg) && g_sifGetRegLogCount < 128u; + if (shouldLog) + { + ++g_sifGetRegLogCount; + } + } + if (shouldLog) + { + auto flags = std::cerr.flags(); + std::cerr << "[sceSifGetReg] reg=0x" << std::hex << reg + << " value=0x" << value + << " pc=0x" << (ctx ? ctx->pc : 0u) + << " ra=0x" << (ctx ? getRegU32(ctx, 31) : 0u) + << std::dec << std::endl; + std::cerr.flags(flags); + } + setReturnU32(ctx, value); + } + + void sceSifGetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t reg = getRegU32(ctx, 4); + uint32_t value = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifSregs.find(reg); + if (it != g_sifSregs.end()) + { + value = it->second; + } + } + setReturnU32(ctx, value); + } + + void sceSifInitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdInitialized = true; + setReturnS32(ctx, 0); + } + + void sceSifInitIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + resetSifHeapState(); + setReturnS32(ctx, 0); + } + + void sceSifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifInitRpc(rdram, ctx, runtime); + } + + void sceSifIsAliveIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::sceSifLoadElf(rdram, ctx, runtime); + } + + void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::sceSifLoadElfPart(rdram, ctx, runtime); + } + + void sceSifLoadFileReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifLoadIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::sceSifLoadModuleBuffer(rdram, ctx, runtime); + } + + void sceSifRebootIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceSifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifRegisterRpc(rdram, ctx, runtime); + } + + void sceSifRemoveCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cid = getRegU32(ctx, 4); + std::lock_guard lock(g_sifCmdStateMutex); + g_sifCmdHandlers.erase(cid); + setReturnS32(ctx, 0); + } + + void sceSifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifRemoveRpc(rdram, ctx, runtime); + } + + void sceSifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifRemoveRpcQueue(rdram, ctx, runtime); + } + + void sceSifResetIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceSifRpcLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifSetCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t newBuffer = getRegU32(ctx, 4); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + prev = g_sifCmdBuffer; + g_sifCmdBuffer = newBuffer; + } + setReturnU32(ctx, prev); + } + + void isceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + sceSifSetDChain(rdram, ctx, runtime); + } + + void isceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + sceSifSetDma(rdram, ctx, runtime); + } + + void sceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, 0); + } + + void sceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + + const uint32_t dmatAddr = getRegU32(ctx, 4); + const uint32_t count = getRegU32(ctx, 5); + if (!dmatAddr || count == 0u || count > 32u) + { + setReturnS32(ctx, 0); + return; + } + + std::array pending{}; + uint32_t pendingCount = 0u; + bool ok = true; + for (uint32_t i = 0; i < count; ++i) + { + const uint32_t entryAddr = dmatAddr + (i * static_cast(sizeof(Ps2SifDmaTransfer))); + const uint8_t *entry = getConstMemPtr(rdram, entryAddr); + if (!entry) + { + ok = false; + break; + } + + Ps2SifDmaTransfer xfer{}; + std::memcpy(&xfer, entry, sizeof(xfer)); + if (xfer.size <= 0) + { + continue; + } + + const uint32_t sizeBytes = static_cast(xfer.size); + if (sizeBytes > PS2_RAM_SIZE) + { + ok = false; + break; + } + if (!canCopyGuestByteRange(rdram, xfer.dest, xfer.src, sizeBytes)) + { + ok = false; + break; + } + + pending[pendingCount++] = xfer; + } + + if (ok) + { + for (uint32_t i = 0; i < pendingCount; ++i) + { + const Ps2SifDmaTransfer &xfer = pending[i]; + if (!copyGuestByteRange(rdram, xfer.dest, xfer.src, static_cast(xfer.size))) + { + ok = false; + break; + } + + ps2_syscalls::noteDtxSifDmaTransfer( + rdram, + xfer.src, + xfer.dest, + static_cast(xfer.size)); + } + } + + if (!ok) + { + static uint32_t warnCount = 0; + if (warnCount < 32u) + { + std::cerr << "sceSifSetDma failed dmat=0x" << std::hex << dmatAddr + << " count=0x" << count + << std::dec << std::endl; + ++warnCount; + } + setReturnS32(ctx, 0); + return; + } + + ps2_syscalls::dispatchDmacHandlersForCause(rdram, runtime, 5u); + + setReturnS32(ctx, static_cast(allocateSifDmaTransferId())); + } + + void sceSifSetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnU32(ctx, getRegU32(ctx, 5)); + } + + void sceSifSetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t reg = getRegU32(ctx, 4); + const uint32_t value = getRegU32(ctx, 5); + uint32_t prev = 0u; + bool shouldLog = false; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifRegs.find(reg); + if (it != g_sifRegs.end()) + { + prev = it->second; + } + g_sifRegs[reg] = value; + shouldLog = shouldTraceSifReg(reg) && g_sifSetRegLogCount < 128u; + if (shouldLog) + { + ++g_sifSetRegLogCount; + } + } + if (shouldLog) + { + auto flags = std::cerr.flags(); + std::cerr << "[sceSifSetReg] reg=0x" << std::hex << reg + << " prev=0x" << prev + << " value=0x" << value + << " pc=0x" << (ctx ? ctx->pc : 0u) + << " ra=0x" << (ctx ? getRegU32(ctx, 31) : 0u) + << std::dec << std::endl; + std::cerr.flags(flags); + } + setReturnU32(ctx, prev); + } + + void sceSifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ps2_syscalls::SifSetRpcQueue(rdram, ctx, runtime); + } + + void sceSifSetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t reg = getRegU32(ctx, 4); + const uint32_t value = getRegU32(ctx, 5); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + auto it = g_sifSregs.find(reg); + if (it != g_sifSregs.end()) + { + prev = it->second; + } + g_sifSregs[reg] = value; + } + setReturnU32(ctx, prev); + } + + void sceSifSetSysCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t newBuffer = getRegU32(ctx, 4); + uint32_t prev = 0u; + { + std::lock_guard lock(g_sifCmdStateMutex); + prev = g_sifSysCmdBuffer; + g_sifSysCmdBuffer = newBuffer; + } + setReturnU32(ctx, prev); + } + + void sceSifStopDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSifSyncIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceSifWriteBackDCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/SIF.h b/ps2xRuntime/src/lib/Kernel/Stubs/SIF.h new file mode 100644 index 00000000..7d7f35c3 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/SIF.h @@ -0,0 +1,57 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void resetSifState(); + void sceSifAddCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifAllocIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifAllocSysMemory(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifDmaStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifExecRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifExitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifExitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifFreeIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifFreeSysMemory(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifGetDataTable(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifGetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifGetNextRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifGetOtherData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifGetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifGetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifInitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifInitIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifIsAliveIop(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 sceSifLoadFileReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifRebootIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifRemoveCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifResetIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifRpcLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void isceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void isceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSetSysCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifStopDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSyncIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifWriteBackDCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/System.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/System.cpp new file mode 100644 index 00000000..e2df2b54 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/System.cpp @@ -0,0 +1,61 @@ +#include "Common.h" +#include "System.h" + +namespace ps2_stubs +{ + void builtin_set_imask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + if (logCount < 8) + { + RUNTIME_LOG("ps2_stub builtin_set_imask"); + ++logCount; + } + setReturnS32(ctx, 0); + } + + void sceIDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceSDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void exit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + if (runtime) + { + runtime->requestStop(); + } + setReturnS32(ctx, 0); + } + + void getpid(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 1); + } + + void sceSetBrokenLink(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSetBrokenLink", rdram, ctx, runtime); + } + + void sceSetPtm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceSetPtm", rdram, ctx, runtime); + } + + void sceDevVif0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceDevVu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/System.h b/ps2xRuntime/src/lib/Kernel/Stubs/System.h new file mode 100644 index 00000000..39f0ad4a --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/System.h @@ -0,0 +1,16 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void builtin_set_imask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceIDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void exit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void getpid(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSetBrokenLink(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSetPtm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDevVif0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceDevVu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/TTY.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/TTY.cpp new file mode 100644 index 00000000..6eb35d9a --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/TTY.cpp @@ -0,0 +1,59 @@ +#include "Common.h" +#include "TTY.h" +#include "ps2_log.h" + +namespace ps2_stubs +{ + void scePrintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t format_addr = getRegU32(ctx, 4); + const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); + if (format_addr == 0) + return; + std::string rendered = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 1); + if (rendered.size() > 2048) + rendered.resize(2048); + PS2_IF_AGRESSIVE_LOGS({ + const std::string logLine = sanitizeForLog(rendered); + uint32_t count = 0; + { + std::lock_guard lock(g_printfLogMutex); + count = ++g_printfLogCount; + } + if (count <= kMaxPrintfLogs) + { + RUNTIME_LOG("PS2 scePrintf: " << logLine); + RUNTIME_LOG(std::flush); + } + else if (count == kMaxPrintfLogs + 1) + { + std::cerr << "PS2 printf logging suppressed after " << kMaxPrintfLogs << " lines" << std::endl; + } + }); + } + + void sceResetttyinit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceResetttyinit", rdram, ctx, runtime); + } + + void sceTtyHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceTtyHandler", rdram, ctx, runtime); + } + + void sceTtyInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceTtyInit", rdram, ctx, runtime); + } + + void sceTtyRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceTtyRead", rdram, ctx, runtime); + } + + void sceTtyWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceTtyWrite", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/TTY.h b/ps2xRuntime/src/lib/Kernel/Stubs/TTY.h new file mode 100644 index 00000000..b63af005 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/TTY.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void scePrintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceResetttyinit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceTtyHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceTtyInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceTtyRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceTtyWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Unimplemented.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/Unimplemented.cpp new file mode 100644 index 00000000..561d619f --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Unimplemented.cpp @@ -0,0 +1,49 @@ +#include "Common.h" +#include "Unimplemented.h" + +namespace ps2_stubs +{ + void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("unknown", rdram, ctx, runtime); + } + + 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=" << stubName + << " PC=0x" << std::hex << ctx->pc + << ", RA=0x" << caller_ra + << ", Stub# guess (from $v0)=0x" << stub_num << std::dec << std::endl; + + // More context for debugging + 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; + + //TODO maybe a macro to disable the exception and just return an success just to see it where goes. + throw std::runtime_error("Unimplemented PS2 stub called: " + stubName); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/Unimplemented.h b/ps2xRuntime/src/lib/Kernel/Stubs/Unimplemented.h new file mode 100644 index 00000000..75c65c33 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/Unimplemented.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + 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/src/lib/Kernel/Stubs/VU.cpp b/ps2xRuntime/src/lib/Kernel/Stubs/VU.cpp new file mode 100644 index 00000000..02741622 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/VU.cpp @@ -0,0 +1,584 @@ +#include "Common.h" +#include "VU.h" +//TODO use glm + +namespace ps2_stubs +{ + void sceVu0ecossin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0ecossin", rdram, ctx, runtime); + } + + namespace + { + bool readVuVec4f(uint8_t *rdram, uint32_t addr, float (&out)[4]) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(out, ptr, sizeof(out)); + return true; + } + + bool writeVuVec4f(uint8_t *rdram, uint32_t addr, const float (&in)[4]) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, in, sizeof(in)); + return true; + } + + bool readVuVec4i(uint8_t *rdram, uint32_t addr, int32_t (&out)[4]) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(out, ptr, sizeof(out)); + return true; + } + + bool writeVuVec4i(uint8_t *rdram, uint32_t addr, const int32_t (&in)[4]) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, in, sizeof(in)); + return true; + } + + bool readVuMatrix4f(uint8_t *rdram, uint32_t addr, float (&out)[16]) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(out, ptr, sizeof(out)); + return true; + } + + bool writeVuMatrix4f(uint8_t *rdram, uint32_t addr, const float (&in)[16]) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, in, sizeof(in)); + return true; + } + + void mulVuMatrix(const float (&lhs)[16], const float (&rhs)[16], float (&out)[16]) + { + std::fill(std::begin(out), std::end(out), 0.0f); + for (int i = 0; i < 4; ++i) + { + for (int j = 0; j < 4; ++j) + { + for (int k = 0; k < 4; ++k) + { + out[4 * i + j] += rhs[4 * k + j] * lhs[4 * i + k]; + } + } + } + } + + void makeIdentityMatrix(float (&out)[16]) + { + std::fill(std::begin(out), std::end(out), 0.0f); + out[0] = 1.0f; + out[5] = 1.0f; + out[10] = 1.0f; + out[15] = 1.0f; + } + } + + void sceVpu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void sceVu0AddVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t lhsAddr = getRegU32(ctx, 5); + const uint32_t rhsAddr = getRegU32(ctx, 6); + float lhs[4]{}, rhs[4]{}, out[4]{}; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = lhs[i] + rhs[i]; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0ApplyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t matrixAddr = getRegU32(ctx, 5); + const uint32_t srcAddr = getRegU32(ctx, 6); + float matrix[16]{}; + float src[4]{}; + float out[4]{}; + if (readVuMatrix4f(rdram, matrixAddr, matrix) && readVuVec4f(rdram, srcAddr, src)) + { + // Match libvux VuxApplyMatrix math while honoring the imported EE ABI: + // a0=out, a1=matrix, a2=vector. + out[0] = (matrix[0] * src[0]) + (matrix[4] * src[1]) + (matrix[8] * src[2]) + (matrix[12] * src[3]); + out[1] = (matrix[1] * src[0]) + (matrix[5] * src[1]) + (matrix[9] * src[2]) + (matrix[13] * src[3]); + out[2] = (matrix[2] * src[0]) + (matrix[6] * src[1]) + (matrix[10] * src[2]) + (matrix[14] * src[3]); + out[3] = (matrix[3] * src[0]) + (matrix[7] * src[1]) + (matrix[11] * src[2]) + (matrix[15] * src[3]); + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0CameraMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0CameraMatrix", rdram, ctx, runtime); + } + + void sceVu0ClampVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0ClampVector", rdram, ctx, runtime); + } + + void sceVu0ClipAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0ClipAll", rdram, ctx, runtime); + } + + void sceVu0ClipScreen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0ClipScreen", rdram, ctx, runtime); + } + + void sceVu0ClipScreen3(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0ClipScreen3", rdram, ctx, runtime); + } + + void sceVu0CopyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + uint8_t *dst = getMemPtr(rdram, dstAddr); + const uint8_t *src = getConstMemPtr(rdram, srcAddr); + if (dst && src) + { + std::memcpy(dst, src, sizeof(float) * 16u); + } + setReturnS32(ctx, 0); + } + + void sceVu0CopyVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + uint8_t *dst = getMemPtr(rdram, dstAddr); + const uint8_t *src = getConstMemPtr(rdram, srcAddr); + if (dst && src) + { + std::memcpy(dst, src, sizeof(float) * 4u); + } + setReturnS32(ctx, 0); + } + + void sceVu0CopyVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + uint8_t *dst = getMemPtr(rdram, dstAddr); + const uint8_t *src = getConstMemPtr(rdram, srcAddr); + if (dst && src) + { + std::memcpy(dst, src, sizeof(float) * 3u); + } + setReturnS32(ctx, 0); + } + + void sceVu0DivVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0DivVector", rdram, ctx, runtime); + } + + void sceVu0DivVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0DivVectorXYZ", rdram, ctx, runtime); + } + + void sceVu0DropShadowMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0DropShadowMatrix", rdram, ctx, runtime); + } + + void sceVu0FTOI0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}; + int32_t out[4]{}; + if (readVuVec4f(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]); + } + (void)writeVuVec4i(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0FTOI4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}; + int32_t out[4]{}; + if (readVuVec4f(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i] * 16.0f); + } + (void)writeVuVec4i(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0InnerProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t lhsAddr = getRegU32(ctx, 4); + const uint32_t rhsAddr = getRegU32(ctx, 5); + float lhs[4]{}, rhs[4]{}; + float dot = 0.0f; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + dot = (lhs[0] * rhs[0]) + (lhs[1] * rhs[1]) + (lhs[2] * rhs[2]) + (lhs[3] * rhs[3]); + } + + if (ctx) + { + ctx->f[0] = dot; + } + uint32_t raw = 0u; + std::memcpy(&raw, &dot, sizeof(raw)); + setReturnU32(ctx, raw); + } + + void sceVu0InterVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0InterVector", rdram, ctx, runtime); + } + + void sceVu0InterVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0InterVectorXYZ", rdram, ctx, runtime); + } + + void sceVu0InversMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0InversMatrix", rdram, ctx, runtime); + } + + void sceVu0ITOF0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + int32_t src[4]{}; + float out[4]{}; + if (readVuVec4i(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]); + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0ITOF12Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + int32_t src[4]{}; + float out[4]{}; + if (readVuVec4i(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]) / 4096.0f; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0ITOF4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + int32_t src[4]{}; + float out[4]{}; + if (readVuVec4i(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = static_cast(src[i]) / 16.0f; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0LightColorMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0LightColorMatrix", rdram, ctx, runtime); + } + + void sceVu0MulMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0MulMatrix", rdram, ctx, runtime); + } + + void sceVu0MulVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0MulVector", rdram, ctx, runtime); + } + + void sceVu0Normalize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}, out[4]{}; + if (readVuVec4f(rdram, srcAddr, src)) + { + const float len = std::sqrt((src[0] * src[0]) + (src[1] * src[1]) + (src[2] * src[2]) + (src[3] * src[3])); + if (len > 1.0e-6f) + { + const float invLen = 1.0f / len; + for (int i = 0; i < 4; ++i) + { + out[i] = src[i] * invLen; + } + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0NormalLightMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0NormalLightMatrix", rdram, ctx, runtime); + } + + void sceVu0OuterProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t lhsAddr = getRegU32(ctx, 5); + const uint32_t rhsAddr = getRegU32(ctx, 6); + float lhs[4]{}, rhs[4]{}, out[4]{}; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + out[0] = (lhs[1] * rhs[2]) - (lhs[2] * rhs[1]); + out[1] = (lhs[2] * rhs[0]) - (lhs[0] * rhs[2]); + out[2] = (lhs[0] * rhs[1]) - (lhs[1] * rhs[0]); + out[3] = 0.0f; + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0RotMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0RotMatrix", rdram, ctx, runtime); + } + + void sceVu0RotMatrixX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + const float angle = ctx ? ctx->f[12] : 0.0f; + float src[16]{}, rot[16]{}, out[16]{}; + if (readVuMatrix4f(rdram, srcAddr, src)) + { + makeIdentityMatrix(rot); + const float cs = std::cos(angle); + const float sn = std::sin(angle); + rot[5] = cs; + rot[6] = sn; + rot[9] = -sn; + rot[10] = cs; + mulVuMatrix(src, rot, out); + (void)writeVuMatrix4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0RotMatrixY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + const float angle = ctx ? ctx->f[12] : 0.0f; + float src[16]{}, rot[16]{}, out[16]{}; + if (readVuMatrix4f(rdram, srcAddr, src)) + { + makeIdentityMatrix(rot); + const float cs = std::cos(angle); + const float sn = std::sin(angle); + rot[0] = cs; + rot[2] = -sn; + rot[8] = sn; + rot[10] = cs; + mulVuMatrix(src, rot, out); + (void)writeVuMatrix4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0RotMatrixZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + const float angle = ctx ? ctx->f[12] : 0.0f; + float src[16]{}, rot[16]{}, out[16]{}; + if (readVuMatrix4f(rdram, srcAddr, src)) + { + makeIdentityMatrix(rot); + const float cs = std::cos(angle); + const float sn = std::sin(angle); + rot[0] = cs; + rot[1] = sn; + rot[4] = -sn; + rot[5] = cs; + mulVuMatrix(src, rot, out); + (void)writeVuMatrix4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0RotTransPers(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0RotTransPers", rdram, ctx, runtime); + } + + void sceVu0RotTransPersN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0RotTransPersN", rdram, ctx, runtime); + } + + void sceVu0ScaleVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[4]{}, out[4]{}; + float scale = ctx ? ctx->f[12] : 0.0f; + if (scale == 0.0f) + { + uint32_t raw = getRegU32(ctx, 6); + std::memcpy(&scale, &raw, sizeof(scale)); + if (scale == 0.0f) + { + scale = static_cast(getRegU32(ctx, 6)); + } + } + + if (readVuVec4f(rdram, srcAddr, src)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = src[i] * scale; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0ScaleVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0ScaleVectorXYZ", rdram, ctx, runtime); + } + + void sceVu0SubVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t lhsAddr = getRegU32(ctx, 5); + const uint32_t rhsAddr = getRegU32(ctx, 6); + float lhs[4]{}, rhs[4]{}, out[4]{}; + if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) + { + for (int i = 0; i < 4; ++i) + { + out[i] = lhs[i] - rhs[i]; + } + (void)writeVuVec4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0TransMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + TODO_NAMED("sceVu0TransMatrix", rdram, ctx, runtime); + } + + void sceVu0TransposeMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t dstAddr = getRegU32(ctx, 4); + const uint32_t srcAddr = getRegU32(ctx, 5); + float src[16]{}; + float out[16]{}; + if (readVuMatrix4f(rdram, srcAddr, src)) + { + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < 4; ++col) + { + out[4 * row + col] = src[4 * col + row]; + } + } + (void)writeVuMatrix4f(rdram, dstAddr, out); + } + setReturnS32(ctx, 0); + } + + void sceVu0UnitMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) + { + TODO_NAMED("sceVu0ViewScreenMatrix", rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Stubs/VU.h b/ps2xRuntime/src/lib/Kernel/Stubs/VU.h new file mode 100644 index 00000000..42acefa6 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Stubs/VU.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ps2_stubs.h" + +namespace ps2_stubs +{ + void sceVu0ecossin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVpu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0AddVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ApplyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0CameraMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ClampVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ClipAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ClipScreen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ClipScreen3(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0CopyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0CopyVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0CopyVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0DivVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0DivVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0DropShadowMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0FTOI0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0FTOI4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0InnerProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0InterVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0InterVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0InversMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ITOF0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ITOF12Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ITOF4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0LightColorMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0MulMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0MulVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0Normalize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0NormalLightMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0OuterProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0RotMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0RotMatrixX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0RotMatrixY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0RotMatrixZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0RotTransPers(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0RotTransPersN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ScaleVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ScaleVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0SubVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0TransMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0TransposeMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0UnitMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceVu0ViewScreenMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Common.h b/ps2xRuntime/src/lib/Kernel/Syscalls/Common.h new file mode 100644 index 00000000..5a9f392b --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Common.h @@ -0,0 +1,36 @@ +#include "ps2_syscalls.h" +#include "ps2_runtime.h" +#include "runtime/ps2_iop_audio.h" +#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 +#include +#endif +#include + +std::string translatePs2Path(const char *ps2Path); + +#include "Helpers/Path.h" +#include "Helpers/State.h" +#include "Helpers/Loader.h" +#include "Helpers/Runtime.h" diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/Dispatcher.cpp similarity index 68% rename from ps2xRuntime/src/lib/ps2_syscalls.cpp rename to ps2xRuntime/src/lib/Kernel/Syscalls/Dispatcher.cpp index 43cdf8b8..c792e6f2 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Dispatcher.cpp @@ -1,46 +1,9 @@ -#include "ps2_syscalls.h" -#include "ps2_runtime.h" -#include "ps2_iop_audio.h" -#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 mkdir -#endif -#include - -std::string translatePs2Path(const char *ps2Path); - -#include "syscalls/helpers/ps2_syscalls_helpers_path.inl" -#include "syscalls/helpers/ps2_syscalls_helpers_state.inl" -#include "syscalls/helpers/ps2_syscalls_helpers_loader.inl" -#include "syscalls/helpers/ps2_syscalls_helpers_runtime.inl" +#include "Common.h" +#include "Dispatcher.h" +#include "System.h" namespace ps2_syscalls { -#include "syscalls/ps2_syscalls_interrupt.inl" -#include "syscalls/ps2_syscalls_system.inl" - void iDeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); - bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { if (dispatchSyscallOverride(syscallNumber, rdram, ctx, runtime)) @@ -281,6 +244,12 @@ namespace ps2_syscalls case static_cast(-0x5F): DisableDmacHandler(rdram, ctx, runtime); return true; + case 0x61: + EnableCache(rdram, ctx, runtime); + return true; + case 0x62: + DisableCache(rdram, ctx, runtime); + return true; case 0x64: FlushCache(rdram, ctx, runtime); return true; @@ -324,117 +293,4 @@ namespace ps2_syscalls return false; } } - -#include "syscalls/ps2_syscalls_thread.inl" -#include "syscalls/ps2_syscalls_flags.inl" -#include "syscalls/ps2_syscalls_rpc.inl" -#include "syscalls/ps2_syscalls_fileio.inl" - - void notifyRuntimeStop() - { - stopInterruptWorker(); - { - std::lock_guard lock(g_irq_handler_mutex); - g_intcHandlers.clear(); - g_dmacHandlers.clear(); - g_nextIntcHandlerId = 1; - g_nextDmacHandlerId = 1; - g_enabled_intc_mask = 0xFFFFFFFFu; - g_enabled_dmac_mask = 0xFFFFFFFFu; - } - { - std::lock_guard lock(g_vsync_flag_mutex); - g_vsync_registration = {}; - g_vsync_tick_counter = 0u; - } - - std::vector> threads; - threads.reserve(32); - { - std::lock_guard lock(g_thread_map_mutex); - for (const auto &entry : g_threads) - { - if (entry.second) - { - threads.push_back(entry.second); - } - } - g_threads.clear(); - g_nextThreadId = 2; // Reserve id 1 for main thread. - } - g_currentThreadId = 1; - - for (const auto &threadInfo : threads) - { - { - std::lock_guard lock(threadInfo->m); - threadInfo->forceRelease = true; - threadInfo->terminated = true; - } - threadInfo->cv.notify_all(); - } - - std::vector> semas; - { - std::lock_guard lock(g_sema_map_mutex); - semas.reserve(g_semas.size()); - for (const auto &entry : g_semas) - { - if (entry.second) - { - semas.push_back(entry.second); - } - } - g_semas.clear(); - g_nextSemaId = 1; - } - for (const auto &sema : semas) - { - sema->cv.notify_all(); - } - - std::vector> eventFlags; - { - std::lock_guard lock(g_event_flag_map_mutex); - eventFlags.reserve(g_eventFlags.size()); - for (const auto &entry : g_eventFlags) - { - if (entry.second) - { - eventFlags.push_back(entry.second); - } - } - g_eventFlags.clear(); - g_nextEventFlagId = 1; - } - for (const auto &eventFlag : eventFlags) - { - eventFlag->cv.notify_all(); - } - - { - std::lock_guard lock(g_alarm_mutex); - g_alarms.clear(); - } - g_alarm_cv.notify_all(); - - { - std::lock_guard lock(g_exit_handler_mutex); - g_exit_handlers.clear(); - } - { - std::lock_guard lock(g_syscall_override_mutex); - g_syscall_overrides.clear(); - } - } - - void joinAllGuestHostThreads() - { - joinAllHostThreads(); - } - - void detachAllGuestHostThreads() - { - detachAllHostThreads(); - } } diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Dispatcher.h b/ps2xRuntime/src/lib/Kernel/Syscalls/Dispatcher.h new file mode 100644 index 00000000..06f2a519 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Dispatcher.h @@ -0,0 +1,8 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/FileIO.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/FileIO.cpp new file mode 100644 index 00000000..172965fd --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/FileIO.cpp @@ -0,0 +1,512 @@ +#include "Common.h" +#include "FileIO.h" + +namespace ps2_syscalls +{ + static int allocatePs2Fd(FILE *file) + { + if (!file) + return -1; + + std::lock_guard lock(g_fd_mutex); + int fd = g_nextFd++; + g_fileDescriptors[fd] = file; + return fd; + } + + static FILE *getHostFile(int ps2Fd) + { + std::lock_guard lock(g_fd_mutex); + auto it = g_fileDescriptors.find(ps2Fd); + if (it != g_fileDescriptors.end()) + { + return it->second; + } + return nullptr; + } + + static void releasePs2Fd(int ps2Fd) + { + std::lock_guard lock(g_fd_mutex); + g_fileDescriptors.erase(ps2Fd); + } + + struct VagAccumEntry + { + std::vector data; + uint32_t firstBufAddr = 0; + }; + static std::unordered_map g_vagAccum; + static std::mutex g_vagAccumMutex; + static constexpr size_t kVagAccumMaxBytes = 16 * 1024 * 1024; + + static const char *translateFioMode(int ps2Flags) + { + bool read = (ps2Flags & PS2_FIO_O_RDONLY) || (ps2Flags & PS2_FIO_O_RDWR); + bool write = (ps2Flags & PS2_FIO_O_WRONLY) || (ps2Flags & PS2_FIO_O_RDWR); + bool append = (ps2Flags & PS2_FIO_O_APPEND); + bool create = (ps2Flags & PS2_FIO_O_CREAT); + bool truncate = (ps2Flags & PS2_FIO_O_TRUNC); + + if (read && write) + { + if (create && truncate) + return "w+b"; + if (create) + return "a+b"; + return "r+b"; + } + else if (write) + { + if (append) + return "ab"; + if (create && truncate) + return "wb"; + if (create) + return "wx"; + return "r+b"; + } + else if (read) + { + return "rb"; + } + return "rb"; + } + + void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + int flags = (int)getRegU32(ctx, 5); // $a1 (PS2 FIO flags) + + const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + if (!ps2Path) + { + std::cerr << "fioOpen error: Invalid path address" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::string hostPath = translatePs2Path(ps2Path); + if (hostPath.empty()) + { + std::cerr << "fioOpen error: Failed to translate path '" << ps2Path << "'" << std::endl; + setReturnS32(ctx, -1); + return; + } + + const char *mode = translateFioMode(flags); + RUNTIME_LOG("fioOpen: '" << hostPath << "' flags=0x" << std::hex << flags << std::dec << " mode='" << mode << "'"); + + FILE *fp = ::fopen(hostPath.c_str(), mode); + if (!fp) + { + std::cerr << "fioOpen error: fopen failed for '" << hostPath << "': " << strerror(errno) << std::endl; + setReturnS32(ctx, -1); // e.g., -ENOENT, -EACCES + return; + } + + int ps2Fd = allocatePs2Fd(fp); + if (ps2Fd < 0) + { + std::cerr << "fioOpen error: Failed to allocate PS2 file descriptor" << std::endl; + ::fclose(fp); + setReturnS32(ctx, -1); // e.g., -EMFILE + return; + } + + // returns the PS2 file descriptor + setReturnS32(ctx, ps2Fd); + } + + void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int ps2Fd = (int)getRegU32(ctx, 4); + + FILE *fp = getHostFile(ps2Fd); + if (!fp) + { + std::cerr << "fioClose warning: Invalid PS2 file descriptor " << ps2Fd << std::endl; + setReturnS32(ctx, -1); + return; + } + + int ret = ::fclose(fp); + releasePs2Fd(ps2Fd); + + { + std::lock_guard lock(g_vagAccumMutex); + auto it = g_vagAccum.find(ps2Fd); + if (it != g_vagAccum.end()) + { + VagAccumEntry &e = it->second; + if (e.data.size() >= 48) + { + const uint32_t magic = (static_cast(e.data[0]) << 24) | + (static_cast(e.data[1]) << 16) | + (static_cast(e.data[2]) << 8) | + static_cast(e.data[3]); + const uint32_t magicLE = (static_cast(e.data[3]) << 24) | + (static_cast(e.data[2]) << 16) | + (static_cast(e.data[1]) << 8) | + static_cast(e.data[0]); + if (magic == 0x56414770u || magicLE == 0x56414770u) + { + if (runtime) + runtime->audioBackend().onVagTransferFromBuffer( + e.data.data(), static_cast(e.data.size()), + e.firstBufAddr ? e.firstBufAddr : 0u); + } + } + g_vagAccum.erase(it); + } + } + + setReturnS32(ctx, ret == 0 ? 0 : -1); + } + + void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int ps2Fd = (int)getRegU32(ctx, 4); // $a0 + uint32_t bufAddr = getRegU32(ctx, 5); // $a1 + size_t size = getRegU32(ctx, 6); // $a2 + + uint8_t *hostBuf = getMemPtr(rdram, bufAddr); + FILE *fp = getHostFile(ps2Fd); + + if (!hostBuf) + { + std::cerr << "fioRead error: Invalid buffer address for fd " << ps2Fd << std::endl; + setReturnS32(ctx, -1); // -EFAULT + return; + } + if (!fp) + { + std::cerr << "fioRead error: Invalid file descriptor " << ps2Fd << std::endl; + setReturnS32(ctx, -1); // -EBADF + return; + } + if (size == 0) + { + setReturnS32(ctx, 0); // Read 0 bytes + return; + } + + size_t bytesRead = 0; + { + std::lock_guard lock(g_sys_fd_mutex); + bytesRead = fread(hostBuf, 1, size, fp); + } + + if (bytesRead < size && ferror(fp)) + { + std::cerr << "fioRead error: fread failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; + clearerr(fp); + setReturnS32(ctx, -1); + return; + } + + { + std::lock_guard lock(g_vagAccumMutex); + auto it = g_vagAccum.find(ps2Fd); + if (it != g_vagAccum.end()) + { + VagAccumEntry &e = it->second; + if (e.data.size() + bytesRead <= kVagAccumMaxBytes) + e.data.insert(e.data.end(), hostBuf, hostBuf + bytesRead); + } + else if (bytesRead >= 4) + { + const uint32_t magic = (static_cast(hostBuf[0]) << 24) | + (static_cast(hostBuf[1]) << 16) | + (static_cast(hostBuf[2]) << 8) | + static_cast(hostBuf[3]); + const uint32_t magicLE = (static_cast(hostBuf[3]) << 24) | + (static_cast(hostBuf[2]) << 16) | + (static_cast(hostBuf[1]) << 8) | + static_cast(hostBuf[0]); + if (magic == 0x56414770u || magicLE == 0x56414770u) + { + VagAccumEntry &e = g_vagAccum[ps2Fd]; + e.firstBufAddr = bufAddr; + if (bytesRead <= kVagAccumMaxBytes) + e.data.assign(hostBuf, hostBuf + bytesRead); + } + } + } + + setReturnS32(ctx, (int32_t)bytesRead); + } + + void fioWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int ps2Fd = (int)getRegU32(ctx, 4); // $a0 + uint32_t bufAddr = getRegU32(ctx, 5); // $a1 + size_t size = getRegU32(ctx, 6); // $a2 + + const uint8_t *hostBuf = getConstMemPtr(rdram, bufAddr); + if (!hostBuf) + { + setReturnS32(ctx, -1); + return; + } + + FILE *fp = getHostFile(ps2Fd); + if (!fp) + { + setReturnS32(ctx, -1); // -EFAULT + return; + } + + if (size == 0) + { + setReturnS32(ctx, 0); // Wrote 0 bytes + return; + } + + size_t bytesWritten = 0; + { + std::lock_guard lock(g_sys_fd_mutex); + bytesWritten = ::fwrite(hostBuf, 1, size, fp); + if (bytesWritten < size && ferror(fp)) + { + clearerr(fp); + setReturnS32(ctx, -1); // -EIO, -ENOSPC etc. + return; + } + } + + // returns number of bytes written + setReturnS32(ctx, (int32_t)bytesWritten); + } + + void fioLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int ps2Fd = (int)getRegU32(ctx, 4); // $a0 + int32_t offset = getRegU32(ctx, 5); // $a1 (PS2 seems to use 32-bit offset here commonly) + int whence = (int)getRegU32(ctx, 6); // $a2 (PS2 FIO_SEEK constants) + + FILE *fp = getHostFile(ps2Fd); + if (!fp) + { + std::cerr << "fioLseek error: Invalid file descriptor " << ps2Fd << std::endl; + setReturnS32(ctx, -1); // -EBADF + return; + } + + int hostWhence; + switch (whence) + { + case PS2_FIO_SEEK_SET: + hostWhence = SEEK_SET; + break; + case PS2_FIO_SEEK_CUR: + hostWhence = SEEK_CUR; + break; + case PS2_FIO_SEEK_END: + hostWhence = SEEK_END; + break; + default: + std::cerr << "fioLseek error: Invalid whence value " << whence << " for fd " << ps2Fd << std::endl; + setReturnS32(ctx, -1); // -EINVAL + return; + } + + if (::fseek(fp, static_cast(offset), hostWhence) != 0) + { + std::cerr << "fioLseek error: fseek failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; + setReturnS32(ctx, -1); // Return error code + return; + } + + long newPos = ::ftell(fp); + if (newPos < 0) + { + std::cerr << "fioLseek error: ftell failed after fseek for fd " << ps2Fd << ": " << strerror(errno) << std::endl; + setReturnS32(ctx, -1); + } + else + { + if (newPos > 0xFFFFFFFFL) + { + std::cerr << "fioLseek warning: New position exceeds 32-bit for fd " << ps2Fd << std::endl; + setReturnS32(ctx, -1); + } + else + { + setReturnS32(ctx, (int32_t)newPos); + } + } + } + + void fioMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + // int mode = (int)getRegU32(ctx, 5); // $a1 - ignored on host + + const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + if (!ps2Path) + { + std::cerr << "fioMkdir error: Invalid path address" << std::endl; + setReturnS32(ctx, -1); // -EFAULT + return; + } + std::string hostPath = translatePs2Path(ps2Path); + if (hostPath.empty()) + { + std::cerr << "fioMkdir error: Failed to translate path '" << ps2Path << "'" << std::endl; + setReturnS32(ctx, -1); + return; + } + std::error_code ec; + bool success = std::filesystem::create_directory(hostPath, ec); + + if (!success && ec) + { + std::cerr << "fioMkdir error: create_directory failed for '" << hostPath + << "': " << ec.message() << std::endl; + setReturnS32(ctx, -1); + } + else + { + RUNTIME_LOG("fioMkdir: Created directory '" << hostPath << "'"); + setReturnS32(ctx, 0); // Success + } + } + + void fioChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + if (!ps2Path) + { + std::cerr << "fioChdir error: Invalid path address" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::string hostPath = translatePs2Path(ps2Path); + if (hostPath.empty()) + { + std::cerr << "fioChdir error: Failed to translate path '" << ps2Path << "'" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::error_code ec; + std::filesystem::current_path(hostPath, ec); + + if (ec) + { + std::cerr << "fioChdir error: current_path failed for '" << hostPath + << "': " << ec.message() << std::endl; + setReturnS32(ctx, -1); + } + else + { + RUNTIME_LOG("fioChdir: Changed directory to '" << hostPath << "'"); + setReturnS32(ctx, 0); // Success + } + } + + void fioRmdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + if (!ps2Path) + { + std::cerr << "fioRmdir error: Invalid path address" << std::endl; + setReturnS32(ctx, -1); + return; + } + std::string hostPath = translatePs2Path(ps2Path); + if (hostPath.empty()) + { + std::cerr << "fioRmdir error: Failed to translate path '" << ps2Path << "'" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::error_code ec; + bool success = std::filesystem::remove(hostPath, ec); + + if (!success || ec) + { + std::cerr << "fioRmdir error: remove failed for '" << hostPath + << "': " << ec.message() << std::endl; + setReturnS32(ctx, -1); + } + else + { + RUNTIME_LOG("fioRmdir: Removed directory '" << hostPath << "'"); + setReturnS32(ctx, 0); // Success + } + } + + void fioGetstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + // we wont implement this for now. + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + uint32_t statBufAddr = getRegU32(ctx, 5); // $a1 + + const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + uint8_t *ps2StatBuf = getMemPtr(rdram, statBufAddr); + + if (!ps2Path) + { + std::cerr << "fioGetstat error: Invalid path addr" << std::endl; + setReturnS32(ctx, -1); + return; + } + if (!ps2StatBuf) + { + std::cerr << "fioGetstat error: Invalid buffer addr" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::string hostPath = translatePs2Path(ps2Path); + if (hostPath.empty()) + { + std::cerr << "fioGetstat error: Bad path translate" << std::endl; + setReturnS32(ctx, -1); + return; + } + + setReturnS32(ctx, -1); + } + + void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); + if (!ps2Path) + { + std::cerr << "fioRemove error: Invalid path" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::string hostPath = translatePs2Path(ps2Path); + if (hostPath.empty()) + { + std::cerr << "fioRemove error: Path translate fail" << std::endl; + setReturnS32(ctx, -1); + return; + } + + std::error_code ec; + bool success = std::filesystem::remove(hostPath, ec); + + if (!success || ec) + { + std::cerr << "fioRemove error: remove failed for '" << hostPath + << "': " << ec.message() << std::endl; + setReturnS32(ctx, -1); + } + else + { + RUNTIME_LOG("fioRemove: Removed file '" << hostPath << "'"); + setReturnS32(ctx, 0); // Success + } + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/FileIO.h b/ps2xRuntime/src/lib/Kernel/Syscalls/FileIO.h new file mode 100644 index 00000000..b4bb7cbf --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/FileIO.h @@ -0,0 +1,17 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioRmdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioGetstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_loader.inl b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Loader.h similarity index 97% rename from ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_loader.inl rename to ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Loader.h index 0c50928d..d9da8eed 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_loader.inl +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Loader.h @@ -67,11 +67,11 @@ namespace return; } - std::cout << "[SIF module] " << op + RUNTIME_LOG("[SIF module] " << op << " id=" << moduleId << " ref=" << refCount << " path=\"" << path << "\"" - << std::endl; + << std::endl); ++g_sif_module_log_count; } @@ -259,8 +259,8 @@ namespace static uint32_t secFilterLogCount = 0; if (!loadAll && secFilterLogCount < 8u) { - std::cout << "[SifLoadElfPart] section filter \"" << sectionName - << "\" requested; loading PT_LOAD segments only." << std::endl; + RUNTIME_LOG("[SifLoadElfPart] section filter \"" << sectionName + << "\" requested; loading PT_LOAD segments only." << std::endl); ++secFilterLogCount; } @@ -412,8 +412,8 @@ namespace 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; + RUNTIME_LOG("[SifLoadElfPart] loaded \"" << ps2Path << "\" epc=0x" + << std::hex << execData.epc << " gp=0x" << execData.gp << std::dec << std::endl); ++successLogs; } diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_path.inl b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Path.h similarity index 100% rename from ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_path.inl rename to ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Path.h diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Runtime.h similarity index 99% rename from ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl rename to ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Runtime.h index 25bf34bc..76cb5583 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/Runtime.h @@ -463,7 +463,7 @@ static int g_intc_tail_order = 1000; static int g_dmac_head_order = 0; static int g_dmac_tail_order = 1000; -std::string translatePs2Path(const char *ps2Path) +inline std::string translatePs2Path(const char *ps2Path) { if (!ps2Path || !*ps2Path) { @@ -718,3 +718,4 @@ static void ensureBootModeTable(uint8_t *rdram) g_bootmode_initialized = true; } + diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/State.h similarity index 70% rename from ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl rename to ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/State.h index 6cb51f43..fac2c0e8 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Helpers/State.h @@ -1,5 +1,5 @@ -std::unordered_map g_fileDescriptors; -int g_nextFd = 3; // Start after stdin, stdout, stderr +inline std::unordered_map g_fileDescriptors; +inline int g_nextFd = 3; // Start after stdin, stdout, stderr struct ThreadInfo { @@ -197,26 +197,26 @@ 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_hostThreads; -static std::mutex g_host_thread_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}; -static std::mutex g_fd_mutex; +inline std::unordered_map> g_threads; +inline int g_nextThreadId = 2; // Reserve 1 for the main thread +inline thread_local int g_currentThreadId = 1; +inline std::mutex g_thread_map_mutex; +inline std::unordered_map g_hostThreads; +inline std::mutex g_host_thread_mutex; + +inline std::unordered_map> g_semas; +inline int g_nextSemaId = 1; +inline std::mutex g_sema_map_mutex; +inline std::unordered_map> g_eventFlags; +inline int g_nextEventFlagId = 1; +inline std::mutex g_event_flag_map_mutex; +inline std::unordered_map> g_alarms; +inline int g_nextAlarmId = 1; +inline std::mutex g_alarm_mutex; +inline std::condition_variable g_alarm_cv; +inline std::once_flag g_alarm_worker_once; +inline std::atomic g_activeThreads{0}; +inline std::mutex g_fd_mutex; static void registerHostThread(int tid, std::thread worker) { @@ -340,23 +340,71 @@ struct RpcClientState 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 std::recursive_mutex g_sif_call_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 SoundDriverRpcState +{ + uintptr_t ownerRuntime = 0; + bool initialized = false; + uint32_t storageBaseAddr = 0; + uint32_t storageSize = 0; + uint32_t statusAddr = 0; + uint32_t addrTableAddr = 0; + uint32_t hdBaseAddr = 0; + uint32_t sqBaseAddr = 0; + uint32_t dataBaseAddr = 0; +}; + +inline std::unordered_map g_rpc_servers; +inline std::unordered_map g_rpc_clients; +inline std::mutex g_rpc_mutex; +inline std::recursive_mutex g_sif_call_rpc_mutex; +inline bool g_rpc_initialized = false; +inline uint32_t g_rpc_next_id = 1; +inline uint32_t g_rpc_packet_index = 0; +inline uint32_t g_rpc_server_index = 0; +inline uint32_t g_rpc_active_queue = 0; +inline SoundDriverRpcState g_soundDriverRpcState; +inline PS2SoundDriverCompatLayout g_soundDriverCompatLayout; +inline PS2DtxCompatLayout g_dtxCompatLayout; +inline std::mutex g_dtx_rpc_mutex; +inline std::unordered_map g_dtx_remote_by_id; +inline uint32_t g_dtx_next_urpc_obj = 0u; + +struct DtxTransferState +{ + uint32_t dtxId = 0; + uint32_t remoteHandle = 0; + uint32_t eeWorkAddr = 0; + uint32_t iopWorkAddr = 0; + uint32_t wkSize = 0; +}; + +inline std::unordered_map g_dtx_transfer_by_id; + +struct DtxSjxState +{ + uint32_t handle = 0; + uint32_t srcSjHandle = 0; + uint32_t dstSjHandle = 0; + uint32_t line = 0; + uint32_t eeObjAddr = 0; + uint16_t xid = 0; +}; + +inline std::unordered_map g_dtx_sjx_by_handle; + +struct DtxPs2RnaState +{ + uint32_t handle = 0; + uint32_t maxChannels = 0; + uint32_t sjHandle0 = 0; + uint32_t sjHandle1 = 0; + uint32_t channelCount = 0; + uint32_t sampleFreq = 0; + uint32_t volume = 0; + bool playEnabled = false; +}; + +inline std::unordered_map g_dtx_ps2rna_by_handle; struct DtxSjrmtState { @@ -374,7 +422,7 @@ struct DtxSjrmtState uint32_t uuid3 = 0; }; -static std::unordered_map g_dtx_sjrmt_by_handle; +inline std::unordered_map g_dtx_sjrmt_by_handle; static uint32_t dtxNormalizeSjrmtCapacity(uint32_t requestedBytes) { @@ -387,16 +435,27 @@ static uint32_t dtxNormalizeSjrmtCapacity(uint32_t requestedBytes) static uint32_t dtxAllocUrpcHandleLocked() { + const PS2DtxCompatLayout &layout = g_dtxCompatLayout; + if (!layout.hasUrpcObjectRange()) + { + return 0u; + } + + if (g_dtx_next_urpc_obj < layout.urpcObjBase || g_dtx_next_urpc_obj >= layout.urpcObjLimit) + { + g_dtx_next_urpc_obj = layout.urpcObjBase; + } + 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 += layout.urpcObjStride; + if (g_dtx_next_urpc_obj < layout.urpcObjBase || g_dtx_next_urpc_obj >= layout.urpcObjLimit) { - g_dtx_next_urpc_obj = kDtxUrpcObjBase; + g_dtx_next_urpc_obj = layout.urpcObjBase; } - if (candidate < kDtxUrpcObjBase || candidate >= kDtxUrpcObjLimit) + if (candidate < layout.urpcObjBase || candidate >= layout.urpcObjLimit) { continue; } @@ -406,6 +465,16 @@ static uint32_t dtxAllocUrpcHandleLocked() continue; } + if (g_dtx_sjx_by_handle.find(candidate) != g_dtx_sjx_by_handle.end()) + { + continue; + } + + if (g_dtx_ps2rna_by_handle.find(candidate) != g_dtx_ps2rna_by_handle.end()) + { + continue; + } + bool inUseByDtxRemote = false; for (const auto &entry : g_dtx_remote_by_id) { @@ -422,7 +491,7 @@ static uint32_t dtxAllocUrpcHandleLocked() } } - return kDtxUrpcObjBase; + return layout.urpcObjBase; } struct ExitHandlerEntry @@ -431,37 +500,37 @@ struct ExitHandlerEntry uint32_t arg = 0; }; -static std::mutex g_exit_handler_mutex; -static std::unordered_map> g_exit_handlers; +inline std::mutex g_exit_handler_mutex; +inline 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; +inline std::mutex g_bootmode_mutex; +inline bool g_bootmode_initialized = false; +inline uint32_t g_bootmode_pool_offset = 0; +inline std::unordered_map g_bootmode_addresses; -static std::mutex g_syscall_override_mutex; -static std::unordered_map g_syscall_overrides; -static std::unordered_set g_syscall_mirror_addrs; +inline std::mutex g_syscall_override_mutex; +inline std::unordered_map g_syscall_overrides; +inline std::unordered_set g_syscall_mirror_addrs; static constexpr uint32_t kGuestSyscallTableGuestBase = 0x80011F80u; static constexpr uint32_t kGuestSyscallTablePhysBase = kGuestSyscallTableGuestBase & 0x1FFFFFFFu; static constexpr uint32_t kGuestSyscallMirrorLimit = 0x00080000u; static constexpr uint32_t kGuestSyscallTableProbeBase = 0x000002F0u; -static std::mutex g_tls_mutex; -static uint32_t g_tls_index = 0; +inline std::mutex g_tls_mutex; +inline 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; +inline std::mutex g_osd_mutex; +inline bool g_osd_config_initialized = false; +inline 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"; +inline std::mutex g_ps2_path_mutex; +inline bool g_ps2_paths_initialized = false; +inline std::filesystem::path g_host_base; +inline std::filesystem::path g_cdrom_base; +inline std::filesystem::path g_host_cwd; +inline std::filesystem::path g_cdrom_cwd; +inline std::string g_ps2_cwd_device = "host0"; static constexpr uint32_t kRpcPacketSize = 64; static constexpr uint32_t kRpcPacketPoolBase = 0x01F00000; @@ -568,8 +637,9 @@ struct SifModuleRecord 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; +inline std::mutex g_sif_module_mutex; +inline std::unordered_map g_sif_modules_by_id; +inline std::unordered_map g_sif_module_id_by_path; +inline int32_t g_next_sif_module_id = 1; +inline uint32_t g_sif_module_log_count = 0; + diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Interrupt.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/Interrupt.cpp new file mode 100644 index 00000000..ce14411e --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Interrupt.cpp @@ -0,0 +1,675 @@ +#include "Common.h" +#include "Interrupt.h" +#include "ps2_log.h" +#include "Stubs/GS.h" + +namespace ps2_syscalls +{ + namespace interrupt_state + { + constexpr uint32_t kIntcVblankStart = 2u; + constexpr uint32_t kIntcVblankEnd = 3u; + constexpr auto kVblankPeriod = std::chrono::microseconds(16667); + constexpr int kMaxCatchupTicks = 4; + + std::mutex g_irq_handler_mutex; + std::mutex g_irq_worker_mutex; + std::condition_variable g_irq_worker_cv; + std::mutex g_vsync_flag_mutex; + std::condition_variable g_vsync_cv; + std::atomic g_irq_worker_stop{false}; + std::atomic g_irq_worker_running{false}; + uint32_t g_enabled_intc_mask = 0xFFFFFFFFu; + uint32_t g_enabled_dmac_mask = 0xFFFFFFFFu; + uint64_t g_vsync_tick_counter = 0u; + VSyncFlagRegistration g_vsync_registration{}; + } + + using namespace interrupt_state; + + static void writeGuestU32NoThrow(uint8_t *rdram, uint32_t addr, uint32_t value) + { + if (addr == 0u) + { + return; + } + + uint8_t *dst = getMemPtr(rdram, addr); + if (!dst) + { + return; + } + std::memcpy(dst, &value, sizeof(value)); + } + + static void writeGuestU64NoThrow(uint8_t *rdram, uint32_t addr, uint64_t value) + { + if (addr == 0u) + { + return; + } + + uint8_t *dst = getMemPtr(rdram, addr); + if (!dst) + { + return; + } + std::memcpy(dst, &value, sizeof(value)); + } + + static uint32_t readGuestU32NoThrow(uint8_t *rdram, uint32_t addr) + { + if (addr == 0u) + { + return 0u; + } + + uint8_t *src = getMemPtr(rdram, addr); + if (!src) + { + return 0u; + } + + uint32_t value = 0u; + std::memcpy(&value, src, sizeof(value)); + return value; + } + + static uint32_t getAsyncHandlerStackTop(PS2Runtime *runtime) + { + constexpr uint32_t kAsyncHandlerStackSize = 0x4000u; + thread_local PS2Runtime *s_cachedRuntime = nullptr; + thread_local uint32_t s_cachedStackTop = 0u; + + if (runtime == nullptr) + { + return PS2_RAM_SIZE - 0x10u; + } + + if (s_cachedRuntime != runtime || s_cachedStackTop == 0u) + { + s_cachedRuntime = runtime; + s_cachedStackTop = runtime->reserveAsyncCallbackStack(kAsyncHandlerStackSize, 16u); + } + + return (s_cachedStackTop != 0u) ? s_cachedStackTop : (PS2_RAM_SIZE - 0x10u); + } + + static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause) + { + if (!rdram || !runtime) + { + return; + } + + std::vector handlers; + { + std::lock_guard lock(g_irq_handler_mutex); + if (cause < 32u && (g_enabled_intc_mask & (1u << cause)) == 0u) + { + return; + } + + handlers.reserve(g_intcHandlers.size()); + for (const auto &[id, info] : g_intcHandlers) + { + (void)id; + if (!info.enabled) + { + continue; + } + if (info.cause != cause) + { + continue; + } + if (info.handler == 0u) + { + continue; + } + handlers.push_back(info); + } + std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) + { return a.order < b.order; }); + } + + for (const IrqHandlerInfo &info : handlers) + { + if (!runtime->hasFunction(info.handler)) + { + if (cause == kIntcVblankStart) + { + PS2_IF_AGRESSIVE_LOGS({ + static std::atomic s_missingHandlerLogCount{0u}; + const uint32_t logIndex = s_missingHandlerLogCount.fetch_add(1u, std::memory_order_relaxed); + if (logIndex < 32u) + { + auto flags = std::cout.flags(); + std::cout << "[INTC:missing] cause=" << cause + << " handler=0x" << std::hex << info.handler + << std::dec + << " id=" << info.id + << std::endl; + std::cout.flags(flags); + } + }); + } + continue; + } + + try + { + R5900Context irqCtx{}; + SET_GPR_U32(&irqCtx, 28, info.gp); + SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime)); + SET_GPR_U32(&irqCtx, 31, 0u); + SET_GPR_U32(&irqCtx, 4, cause); + SET_GPR_U32(&irqCtx, 5, info.arg); + SET_GPR_U32(&irqCtx, 6, 0u); + SET_GPR_U32(&irqCtx, 7, 0u); + irqCtx.pc = info.handler; + + while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested()) + { + PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc); + if (!step) + { + break; + } + // Interrupt handlers must be able to preempt a guest thread that is + // spinning on interrupt-produced state, such as a vblank counter. + step(rdram, &irqCtx, runtime); + } + } + catch (const ThreadExitException &) + { + } + catch (const std::exception &e) + { + static uint32_t warnCount = 0; + if (warnCount < 8u) + { + std::cerr << "[INTC] handler 0x" << std::hex << info.handler + << " threw exception: " << e.what() << std::dec << std::endl; + ++warnCount; + } + } + } + } + + void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause) + { + if (!rdram || !runtime) + { + return; + } + + std::vector handlers; + { + std::lock_guard lock(g_irq_handler_mutex); + if (cause < 32u && (g_enabled_dmac_mask & (1u << cause)) == 0u) + { + return; + } + + handlers.reserve(g_dmacHandlers.size()); + for (const auto &[id, info] : g_dmacHandlers) + { + (void)id; + if (!info.enabled) + { + continue; + } + if (info.cause != cause) + { + continue; + } + if (info.handler == 0u) + { + continue; + } + handlers.push_back(info); + } + std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) + { return a.order < b.order; }); + } + + for (const IrqHandlerInfo &info : handlers) + { + if (!runtime->hasFunction(info.handler)) + { + continue; + } + + try + { + R5900Context irqCtx{}; + SET_GPR_U32(&irqCtx, 28, info.gp); + SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime)); + SET_GPR_U32(&irqCtx, 31, 0u); + SET_GPR_U32(&irqCtx, 4, cause); + SET_GPR_U32(&irqCtx, 5, info.arg); + SET_GPR_U32(&irqCtx, 6, 0u); + SET_GPR_U32(&irqCtx, 7, 0u); + irqCtx.pc = info.handler; + + while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested()) + { + PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc); + if (!step) + { + break; + } + step(rdram, &irqCtx, runtime); + } + } + catch (const ThreadExitException &) + { + } + catch (const std::exception &e) + { + static uint32_t warnCount = 0; + if (warnCount < 8u) + { + std::cerr << "[DMAC] handler 0x" << std::hex << info.handler + << " threw exception: " << e.what() << std::dec << std::endl; + ++warnCount; + } + } + } + } + + static uint64_t signalVSyncFlag(uint8_t *rdram) + { + VSyncFlagRegistration reg{}; + uint64_t tickValue = 0u; + { + std::lock_guard lock(g_vsync_flag_mutex); + reg = g_vsync_registration; + tickValue = ++g_vsync_tick_counter; + } + + g_vsync_cv.notify_all(); + + if (reg.flagAddr != 0u) + { + writeGuestU32NoThrow(rdram, reg.flagAddr, 1u); + } + if (reg.tickAddr != 0u) + { + writeGuestU64NoThrow(rdram, reg.tickAddr, tickValue); + } + return tickValue; + } + + static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime) + { + g_currentThreadId = -1; + + using clock = std::chrono::steady_clock; + auto nextTick = clock::now() + kVblankPeriod; + + while (runtime != nullptr && !runtime->isStopRequested()) + { + { + std::unique_lock lock(g_irq_worker_mutex); + if (g_irq_worker_cv.wait_until(lock, nextTick, []() + { return g_irq_worker_stop.load(std::memory_order_acquire); })) + { + break; + } + } + + const auto now = clock::now(); + int ticksToProcess = 0; + while (now >= nextTick && ticksToProcess < kMaxCatchupTicks) + { + ++ticksToProcess; + nextTick += kVblankPeriod; + } + if (ticksToProcess == 0) + { + continue; + } + + for (int i = 0; i < ticksToProcess; ++i) + { + const uint64_t tickValue = signalVSyncFlag(rdram); + ps2_stubs::dispatchGsSyncVCallback(rdram, runtime, tickValue); + dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart); + std::this_thread::sleep_for(std::chrono::microseconds(500)); + dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd); + } + } + + g_irq_worker_running.store(false, std::memory_order_release); + g_irq_worker_cv.notify_all(); + } + + static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime) + { + if (!rdram || !runtime) + { + return; + } + + std::lock_guard lock(g_irq_worker_mutex); + if (g_irq_worker_running.load(std::memory_order_acquire)) + { + return; + } + + g_irq_worker_stop.store(false, std::memory_order_release); + g_irq_worker_running.store(true, std::memory_order_release); + try + { + std::thread(interruptWorkerMain, rdram, runtime).detach(); + } + catch (...) + { + g_irq_worker_running.store(false, std::memory_order_release); + } + } + + void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime) + { + ensureInterruptWorkerRunning(rdram, runtime); + } + + uint64_t GetCurrentVSyncTick() + { + std::lock_guard lock(g_vsync_flag_mutex); + return g_vsync_tick_counter; + } + + void stopInterruptWorker() + { + g_irq_worker_stop.store(true, std::memory_order_release); + g_irq_worker_cv.notify_all(); + std::unique_lock lock(g_irq_worker_mutex); + g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []() + { return !g_irq_worker_running.load(std::memory_order_acquire); }); + g_vsync_cv.notify_all(); + } + + uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime) + { + ensureInterruptWorkerRunning(rdram, runtime); + std::unique_lock lock(g_vsync_flag_mutex); + uint64_t current = g_vsync_tick_counter; + { + PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); + g_vsync_cv.wait(lock, [current, runtime]() + { return g_vsync_tick_counter > current || (runtime != nullptr && runtime->isStopRequested()); }); + } + return g_vsync_tick_counter; + } + + void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime) + { + (void)WaitForNextVSyncTick(rdram, runtime); + } + + void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t flagAddr = getRegU32(ctx, 4); + const uint32_t tickAddr = getRegU32(ctx, 5); + + { + std::lock_guard lock(g_vsync_flag_mutex); + g_vsync_registration.flagAddr = flagAddr; + g_vsync_registration.tickAddr = tickAddr; + } + + writeGuestU32NoThrow(rdram, flagAddr, 0u); + writeGuestU64NoThrow(rdram, tickAddr, 0u); + ensureInterruptWorkerRunning(rdram, runtime); + setReturnS32(ctx, KE_OK); + } + + void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_intc_mask |= (1u << cause); + } + if (cause == kIntcVblankStart || cause == kIntcVblankEnd) + { + PS2_IF_AGRESSIVE_LOGS({ + static std::atomic s_enableLogCount{0u}; + const uint32_t logIndex = s_enableLogCount.fetch_add(1u, std::memory_order_relaxed); + if (logIndex < 32u) + { + RUNTIME_LOG("[EnableIntc] cause=" << cause); + } + }); + } + setReturnS32(ctx, KE_OK); + } + + void iEnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + EnableIntc(rdram, ctx, runtime); + } + + void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_intc_mask &= ~(1u << cause); + } + if (cause == kIntcVblankStart || cause == kIntcVblankEnd) + { + PS2_IF_AGRESSIVE_LOGS({ + static std::atomic s_disableLogCount{0u}; + const uint32_t logIndex = s_disableLogCount.fetch_add(1u, std::memory_order_relaxed); + if (logIndex < 32u) + { + RUNTIME_LOG("[DisableIntc] cause=" << cause); + } + }); + } + setReturnS32(ctx, KE_OK); + } + + void iDisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + DisableIntc(rdram, ctx, runtime); + } + + void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + IrqHandlerInfo info{}; + info.cause = getRegU32(ctx, 4); + info.handler = getRegU32(ctx, 5); + uint32_t next = getRegU32(ctx, 6); + info.arg = getRegU32(ctx, 7); + info.gp = getRegU32(ctx, 28); + info.sp = getRegU32(ctx, 29); + info.enabled = true; + + int handlerId = 0; + { + std::lock_guard lock(g_irq_handler_mutex); + info.order = (next == 0) ? --g_intc_head_order : ++g_intc_tail_order; + handlerId = g_nextIntcHandlerId++; + info.id = handlerId; + g_intcHandlers[handlerId] = info; + } + + if (info.cause == kIntcVblankStart) + { + PS2_IF_AGRESSIVE_LOGS({ + static std::atomic s_addHandlerLogCount{0u}; + const uint32_t logIndex = s_addHandlerLogCount.fetch_add(1u, std::memory_order_relaxed); + if (logIndex < 32u) + { + auto flags = std::cout.flags(); + std::cout << "[AddIntcHandler] cause=" << info.cause + << " handler=0x" << std::hex << info.handler + << " arg=0x" << info.arg + << " gp=0x" << info.gp + << " sp=0x" << info.sp + << std::dec + << " id=" << handlerId + << std::endl; + std::cout.flags(flags); + } + }); + } + + ensureInterruptWorkerRunning(rdram, runtime); + setReturnS32(ctx, handlerId); + } + + void AddIntcHandler2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + AddIntcHandler(rdram, ctx, runtime); + } + + void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cause = getRegU32(ctx, 4); + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (handlerId > 0) + { + std::lock_guard lock(g_irq_handler_mutex); + auto it = g_intcHandlers.find(handlerId); + if (it != g_intcHandlers.end() && it->second.cause == cause) + { + g_intcHandlers.erase(it); + } + } + setReturnS32(ctx, KE_OK); + } + + void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + IrqHandlerInfo info{}; + info.cause = getRegU32(ctx, 4); + info.handler = getRegU32(ctx, 5); + uint32_t next = getRegU32(ctx, 6); + info.arg = getRegU32(ctx, 7); + info.gp = getRegU32(ctx, 28); + info.sp = getRegU32(ctx, 29); + info.enabled = true; + + int handlerId = 0; + { + std::lock_guard lock(g_irq_handler_mutex); + info.order = (next == 0) ? --g_dmac_head_order : ++g_dmac_tail_order; + handlerId = g_nextDmacHandlerId++; + info.id = handlerId; + g_dmacHandlers[handlerId] = info; + } + setReturnS32(ctx, handlerId); + } + + void AddDmacHandler2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + AddDmacHandler(rdram, ctx, runtime); + } + + void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cause = getRegU32(ctx, 4); + const int handlerId = static_cast(getRegU32(ctx, 5)); + if (handlerId > 0) + { + std::lock_guard lock(g_irq_handler_mutex); + auto it = g_dmacHandlers.find(handlerId); + if (it != g_dmacHandlers.end() && it->second.cause == cause) + { + g_dmacHandlers.erase(it); + } + } + setReturnS32(ctx, KE_OK); + } + + void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + { + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) + { + it->second.enabled = true; + } + } + setReturnS32(ctx, KE_OK); + } + + void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + { + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) + { + it->second.enabled = false; + } + } + setReturnS32(ctx, KE_OK); + } + + void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + { + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) + { + it->second.enabled = true; + } + } + setReturnS32(ctx, KE_OK); + } + + void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const int handlerId = static_cast(getRegU32(ctx, 5)); + { + std::lock_guard lock(g_irq_handler_mutex); + if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) + { + it->second.enabled = false; + } + } + setReturnS32(ctx, KE_OK); + } + + void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_dmac_mask |= (1u << cause); + } + setReturnS32(ctx, KE_OK); + } + + void iEnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + EnableDmac(rdram, ctx, runtime); + } + + void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t cause = getRegU32(ctx, 4); + if (cause < 32u) + { + std::lock_guard lock(g_irq_handler_mutex); + g_enabled_dmac_mask &= ~(1u << cause); + } + setReturnS32(ctx, KE_OK); + } + + void iDisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + DisableDmac(rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Interrupt.h b/ps2xRuntime/src/lib/Kernel/Syscalls/Interrupt.h new file mode 100644 index 00000000..eb9ba5f0 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Interrupt.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + namespace interrupt_state + { + struct VSyncFlagRegistration + { + uint32_t flagAddr; + uint32_t tickAddr; + }; + + extern std::mutex g_irq_handler_mutex; + extern std::mutex g_irq_worker_mutex; + extern std::condition_variable g_irq_worker_cv; + extern std::mutex g_vsync_flag_mutex; + extern std::condition_variable g_vsync_cv; + extern std::atomic g_irq_worker_stop; + extern std::atomic g_irq_worker_running; + extern uint32_t g_enabled_intc_mask; + extern uint32_t g_enabled_dmac_mask; + extern uint64_t g_vsync_tick_counter; + extern VSyncFlagRegistration g_vsync_registration; + } + + void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause); + void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime); + uint64_t GetCurrentVSyncTick(); + void stopInterruptWorker(); + uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime); + void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime); + void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iEnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iDisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void AddIntcHandler2(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 AddDmacHandler2(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 EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iEnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iDisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Lifecycle.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/Lifecycle.cpp new file mode 100644 index 00000000..d5a31ea7 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Lifecycle.cpp @@ -0,0 +1,116 @@ +#include "Common.h" +#include "Interrupt.h" +#include "Lifecycle.h" + +namespace ps2_syscalls +{ + using namespace interrupt_state; + + void notifyRuntimeStop() + { + stopInterruptWorker(); + { + std::lock_guard lock(g_irq_handler_mutex); + g_intcHandlers.clear(); + g_dmacHandlers.clear(); + g_nextIntcHandlerId = 1; + g_nextDmacHandlerId = 1; + g_enabled_intc_mask = 0xFFFFFFFFu; + g_enabled_dmac_mask = 0xFFFFFFFFu; + } + { + std::lock_guard lock(g_vsync_flag_mutex); + g_vsync_registration = {}; + g_vsync_tick_counter = 0u; + } + + std::vector> threads; + threads.reserve(32); + { + std::lock_guard lock(g_thread_map_mutex); + for (const auto &entry : g_threads) + { + if (entry.second) + { + threads.push_back(entry.second); + } + } + g_threads.clear(); + g_nextThreadId = 2; // Reserve id 1 for main thread. + } + g_currentThreadId = 1; + + for (const auto &threadInfo : threads) + { + { + std::lock_guard lock(threadInfo->m); + threadInfo->forceRelease = true; + threadInfo->terminated = true; + } + threadInfo->cv.notify_all(); + } + + std::vector> semas; + { + std::lock_guard lock(g_sema_map_mutex); + semas.reserve(g_semas.size()); + for (const auto &entry : g_semas) + { + if (entry.second) + { + semas.push_back(entry.second); + } + } + g_semas.clear(); + g_nextSemaId = 1; + } + for (const auto &sema : semas) + { + sema->cv.notify_all(); + } + + std::vector> eventFlags; + { + std::lock_guard lock(g_event_flag_map_mutex); + eventFlags.reserve(g_eventFlags.size()); + for (const auto &entry : g_eventFlags) + { + if (entry.second) + { + eventFlags.push_back(entry.second); + } + } + g_eventFlags.clear(); + g_nextEventFlagId = 1; + } + for (const auto &eventFlag : eventFlags) + { + eventFlag->cv.notify_all(); + } + + { + std::lock_guard lock(g_alarm_mutex); + g_alarms.clear(); + } + g_alarm_cv.notify_all(); + + { + std::lock_guard lock(g_exit_handler_mutex); + g_exit_handlers.clear(); + } + { + std::lock_guard lock(g_syscall_override_mutex); + g_syscall_overrides.clear(); + } + } + + void joinAllGuestHostThreads() + { + joinAllHostThreads(); + } + + void detachAllGuestHostThreads() + { + detachAllHostThreads(); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Lifecycle.h b/ps2xRuntime/src/lib/Kernel/Syscalls/Lifecycle.h new file mode 100644 index 00000000..749ca1e2 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Lifecycle.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + void notifyRuntimeStop(); + void joinAllGuestHostThreads(); + void detachAllGuestHostThreads(); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/RPC.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/RPC.cpp new file mode 100644 index 00000000..f9f26ea2 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/RPC.cpp @@ -0,0 +1,2512 @@ +#include "Common.h" +#include "RPC.h" + +namespace ps2_syscalls +{ + namespace + { + constexpr uint32_t kSoundDriverStatusSize = 0x42u; + constexpr uint32_t kSoundDriverSeInfoOffset = 0x00u; + constexpr uint32_t kSoundDriverMidiInfoOffset = 0x0Cu; + constexpr uint32_t kSoundDriverMidiSumOffset = 0x1Eu; + constexpr uint32_t kSoundDriverSeSumOffset = 0x26u; + constexpr uint32_t kSoundDriverAddrTableEntries = 16u; + constexpr uint32_t kSoundDriverHdRegionSize = 0x4000u; + constexpr uint32_t kSoundDriverSqRegionSize = 0x18000u; + constexpr uint32_t kSoundDriverDataRegionSize = 0x40000u; + constexpr uint32_t kSoundDriverStatusAlignment = 0x100u; + constexpr uint32_t kSoundDriverAddrTableAlignment = 0x100u; + constexpr uint32_t kSoundDriverStorageAlignment = 0x1000u; + constexpr uint32_t kSoundDriverGuestPoolBase = 0x00120000u; + constexpr uint32_t kSoundDriverGuestPoolLimit = 0x00200000u; + + void resetDtxRpcStateUnlocked() + { + g_dtx_remote_by_id.clear(); + g_dtx_transfer_by_id.clear(); + g_dtx_sjx_by_handle.clear(); + g_dtx_ps2rna_by_handle.clear(); + g_dtx_sjrmt_by_handle.clear(); + g_dtx_next_urpc_obj = g_dtxCompatLayout.urpcObjBase; + } + + bool readGuestU16(const uint8_t *rdram, uint32_t addr, uint16_t &out) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + out = 0u; + return false; + } + std::memcpy(&out, ptr, sizeof(out)); + return true; + } + + bool readGuestS16(const uint8_t *rdram, uint32_t addr, int16_t &out) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + out = 0; + return false; + } + std::memcpy(&out, ptr, sizeof(out)); + return true; + } + + bool writeGuestU16(uint8_t *rdram, uint32_t addr, uint16_t value) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, &value, sizeof(value)); + return true; + } + + bool writeGuestS16(uint8_t *rdram, uint32_t addr, int16_t value) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, &value, sizeof(value)); + return true; + } + + bool readGuestU32(const uint8_t *rdram, uint32_t addr, uint32_t &out) + { + const uint8_t *ptr = getConstMemPtr(rdram, addr); + if (!ptr) + { + out = 0u; + return false; + } + std::memcpy(&out, ptr, sizeof(out)); + return true; + } + + bool writeGuestU32(uint8_t *rdram, uint32_t addr, uint32_t value) + { + uint8_t *ptr = getMemPtr(rdram, addr); + if (!ptr) + { + return false; + } + std::memcpy(ptr, &value, sizeof(value)); + return true; + } + + bool normalizeGuestLinearAddr(uint32_t addr, uint32_t &out) + { + uint32_t offset = 0u; + bool scratch = false; + if (!ps2ResolveGuestPointer(addr, offset, scratch) || scratch) + { + out = 0u; + return false; + } + out = offset; + return true; + } + + bool hasAnyNonZero(const uint8_t *ptr, size_t bytes) + { + if (!ptr) + { + return false; + } + for (size_t i = 0; i < bytes; ++i) + { + if (ptr[i] != 0u) + { + return true; + } + } + return false; + } + + uint32_t alignUp(uint32_t value, uint32_t alignment) + { + if (alignment == 0u) + { + return value; + } + return (value + (alignment - 1u)) & ~(alignment - 1u); + } + + void resetSoundDriverRpcStateUnlocked() + { + const PS2SoundDriverCompatLayout compat = g_soundDriverCompatLayout; + g_soundDriverRpcState = {}; + g_soundDriverCompatLayout = compat; + } + + void adoptSoundDriverRuntimeUnlocked(PS2Runtime *runtime) + { + const uintptr_t runtimeCookie = reinterpret_cast(runtime); + if (g_soundDriverRpcState.ownerRuntime != runtimeCookie) + { + resetSoundDriverRpcStateUnlocked(); + g_soundDriverRpcState.ownerRuntime = runtimeCookie; + } + } + + bool checkSoundDriverMemoryUnlocked(uint8_t *rdram, PS2Runtime *runtime) + { + if (!rdram || !runtime) + { + return false; + } + + adoptSoundDriverRuntimeUnlocked(runtime); + + if (g_soundDriverRpcState.statusAddr == 0u) + { + const uint32_t statusAddr = alignUp(kSoundDriverGuestPoolBase, kSoundDriverStatusAlignment); + const uint32_t addrTableAddr = + alignUp(statusAddr + kSoundDriverStatusSize, kSoundDriverAddrTableAlignment); + const uint32_t hdBaseAddr = + alignUp(addrTableAddr + (kSoundDriverAddrTableEntries * sizeof(uint32_t)), kSoundDriverStorageAlignment); + const uint32_t sqBaseAddr = alignUp(hdBaseAddr + kSoundDriverHdRegionSize, kSoundDriverStorageAlignment); + const uint32_t dataBaseAddr = alignUp(sqBaseAddr + kSoundDriverSqRegionSize, kSoundDriverStorageAlignment); + const uint32_t storageEnd = dataBaseAddr + kSoundDriverDataRegionSize; + if (storageEnd > kSoundDriverGuestPoolLimit) + { + return false; + } + + g_soundDriverRpcState.statusAddr = statusAddr; + g_soundDriverRpcState.addrTableAddr = addrTableAddr; + g_soundDriverRpcState.hdBaseAddr = hdBaseAddr; + g_soundDriverRpcState.sqBaseAddr = sqBaseAddr; + g_soundDriverRpcState.dataBaseAddr = dataBaseAddr; + g_soundDriverRpcState.storageBaseAddr = hdBaseAddr; + g_soundDriverRpcState.storageSize = + (dataBaseAddr + kSoundDriverDataRegionSize) - hdBaseAddr; + } + + if (g_soundDriverRpcState.statusAddr == 0u || + g_soundDriverRpcState.addrTableAddr == 0u || + g_soundDriverRpcState.storageBaseAddr == 0u) + { + return false; + } + + if (!g_soundDriverRpcState.initialized) + { + rpcZeroRdram(rdram, g_soundDriverRpcState.statusAddr, kSoundDriverStatusSize); + rpcZeroRdram(rdram, + g_soundDriverRpcState.addrTableAddr, + kSoundDriverAddrTableEntries * sizeof(uint32_t)); + rpcZeroRdram(rdram, g_soundDriverRpcState.storageBaseAddr, g_soundDriverRpcState.storageSize); + (void)writeGuestU32(rdram, g_soundDriverRpcState.addrTableAddr + (0u * sizeof(uint32_t)), g_soundDriverRpcState.hdBaseAddr); + (void)writeGuestU32(rdram, g_soundDriverRpcState.addrTableAddr + (1u * sizeof(uint32_t)), g_soundDriverRpcState.sqBaseAddr); + (void)writeGuestU32(rdram, g_soundDriverRpcState.addrTableAddr + (2u * sizeof(uint32_t)), g_soundDriverRpcState.dataBaseAddr); + g_soundDriverRpcState.initialized = true; + } + + return true; + } + + int16_t soundDriverCheckValue(const uint8_t *rdram, uint32_t primaryBase, uint32_t fallbackBase, uint32_t index, uint32_t count) + { + if (index >= count) + { + return 0; + } + + int16_t value = 0; + if (readGuestS16(rdram, primaryBase + (index * sizeof(int16_t)), value) && value != 0) + { + return value; + } + + (void)readGuestS16(rdram, fallbackBase + (index * sizeof(int16_t)), value); + return value; + } + + bool selectSoundDriverCompatChecks(const uint8_t *rdram, uint32_t &seBase, uint32_t &midiBase) + { + const PS2SoundDriverCompatLayout &compat = g_soundDriverCompatLayout; + if (!compat.hasChecksumTables()) + { + return false; + } + + seBase = compat.primarySeCheckAddr; + midiBase = compat.primaryMidiCheckAddr; + + const uint8_t *selectedSe = getConstMemPtr(rdram, seBase); + const uint8_t *selectedMidi = getConstMemPtr(rdram, midiBase); + const bool primaryLooksLive = + hasAnyNonZero(selectedSe, 5u * sizeof(int16_t)) || + hasAnyNonZero(selectedMidi, 4u * sizeof(int16_t)); + + if ((!selectedSe || !selectedMidi) || !primaryLooksLive) + { + const uint8_t *fallbackSe = getConstMemPtr(rdram, compat.fallbackSeCheckAddr); + const uint8_t *fallbackMidi = getConstMemPtr(rdram, compat.fallbackMidiCheckAddr); + const bool fallbackLooksLive = + hasAnyNonZero(fallbackSe, 5u * sizeof(int16_t)) || + hasAnyNonZero(fallbackMidi, 4u * sizeof(int16_t)); + if (fallbackLooksLive) + { + seBase = compat.fallbackSeCheckAddr; + midiBase = compat.fallbackMidiCheckAddr; + return true; + } + } + + return selectedSe != nullptr && selectedMidi != nullptr; + } + + void backfillSoundDriverStatusFromCompatUnlocked(uint8_t *rdram) + { + if (!rdram || g_soundDriverRpcState.statusAddr == 0u || !g_soundDriverCompatLayout.hasChecksumTables()) + { + return; + } + + uint32_t seBase = 0u; + uint32_t midiBase = 0u; + if (!selectSoundDriverCompatChecks(rdram, seBase, midiBase)) + { + return; + } + + const uint8_t *selectedSe = getConstMemPtr(rdram, seBase); + const uint8_t *selectedMidi = getConstMemPtr(rdram, midiBase); + uint8_t *status = getMemPtr(rdram, g_soundDriverRpcState.statusAddr); + if (!selectedSe || !selectedMidi || !status) + { + return; + } + + auto backfillZeroS16Slots = [&](uint32_t statusOffset, const uint8_t *compatBase, uint32_t slotCount) + { + for (uint32_t slot = 0u; slot < slotCount; ++slot) + { + int16_t liveValue = 0; + if (!readGuestS16(rdram, + g_soundDriverRpcState.statusAddr + statusOffset + (slot * sizeof(int16_t)), + liveValue)) + { + continue; + } + + if (liveValue != 0) + { + continue; + } + + int16_t compatValue = 0; + std::memcpy(&compatValue, compatBase + (slot * sizeof(int16_t)), sizeof(compatValue)); + if (compatValue == 0) + { + continue; + } + + (void)writeGuestS16(rdram, + g_soundDriverRpcState.statusAddr + statusOffset + (slot * sizeof(int16_t)), + compatValue); + } + }; + + backfillZeroS16Slots(kSoundDriverSeSumOffset, selectedSe, 5u); + backfillZeroS16Slots(kSoundDriverMidiSumOffset, selectedMidi, 4u); + } + + size_t soundDriverCommandLength(uint8_t command) + { + const uint8_t hi = static_cast(command & 0xF0u); + switch (hi) + { + case 0x00u: + { + size_t len = 4u; + if ((command & 0x01u) != 0u) + { + ++len; + } + if ((command & 0x02u) != 0u) + { + ++len; + } + if ((command & 0x04u) != 0u) + { + len += 2u; + } + return len; + } + case 0x10u: + return (command == 0x11u) ? 3u : 1u; + case 0x20u: + if (command == 0x22u || command == 0x23u || command == 0x24u || command == 0x25u) + { + return 3u; + } + if (command == 0x26u) + { + return 4u; + } + if (command == 0x20u) + { + return 5u; + } + if (command == 0x27u || command == 0x28u || command == 0x29u || command == 0x2Cu || command == 0x2Du) + { + return 8u; + } + return 2u; + case 0x40u: + if (command == 0x47u || command == 0x48u || command == 0x49u || command == 0x4Au || + command == 0x41u || command == 0x42u) + { + return 2u; + } + if (command == 0x4Bu) + { + return 3u; + } + if (command == 0x45u || command == 0x4Cu) + { + return 4u; + } + if (command == 0x44u) + { + return 6u; + } + if (command == 0x4Du || command == 0x4Eu) + { + return 3u; + } + if (command == 0x4Fu) + { + return 6u; + } + return 1u; + case 0x50u: + case 0x60u: + if (command == 0x51u || command == 0x52u || command == 0x53u || command == 0x54u) + { + return 8u; + } + return 2u; + default: + return 0u; + } + } + + void applySoundDriverCommandUnlocked(uint8_t *rdram, const uint8_t *cmd) + { + if (!rdram || !cmd || g_soundDriverRpcState.statusAddr == 0u) + { + return; + } + + const uint8_t op = cmd[0]; + switch (op) + { + case 0x20u: // SdrBgmReq + { + const uint32_t port = cmd[1] & 0x0Fu; + uint16_t midiInfo = 0u; + (void)readGuestU16(rdram, g_soundDriverRpcState.statusAddr + kSoundDriverMidiInfoOffset, midiInfo); + midiInfo = static_cast(midiInfo | static_cast(1u << port)); + (void)writeGuestU16(rdram, g_soundDriverRpcState.statusAddr + kSoundDriverMidiInfoOffset, midiInfo); + break; + } + case 0x21u: // SdrBgmStop + { + const uint32_t port = cmd[1] & 0x0Fu; + uint16_t midiInfo = 0u; + (void)readGuestU16(rdram, g_soundDriverRpcState.statusAddr + kSoundDriverMidiInfoOffset, midiInfo); + midiInfo = static_cast(midiInfo & ~static_cast(1u << port)); + (void)writeGuestU16(rdram, g_soundDriverRpcState.statusAddr + kSoundDriverMidiInfoOffset, midiInfo); + break; + } + case 0x28u: // SdrHDDataSet + { + const uint32_t port = cmd[1] & 0x0Fu; + const PS2SoundDriverCompatLayout &compat = g_soundDriverCompatLayout; + if (compat.primaryMidiCheckAddr != 0u || compat.fallbackMidiCheckAddr != 0u) + { + const int16_t checksum = + soundDriverCheckValue(rdram, compat.primaryMidiCheckAddr, compat.fallbackMidiCheckAddr, port, 4u); + (void)writeGuestS16(rdram, + g_soundDriverRpcState.statusAddr + kSoundDriverMidiSumOffset + (port * sizeof(int16_t)), + checksum); + } + break; + } + case 0x29u: // SdrHDDataSet2 + { + const uint32_t port = cmd[1] & 0x0Fu; + const PS2SoundDriverCompatLayout &compat = g_soundDriverCompatLayout; + if (compat.primarySeCheckAddr != 0u || compat.fallbackSeCheckAddr != 0u) + { + const int16_t checksum = + soundDriverCheckValue(rdram, compat.primarySeCheckAddr, compat.fallbackSeCheckAddr, port, 5u); + (void)writeGuestS16(rdram, + g_soundDriverRpcState.statusAddr + kSoundDriverSeSumOffset + (port * sizeof(int16_t)), + checksum); + } + break; + } + case 0x10u: // SdrSeAllStop + rpcZeroRdram(rdram, g_soundDriverRpcState.statusAddr + kSoundDriverSeInfoOffset, 6u * sizeof(uint16_t)); + break; + default: + break; + } + } + + void handleSoundDriverCommandBuffer(uint8_t *rdram, PS2Runtime *runtime, uint32_t sendBuf, uint32_t sendSize) + { + if (!rdram || !runtime || !sendBuf || sendSize == 0u) + { + return; + } + + std::lock_guard lock(g_rpc_mutex); + if (!checkSoundDriverMemoryUnlocked(rdram, runtime)) + { + return; + } + + const uint8_t *packet = getConstMemPtr(rdram, sendBuf); + if (!packet) + { + return; + } + + for (uint32_t offset = 0u; offset < sendSize;) + { + const uint8_t op = packet[offset]; + if (op == 0xFFu) + { + break; + } + + const size_t len = soundDriverCommandLength(op); + if (len == 0u || (offset + len) > sendSize) + { + break; + } + + applySoundDriverCommandUnlocked(rdram, packet + offset); + offset += static_cast(len); + } + } + + bool handleSoundDriverRpcServiceImpl(uint8_t *rdram, PS2Runtime *runtime, + uint32_t sid, uint32_t rpcNum, + uint32_t sendBuf, uint32_t sendSize, + uint32_t recvBuf, uint32_t recvSize, + uint32_t &resultPtr, + bool &signalNowaitCompletion) + { + resultPtr = 0u; + signalNowaitCompletion = false; + + if (!runtime || !rdram) + { + return false; + } + + if (sid == IOP_SID_SNDDRV_COMMAND && rpcNum == IOP_RPC_SNDDRV_SUBMIT) + { + handleSoundDriverCommandBuffer(rdram, runtime, sendBuf, sendSize); + return true; + } + + if (sid == IOP_SID_SNDDRV_STATE && + (rpcNum == IOP_RPC_SNDDRV_GET_STATUS_ADDR || rpcNum == IOP_RPC_SNDDRV_GET_ADDR_TABLE)) + { + uint32_t responseWord = 0u; + { + std::lock_guard lock(g_rpc_mutex); + if (!checkSoundDriverMemoryUnlocked(rdram, runtime)) + { + return false; + } + responseWord = + (rpcNum == IOP_RPC_SNDDRV_GET_STATUS_ADDR) ? g_soundDriverRpcState.statusAddr + : g_soundDriverRpcState.addrTableAddr; + } + + if (recvBuf && recvSize >= sizeof(uint32_t)) + { + (void)writeGuestU32(rdram, recvBuf, responseWord); + if (recvSize > sizeof(uint32_t)) + { + rpcZeroRdram(rdram, recvBuf + sizeof(uint32_t), recvSize - sizeof(uint32_t)); + } + resultPtr = recvBuf; + } + + signalNowaitCompletion = true; + return true; + } + + return false; + } + + } // namespace + + bool handleSoundDriverRpcService(uint8_t *rdram, PS2Runtime *runtime, + uint32_t sid, uint32_t rpcNum, + uint32_t sendBuf, uint32_t sendSize, + uint32_t recvBuf, uint32_t recvSize, + uint32_t &resultPtr, + bool &signalNowaitCompletion) + { + return handleSoundDriverRpcServiceImpl(rdram, runtime, + sid, rpcNum, + sendBuf, sendSize, + recvBuf, recvSize, + resultPtr, + signalNowaitCompletion); + } + + namespace + { + + bool signalRpcCompletionSema(uint32_t semaId) + { + if (semaId == 0u || semaId > 0xFFFFu) + { + return false; + } + + auto sema = lookupSemaInfo(static_cast(semaId)); + if (!sema) + { + return false; + } + + bool signaled = false; + { + std::lock_guard lock(sema->m); + if (!sema->deleted && sema->count < sema->maxCount) + { + sema->count++; + signaled = true; + } + } + + if (signaled) + { + sema->cv.notify_one(); + } + return signaled; + } + + const char *dtxUrpcCommandName(uint32_t command) + { + switch (command) + { + case 0u: + return "SJX_CREATE"; + case 1u: + return "SJX_DESTROY"; + case 2u: + return "SJX_RESET"; + case 3u: + return "SJX_REINIT"; + case 8u: + return "PS2RNA_CREATE"; + case 9u: + return "PS2RNA_DESTROY"; + case 10u: + return "PS2RNA_REINIT"; + case 32u: + return "SJRMT_RBF_CREATE"; + case 33u: + return "SJRMT_MEM_CREATE"; + case 34u: + return "SJRMT_UNI_CREATE"; + case 35u: + return "SJRMT_DESTROY"; + case 36u: + return "SJRMT_GET_UUID"; + case 37u: + return "SJRMT_RESET"; + case 38u: + return "SJRMT_GET_CHUNK"; + case 39u: + return "SJRMT_UNGET_CHUNK"; + case 40u: + return "SJRMT_PUT_CHUNK"; + case 41u: + return "SJRMT_GET_NUM_DATA"; + case 42u: + return "SJRMT_IS_GET_CHUNK"; + case 43u: + return "SJRMT_INIT"; + case 44u: + return "SJRMT_FINISH"; + default: + return "UNKNOWN"; + } + } + + const char *dtxStreamName(uint32_t streamId) + { + switch (streamId) + { + case 0u: + return "room"; + case 1u: + return "data"; + default: + return "other"; + } + } + + bool dtxMatchesTransfer(const DtxTransferState &state, uint32_t srcAddr, uint32_t dstAddr, uint32_t sizeBytes) + { + (void)dstAddr; + return state.eeWorkAddr != 0u && + state.wkSize >= 64u && + state.eeWorkAddr == srcAddr && + state.wkSize == sizeBytes; + } + + bool dtxCopyBytes(uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t len) + { + for (uint32_t i = 0; i < len; ++i) + { + const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); + uint8_t *dst = getMemPtr(rdram, dstAddr + i); + if (!src || !dst) + { + return false; + } + *dst = *src; + } + return true; + } + + void dtxAppendToSjrmtData(uint8_t *rdram, DtxSjrmtState &state, uint32_t srcDataAddr, uint32_t requestedLen) + { + if (!rdram) + { + return; + } + + const uint32_t cap = (state.wkSize == 0u) ? 0x4000u : state.wkSize; + if (cap == 0u || state.wkAddr == 0u || state.roomBytes == 0u || requestedLen == 0u) + { + return; + } + + const uint32_t requestedCopyLen = std::min(requestedLen, state.roomBytes); + uint32_t copiedLen = 0u; + for (uint32_t i = 0; i < requestedCopyLen; ++i) + { + const uint32_t writeOffset = (state.writePos + i) % cap; + if (!dtxCopyBytes(rdram, state.wkAddr + writeOffset, srcDataAddr + i, 1u)) + { + break; + } + ++copiedLen; + } + + state.writePos = (state.writePos + copiedLen) % cap; + state.roomBytes -= copiedLen; + state.dataBytes = std::min(cap, state.dataBytes + copiedLen); + } + + void dtxConsumeSjrmtDataLocked(DtxSjrmtState &state) + { + const uint32_t cap = (state.wkSize == 0u) ? 0x4000u : state.wkSize; + if (cap == 0u || state.dataBytes == 0u) + { + return; + } + + const uint32_t consumed = std::min(cap, state.dataBytes); + state.readPos = (state.readPos + consumed) % cap; + state.dataBytes -= consumed; + state.roomBytes = std::min(cap, state.roomBytes + consumed); + } + + void dtxConsumeActivePs2RnaStreamsLocked() + { + for (const auto &entry : g_dtx_ps2rna_by_handle) + { + const DtxPs2RnaState &rna = entry.second; + if (!rna.playEnabled) + { + continue; + } + + auto consumeHandle = [&](uint32_t sjHandle) + { + if (sjHandle == 0u) + { + return; + } + + auto it = g_dtx_sjrmt_by_handle.find(sjHandle); + if (it == g_dtx_sjrmt_by_handle.end()) + { + return; + } + + dtxConsumeSjrmtDataLocked(it->second); + }; + + consumeHandle(rna.sjHandle0); + consumeHandle(rna.sjHandle1); + } + } + + void dtxApplySjxPayload(uint8_t *rdram, const DtxTransferState &transfer) + { + if (!rdram || transfer.eeWorkAddr == 0u || transfer.wkSize < 64u) + { + return; + } + + uint32_t commandCount = 0u; + if (!readGuestU32(rdram, transfer.eeWorkAddr, commandCount)) + { + return; + } + + commandCount = std::min(commandCount, 128u); + if (commandCount == 0u) + { + return; + } + + constexpr uint32_t kSjxHeaderSize = 16u; + constexpr uint32_t kSjxCommandSize = 16u; + + std::lock_guard lock(g_dtx_rpc_mutex); + for (uint32_t i = 0; i < commandCount; ++i) + { + const uint32_t cmdAddr = transfer.eeWorkAddr + kSjxHeaderSize + (i * kSjxCommandSize); + uint8_t *cmdPtr = getMemPtr(rdram, cmdAddr); + if (!cmdPtr) + { + break; + } + + const uint8_t cmdNo = cmdPtr[0]; + const uint8_t cmdLine = cmdPtr[1]; + uint16_t cmdXid = 0u; + uint32_t sjxHandle = 0u; + uint32_t chunkDataAddr = 0u; + uint32_t chunkLen = 0u; + std::memcpy(&cmdXid, cmdPtr + 2u, sizeof(cmdXid)); + std::memcpy(&sjxHandle, cmdPtr + 4u, sizeof(sjxHandle)); + std::memcpy(&chunkDataAddr, cmdPtr + 8u, sizeof(chunkDataAddr)); + std::memcpy(&chunkLen, cmdPtr + 12u, sizeof(chunkLen)); + + if (cmdNo != 0u || chunkLen == 0u) + { + continue; + } + + auto sjxIt = g_dtx_sjx_by_handle.find(sjxHandle); + if (sjxIt == g_dtx_sjx_by_handle.end()) + { + continue; + } + + DtxSjxState &sjx = sjxIt->second; + if (sjx.xid != 0u && sjx.xid != cmdXid) + { + continue; + } + + auto sjrmtIt = g_dtx_sjrmt_by_handle.find(sjx.dstSjHandle); + if (sjrmtIt == g_dtx_sjrmt_by_handle.end()) + { + continue; + } + + if (cmdLine == sjx.line) + { + dtxAppendToSjrmtData(rdram, sjrmtIt->second, chunkDataAddr, chunkLen); + + // The IOP side consumes data from the remote SJ stream and returns the + // chunk to the EE-side room queue. Rewriting the ack packet to line 0 + // makes the EE sjx_rcvcbf recycle the chunk instead of requeueing it + // as playable data forever. + cmdPtr[1] = 0u; + } + } + + dtxConsumeActivePs2RnaStreamsLocked(); + } + + void dtxApplyPs2RnaPayload(uint8_t *rdram, const DtxTransferState &transfer) + { + if (!rdram || transfer.eeWorkAddr == 0u || transfer.wkSize < 64u) + { + return; + } + + uint32_t commandCount = 0u; + if (!readGuestU32(rdram, transfer.eeWorkAddr, commandCount)) + { + return; + } + + commandCount = std::min(commandCount, 128u); + if (commandCount == 0u) + { + return; + } + + constexpr uint32_t kPs2RnaHeaderSize = 16u; + constexpr uint32_t kPs2RnaCommandSize = 16u; + + std::lock_guard lock(g_dtx_rpc_mutex); + for (uint32_t i = 0; i < commandCount; ++i) + { + const uint32_t cmdAddr = transfer.eeWorkAddr + kPs2RnaHeaderSize + (i * kPs2RnaCommandSize); + const uint8_t *cmdPtr = getConstMemPtr(rdram, cmdAddr); + if (!cmdPtr) + { + break; + } + + uint16_t cmdNo = 0u; + uint32_t rnaHandle = 0u; + uint32_t arg1 = 0u; + uint32_t arg2 = 0u; + std::memcpy(&cmdNo, cmdPtr + 0u, sizeof(cmdNo)); + std::memcpy(&rnaHandle, cmdPtr + 4u, sizeof(rnaHandle)); + std::memcpy(&arg1, cmdPtr + 8u, sizeof(arg1)); + std::memcpy(&arg2, cmdPtr + 12u, sizeof(arg2)); + + auto it = g_dtx_ps2rna_by_handle.find(rnaHandle); + if (it == g_dtx_ps2rna_by_handle.end()) + { + continue; + } + + DtxPs2RnaState &state = it->second; + switch (cmdNo) + { + case 0u: // IOPRNA_CMD_START + state.playEnabled = true; + break; + case 1u: // IOPRNA_CMD_STOP + state.playEnabled = false; + break; + case 2u: // IOPRNA_CMD_SETPSW + state.playEnabled = (arg1 != 0u); + break; + case 3u: // IOPRNA_CMD_SETNCH + state.channelCount = arg1; + break; + case 4u: // IOPRNA_CMD_SETSFREQ + state.sampleFreq = arg1; + break; + case 5u: // IOPRNA_CMD_SETVOL + state.volume = arg2; + break; + default: + break; + } + } + + dtxConsumeActivePs2RnaStreamsLocked(); + } + + } // namespace + + void noteDtxSifDmaTransfer(uint8_t *rdram, uint32_t srcAddr, uint32_t dstAddr, uint32_t sizeBytes) + { + if (!rdram || sizeBytes < 64u) + { + return; + } + + uint32_t normalizedSrc = 0u; + uint32_t normalizedDst = 0u; + if (!normalizeGuestLinearAddr(srcAddr, normalizedSrc) || + !normalizeGuestLinearAddr(dstAddr, normalizedDst)) + { + return; + } + + DtxTransferState matched{}; + bool found = false; + { + std::lock_guard lock(g_dtx_rpc_mutex); + if (!g_dtxCompatLayout.isConfigured()) + { + return; + } + for (const auto &entry : g_dtx_transfer_by_id) + { + if (dtxMatchesTransfer(entry.second, normalizedSrc, normalizedDst, sizeBytes)) + { + matched = entry.second; + found = true; + break; + } + } + } + + if (!found) + { + static uint32_t dtxMissLogCount = 0u; + if (dtxMissLogCount < 64u) + { + uint32_t knownTransfers = 0u; + uint32_t sampleDtxId = 0u; + uint32_t sampleSrc = 0u; + uint32_t sampleSize = 0u; + { + std::lock_guard lock(g_dtx_rpc_mutex); + knownTransfers = static_cast(g_dtx_transfer_by_id.size()); + if (!g_dtx_transfer_by_id.empty()) + { + const auto &sample = *g_dtx_transfer_by_id.begin(); + sampleDtxId = sample.second.dtxId; + sampleSrc = sample.second.eeWorkAddr; + sampleSize = sample.second.wkSize; + } + } + + if (knownTransfers != 0u) + { + RUNTIME_LOG("[sceSifSetDma:DTX_MISS] src=0x" << std::hex << normalizedSrc + << " dst=0x" << normalizedDst + << " size=0x" << sizeBytes + << " known=" << std::dec << knownTransfers + << " sampleDtxId=0x" << std::hex << sampleDtxId + << " sampleSrc=0x" << sampleSrc + << " sampleSize=0x" << sampleSize + << std::dec << std::endl); + ++dtxMissLogCount; + } + } + return; + } + + const uint32_t footerTicketAddr = matched.eeWorkAddr + matched.wkSize - sizeof(uint32_t); + uint32_t ticketNo = 0u; + if (!readGuestU32(rdram, footerTicketAddr, ticketNo)) + { + return; + } + + (void)writeGuestU32(rdram, footerTicketAddr, ticketNo + 1u); + + if (matched.dtxId == 0u) + { + dtxApplySjxPayload(rdram, matched); + } + else if (matched.dtxId == 1u) + { + dtxApplyPs2RnaPayload(rdram, matched); + } + + static uint32_t dtxAckLogCount = 0u; + if (dtxAckLogCount < 32u) + { + RUNTIME_LOG("[sceSifSetDma:DTX_ACK] dtxId=0x" << std::hex << matched.dtxId + << " ee=0x" << matched.eeWorkAddr + << " iop=0x" << matched.iopWorkAddr + << " size=0x" << matched.wkSize + << " ticket=0x" << ticketNo + << "->0x" << (ticketNo + 1u)); + if (matched.dtxId == 0u) + { + uint32_t totalData = 0u; + std::lock_guard lock(g_dtx_rpc_mutex); + for (const auto &entry : g_dtx_sjrmt_by_handle) + { + totalData += entry.second.dataBytes; + } + RUNTIME_LOG(" sjrmtData=0x" << totalData); + } + RUNTIME_LOG(std::dec << std::endl); + ++dtxAckLogCount; + } + } + + void resetSoundDriverRpcState() + { + std::lock_guard lock(g_rpc_mutex); + resetSoundDriverRpcStateUnlocked(); + } + + void setSoundDriverCompatLayout(const PS2SoundDriverCompatLayout &layout) + { + std::lock_guard lock(g_rpc_mutex); + g_soundDriverCompatLayout = layout; + } + + void clearSoundDriverCompatLayout() + { + std::lock_guard lock(g_rpc_mutex); + g_soundDriverCompatLayout = {}; + } + + void setDtxCompatLayout(const PS2DtxCompatLayout &layout) + { + std::scoped_lock lock(g_rpc_mutex, g_dtx_rpc_mutex); + g_dtxCompatLayout = layout; + resetDtxRpcStateUnlocked(); + } + + void clearDtxCompatLayout() + { + std::scoped_lock lock(g_rpc_mutex, g_dtx_rpc_mutex); + g_dtxCompatLayout = {}; + resetDtxRpcStateUnlocked(); + } + + void prepareSoundDriverStatusTransfer(uint8_t *rdram, uint32_t srcAddr, uint32_t size) + { + std::lock_guard lock(g_rpc_mutex); + if (srcAddr != g_soundDriverRpcState.statusAddr || size != kSoundDriverStatusSize) + { + return; + } + + backfillSoundDriverStatusFromCompatUnlocked(rdram); + } + + void finalizeSoundDriverStatusTransfer(uint8_t *rdram, uint32_t srcAddr, uint32_t dstAddr, uint32_t size) + { + (void)rdram; + (void)srcAddr; + (void)dstAddr; + (void)size; + } + + void SifStopModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 + const std::string modulePath = readGuestCStringBounded(rdram, pathAddr, kMaxSifModulePathBytes); + if (modulePath.empty()) + { + setReturnS32(ctx, -1); + return; + } + + 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::scoped_lock lock(g_rpc_mutex, g_dtx_rpc_mutex); + if (runtime) + { + runtime->iop().init(rdram); + } + 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; + resetDtxRpcStateUnlocked(); + g_soundDriverRpcState.initialized = false; + g_rpc_initialized = true; + RUNTIME_LOG("[SifInitRpc] Initialized"); + } + else + { + resetDtxRpcStateUnlocked(); + g_soundDriverRpcState.initialized = false; + } + setReturnS32(ctx, 0); + } + + void SifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t clientPtr = getRegU32(ctx, 4); + uint32_t rpcId = getRegU32(ctx, 5); + uint32_t mode = getRegU32(ctx, 6); + + t_SifRpcClientData *client = reinterpret_cast(getMemPtr(rdram, clientPtr)); + + if (!client) + { + setReturnS32(ctx, -1); + return; + } + + 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::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); + } + + void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + std::lock_guard rpcCallLock(g_sif_call_rpc_mutex); + + uint32_t clientPtr = getRegU32(ctx, 4); + 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; + + // Decode both extended-reg convention (EE default) and standard O32 stack convention, + // picking REG whenever plausible, to avoid zero-collision on the stack. + uint32_t sp = getRegU32(ctx, 29); + + uint32_t sendSizeReg = getRegU32(ctx, 8); + uint32_t recvBufReg = getRegU32(ctx, 9); + uint32_t recvSizeReg = getRegU32(ctx, 10); + uint32_t endFuncReg = getRegU32(ctx, 11); + uint32_t endParamReg = 0; + (void)readStackU32(rdram, sp, 0x0, endParamReg); + + uint32_t sendSizeStk = 0; + uint32_t recvBufStk = 0; + uint32_t recvSizeStk = 0; + uint32_t endFuncStk = 0; + uint32_t endParamStk = 0; + (void)readStackU32(rdram, sp, 0x10, sendSizeStk); + (void)readStackU32(rdram, sp, 0x14, recvBufStk); + (void)readStackU32(rdram, sp, 0x18, recvSizeStk); + (void)readStackU32(rdram, sp, 0x1C, endFuncStk); + (void)readStackU32(rdram, sp, 0x20, endParamStk); + + auto looksLikeGuestPtr = [&](uint32_t v) -> bool + { + if (v == 0) + return true; + const uint32_t norm = v & 0x1FFFFFFFu; + return norm >= 0x10000u && norm < PS2_RAM_SIZE; + }; + + auto looksLikeSize = [&](uint32_t v) -> bool + { + return v <= 0x2000000u; + }; + + auto looksLikeFunc = [&](uint32_t v) -> bool + { + return v == 0 || looksLikeGuestPtr(v); + }; + + auto plausiblePack = [&](uint32_t sendSz, uint32_t rbuf, uint32_t rsz, uint32_t endFn) -> bool + { + return looksLikeSize(sendSz) && looksLikeGuestPtr(rbuf) && looksLikeSize(rsz) && looksLikeFunc(endFn); + }; + + const bool regPackPlausible = plausiblePack(sendSizeReg, recvBufReg, recvSizeReg, endFuncReg); + const bool stackPackPlausible = plausiblePack(sendSizeStk, recvBufStk, recvSizeStk, endFuncStk); + + uint32_t boundSidHint = 0u; + { + std::lock_guard lock(g_rpc_mutex); + auto it = g_rpc_clients.find(clientPtr); + if (it != g_rpc_clients.end()) + { + boundSidHint = it->second.sid; + } + } + PS2DtxCompatLayout dtxCompat{}; + { + std::lock_guard lock(g_dtx_rpc_mutex); + dtxCompat = g_dtxCompatLayout; + } + + auto looksLikeDtxCreatePack = [&](uint32_t sendSz, uint32_t rbuf, uint32_t rsz) -> bool + { + return rbuf != 0u && rsz >= 4u && rsz <= 0x40u && + sendSz >= 12u && sendSz <= 0x1000u; + }; + + const bool isDtxCreate34Call = dtxCompat.isConfigured() && + (boundSidHint == dtxCompat.rpcSid) && + (rpcNum == 0x422u); + const bool forceStackForDtxCreate34 = + isDtxCreate34Call && + stackPackPlausible && + looksLikeDtxCreatePack(sendSizeStk, recvBufStk, recvSizeStk) && + !looksLikeDtxCreatePack(sendSizeReg, recvBufReg, recvSizeReg); + + bool useRegConvention = true; + if (forceStackForDtxCreate34) + { + useRegConvention = false; + } + else if (!regPackPlausible && stackPackPlausible) + { + const bool regHasValidCallback = (endFuncReg != 0u) && looksLikeFunc(endFuncReg); + const bool stackHasValidCallback = (endFuncStk != 0u) && looksLikeFunc(endFuncStk); + if (!(regHasValidCallback && !stackHasValidCallback)) + { + useRegConvention = false; + } + } + + sendSize = useRegConvention ? sendSizeReg : sendSizeStk; + recvBuf = useRegConvention ? recvBufReg : recvBufStk; + recvSize = useRegConvention ? recvSizeReg : recvSizeStk; + endFunc = useRegConvention ? endFuncReg : endFuncStk; + endParam = useRegConvention ? endParamReg : endParamStk; + + const bool isDtxLikeRpc = dtxCompat.isConfigured() && + ((boundSidHint == dtxCompat.rpcSid) || ((rpcNum & 0xFF00u) == 0x0400u)); + static uint32_t dtxAbiLogCount = 0u; + if (isDtxLikeRpc && dtxAbiLogCount < 96u) + { + RUNTIME_LOG("[SifCallRpc:ABI] client=0x" << std::hex << clientPtr + << " rpc=0x" << rpcNum + << " sidHint=0x" << boundSidHint + << " useReg=" << (useRegConvention ? 1 : 0) + << " reg=(" << sendSizeReg << "," << recvBufReg << "," << recvSizeReg << "," << endFuncReg << "," << endParamReg << ")" + << " stk=(" << sendSizeStk << "," << recvBufStk << "," << recvSizeStk << "," << endFuncStk << "," << endParamStk << ")" + << " plausible=(" << (regPackPlausible ? 1 : 0) << "," << (stackPackPlausible ? 1 : 0) << ")" + << " force34=" << (forceStackForDtxCreate34 ? 1 : 0) + << std::dec << std::endl); + ++dtxAbiLogCount; + } + + 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; + }; + + if (!handled && runtime) + { + runtime->iop().init(rdram); + uint32_t iopResultPtr = 0u; + bool iopSignalNowaitCompletion = false; + if (runtime->iop().handleRPC(runtime, + sid, rpcNum, + sendBuf, sendSize, + recvBuf, recvSize, + iopResultPtr, + iopSignalNowaitCompletion)) + { + handled = true; + resultPtr = iopResultPtr; + if (iopSignalNowaitCompletion && (mode & kSifRpcModeNowait) != 0u) + { + uint32_t semaId = static_cast(client->hdr.sema_id); + if (semaId == 0xFFFFFFFFu || semaId == 0u) + { + semaId = endParam; + } + (void)signalRpcCompletionSema(semaId); + } + } + } + + const bool isDtxUrpc = dtxCompat.isUrpcRpc(sid, rpcNum); + 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 (dtxCompat.hasUrpcTables() && dtxUrpcCommand < 64u) + { + (void)readRpcU32(dtxCompat.urpcFnTableBase + (dtxUrpcCommand * 4u), dtxUrpcFn); + (void)readRpcU32(dtxCompat.urpcObjTableBase + (dtxUrpcCommand * 4u), dtxUrpcObj); + } + hasUrpcHandler = (dtxUrpcCommand < 64u) && (dtxUrpcFn != 0u); + } + const bool allowServerDispatch = !isDtxUrpc || hasUrpcHandler; + const bool isDtxSid = dtxCompat.isConfigured() && sid == dtxCompat.rpcSid; + + if (sd && sd->func && (!isDtxSid || 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 && dtxCompat.dispatcherFuncAddr != 0u) + { + dtxUrpcDispatchAttempted = true; + handled = rpcInvokeFunction(rdram, ctx, runtime, dtxCompat.dispatcherFuncAddr, rpcNum, sendBuf, sendSize, 0, &resultPtr); + if (handled && resultPtr == 0) + { + resultPtr = sendBuf; + } + } + } + + if (!handled && isDtxSid) + { + if (rpcNum == 2 && recvBuf && recvSize >= sizeof(uint32_t)) + { + uint32_t dtxId = 0; + uint32_t eeWorkAddr = 0u; + uint32_t iopWorkAddr = 0u; + uint32_t wkSize = 0u; + if (sendBuf && sendSize >= sizeof(uint32_t)) + { + (void)readRpcU32(sendBuf + 0u, dtxId); + } + if (sendBuf && sendSize >= (4u * sizeof(uint32_t))) + { + (void)readRpcU32(sendBuf + 4u, eeWorkAddr); + (void)readRpcU32(sendBuf + 8u, iopWorkAddr); + (void)readRpcU32(sendBuf + 12u, wkSize); + } + + uint32_t normalizedEeWorkAddr = 0u; + uint32_t normalizedIopWorkAddr = 0u; + (void)normalizeGuestLinearAddr(eeWorkAddr, normalizedEeWorkAddr); + (void)normalizeGuestLinearAddr(iopWorkAddr, normalizedIopWorkAddr); + + 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; + } + + DtxTransferState state{}; + state.dtxId = dtxId; + state.remoteHandle = remoteHandle; + state.eeWorkAddr = normalizedEeWorkAddr; + state.iopWorkAddr = normalizedIopWorkAddr; + state.wkSize = wkSize; + g_dtx_transfer_by_id[dtxId] = state; + } + + (void)writeRpcU32(recvBuf, remoteHandle); + if (recvSize > sizeof(uint32_t)) + { + rpcZeroRdram(rdram, recvBuf + sizeof(uint32_t), recvSize - sizeof(uint32_t)); + } + static uint32_t dtxCreateLogCount = 0; + if (dtxCreateLogCount < 64u) + { + RUNTIME_LOG("[SifCallRpc:DTX_CREATE] dtxId=0x" << std::hex << dtxId + << " remote=0x" << remoteHandle + << " recvBuf=0x" << recvBuf + << " recvSize=0x" << recvSize + << std::dec << std::endl); + ++dtxCreateLogCount; + } + 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_transfer_by_id.erase(it->first); + 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 0u: // SJX_CREATE + { + uint32_t srcSjHandle = 0u; + uint32_t dstSjHandle = 0u; + uint32_t line = 0u; + uint32_t eeObjAddr = 0u; + (void)readSendWord(0u, srcSjHandle); + (void)readSendWord(1u, dstSjHandle); + (void)readSendWord(2u, line); + (void)readSendWord(3u, eeObjAddr); + + std::lock_guard lock(g_dtx_rpc_mutex); + const uint32_t handle = dtxAllocUrpcHandleLocked(); + DtxSjxState state{}; + state.handle = handle; + state.srcSjHandle = srcSjHandle; + state.dstSjHandle = dstSjHandle; + state.line = line; + state.eeObjAddr = eeObjAddr; + g_dtx_sjx_by_handle[handle] = state; + outWords[0] = handle ? handle : 1u; + outWordCount = 1u; + break; + } + case 1u: // SJX_DESTROY + { + uint32_t handle = 0u; + (void)readSendWord(0u, handle); + std::lock_guard lock(g_dtx_rpc_mutex); + g_dtx_sjx_by_handle.erase(handle); + outWords[0] = 1u; + outWordCount = 1u; + break; + } + case 2u: // SJX_RESET + { + uint32_t handle = 0u; + uint32_t xid = 0u; + (void)readSendWord(0u, handle); + (void)readSendWord(1u, xid); + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjx_by_handle.find(handle); + if (it != g_dtx_sjx_by_handle.end()) + { + it->second.xid = static_cast(xid & 0xFFFFu); + } + outWords[0] = 1u; + outWordCount = 1u; + break; + } + case 8u: // PS2RNA_CREATE + { + uint32_t maxChannels = 0u; + uint32_t sjHandle0 = 0u; + uint32_t sjHandle1 = 0u; + (void)readSendWord(0u, maxChannels); + (void)readSendWord(2u, sjHandle0); + (void)readSendWord(3u, sjHandle1); + + std::lock_guard lock(g_dtx_rpc_mutex); + const uint32_t handle = dtxAllocUrpcHandleLocked(); + DtxPs2RnaState state{}; + state.handle = handle; + state.maxChannels = maxChannels; + state.sjHandle0 = sjHandle0; + state.sjHandle1 = sjHandle1; + state.channelCount = maxChannels; + g_dtx_ps2rna_by_handle[handle] = state; + outWords[0] = handle ? handle : 1u; + outWordCount = 1u; + break; + } + case 9u: // PS2RNA_DESTROY + { + uint32_t handle = 0u; + (void)readSendWord(0u, handle); + std::lock_guard lock(g_dtx_rpc_mutex); + g_dtx_ps2rna_by_handle.erase(handle); + outWords[0] = 1u; + outWordCount = 1u; + break; + } + 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 && resultPtr != recvBuf) + { + rpcCopyToRdram(rdram, recvBuf, resultPtr, recvSize); + } + else if (!handled && sendBuf && sendSize > 0 && sendBuf != recvBuf) + { + 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); + } + RUNTIME_LOG("[SifCallRpc:DTX] rpcNum=0x" << std::hex << rpcNum + << " cmd=0x" << dtxUrpcCommand + << " name=" << dtxUrpcCommandName(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); + + if (dtxUrpcCommand >= 37u && dtxUrpcCommand <= 42u) + { + std::lock_guard lock(g_dtx_rpc_mutex); + auto it = g_dtx_sjrmt_by_handle.find(dtxUrpcSend0); + if (it != g_dtx_sjrmt_by_handle.end()) + { + const DtxSjrmtState &state = it->second; + RUNTIME_LOG("[SifCallRpc:DTX_SJRMT] handle=0x" << std::hex << dtxUrpcSend0 + << " cmd=" << dtxUrpcCommandName(dtxUrpcCommand) + << " wk=0x" << state.wkAddr + << " size=0x" << state.wkSize + << " read=0x" << state.readPos + << " write=0x" << state.writePos + << std::dec + << " room=" << state.roomBytes + << " data=" << state.dataBytes); + if ((dtxUrpcCommand == 38u || dtxUrpcCommand == 42u) && sendBuf && sendSize >= 12u) + { + uint32_t streamId = 0u; + uint32_t nbyte = 0u; + (void)readRpcU32(sendBuf + 4u, streamId); + (void)readRpcU32(sendBuf + 8u, nbyte); + RUNTIME_LOG(" stream=" << dtxStreamName(streamId) + << " req=" << nbyte); + } + RUNTIME_LOG(std::endl); + } + } + ++dtxUrpcLogCount; + } + } + + if (endFunc) + { + bool callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, endFunc, endParam, 0, 0, 0, nullptr); + + if (!callbackInvoked && endFunc >= 0x10000u) + { + const uint32_t normalizedEndFunc = endFunc - 0x10000u; + if (runtime->hasFunction(normalizedEndFunc)) + { + callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, normalizedEndFunc, endParam, 0, 0, 0, nullptr); + } + } + + PS2SoundDriverCompatLayout soundCompat{}; + { + std::lock_guard lock(g_rpc_mutex); + soundCompat = g_soundDriverCompatLayout; + } + + if (soundCompat.matchesCompletionCallback(endFunc)) + { + uint32_t semaId = static_cast(client->hdr.sema_id); + if (semaId == 0xFFFFFFFFu || semaId == 0u) + { + semaId = endParam; + } + (void)signalRpcCompletionSema(semaId); + if (rdram && soundCompat.busyFlagAddr != 0u && soundCompat.matchesClearBusyCallback(endFunc)) + { + if (uint32_t *busy = reinterpret_cast(getMemPtr(rdram, soundCompat.busyFlagAddr))) + { + *busy = 0u; + } + } + } + + if (!callbackInvoked) + { + uint32_t semaId = static_cast(client->hdr.sema_id); + if (semaId == 0xFFFFFFFFu || semaId == 0u) + { + semaId = endParam; + } + const bool fallbackSignaledSema = signalRpcCompletionSema(semaId); + + static uint32_t unresolvedEndFuncWarnCount = 0; + if (unresolvedEndFuncWarnCount < 32u) + { + std::cerr << "[SifCallRpc] unresolved end callback endFunc=0x" << std::hex << endFunc + << " semaId=0x" << semaId + << " fallbackSignal=" << std::dec << (fallbackSignaledSema ? 1 : 0) + << std::endl; + ++unresolvedEndFuncWarnCount; + } + } + } + + static int logCount = 0; + if (logCount < 10) + { + RUNTIME_LOG("[SifCallRpc] client=0x" << std::hex << clientPtr + << " sid=0x" << sid + << " rpcNum=0x" << rpcNum + << " mode=0x" << mode + << " 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; + + { + std::lock_guard lock(g_rpc_mutex); + + 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; + } + } + } + } + + 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; + } + } + } + } + + RUNTIME_LOG("[SifRegisterRpc] sid=0x" << std::hex << sid << " sd=0x" << sdPtr << std::dec); + setReturnS32(ctx, 0); + } + + void SifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + 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) + { + 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; + } + + std::lock_guard lock(g_rpc_mutex); + + 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) + { + SifCallRpc(rdram, ctx, runtime); + } + + 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) + { + RUNTIME_LOG("[sceSifSendCmd] cid=0x" << std::hex << cid + << " packet=0x" << packetAddr + << " psize=0x" << packetSize + << " extra=0x" << destExtra << std::dec << std::endl); + ++logCount; + } + + // Return non-zero on success. + setReturnS32(ctx, 1); + } + + void sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t queuePtr = getRegU32(ctx, 4); + setReturnS32(ctx, static_cast(queuePtr)); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/RPC.h b/ps2xRuntime/src/lib/Kernel/Syscalls/RPC.h new file mode 100644 index 00000000..a968a754 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/RPC.h @@ -0,0 +1,29 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + void SifStopModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void noteDtxSifDmaTransfer(uint8_t *rdram, uint32_t srcAddr, uint32_t dstAddr, uint32_t sizeBytes); + bool handleSoundDriverRpcService(uint8_t *rdram, PS2Runtime *runtime, + uint32_t sid, uint32_t rpcNum, + uint32_t sendBuf, uint32_t sendSize, + uint32_t recvBuf, uint32_t recvSize, + uint32_t &resultPtr, + bool &signalNowaitCompletion); + void prepareSoundDriverStatusTransfer(uint8_t *rdram, uint32_t srcAddr, uint32_t size); + void finalizeSoundDriverStatusTransfer(uint8_t *rdram, uint32_t srcAddr, uint32_t dstAddr, uint32_t size); + void sceSifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Sync.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/Sync.cpp new file mode 100644 index 00000000..58b2414a --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Sync.cpp @@ -0,0 +1,923 @@ +#include "Common.h" +#include "Sync.h" + +namespace ps2_syscalls +{ + static bool looksLikeGuestPointerOrNull(uint32_t value) + { + if (value == 0u) + { + return true; + } + const uint32_t normalized = value & 0x1FFFFFFFu; + return normalized < PS2_RAM_SIZE; + } + + static bool readGuestU32Safe(const uint8_t *rdram, uint32_t addr, uint32_t &out) + { + const uint8_t *b0 = getConstMemPtr(rdram, addr + 0u); + const uint8_t *b1 = getConstMemPtr(rdram, addr + 1u); + const uint8_t *b2 = getConstMemPtr(rdram, addr + 2u); + const uint8_t *b3 = getConstMemPtr(rdram, addr + 3u); + if (!b0 || !b1 || !b2 || !b3) + { + out = 0u; + return false; + } + + out = static_cast(*b0) | + (static_cast(*b1) << 8) | + (static_cast(*b2) << 16) | + (static_cast(*b3) << 24); + return true; + } + + struct DecodedSemaParams + { + int init = 0; + int max = 1; + uint32_t attr = 0; + uint32_t option = 0; + }; + + static DecodedSemaParams decodeCreateSemaParams(const uint32_t *param, uint32_t availableWords) + { + DecodedSemaParams out{}; + if (!param || availableWords == 0u) + { + return out; + } + + // EE layout (kernel.h): + // [0]=count [1]=max_count [2]=init_count [3]=wait_threads [4]=attr [5]=option + const bool hasEeLayout = availableWords >= 3u; + const int eeMax = hasEeLayout ? static_cast(param[1]) : 1; + const int eeInit = hasEeLayout ? static_cast(param[2]) : 0; + const uint32_t eeAttr = (availableWords >= 5u) ? param[4] : 0u; + const uint32_t eeOption = (availableWords >= 6u) ? param[5] : 0u; + + // Legacy layout (IOP-style): + // [0]=attr [1]=option [2]=init [3]=max + const bool hasLegacyLayout = availableWords >= 4u; + const int legacyMax = hasLegacyLayout ? static_cast(param[3]) : 1; + const int legacyInit = hasLegacyLayout ? static_cast(param[2]) : 0; + const uint32_t legacyAttr = hasLegacyLayout ? param[0] : 0u; + const uint32_t legacyOption = hasLegacyLayout ? param[1] : 0u; + + auto countLooksPlausible = [](int value) -> bool + { + return value > 0 && value <= 0x10000; + }; + + bool useLegacyLayout = hasLegacyLayout && !hasEeLayout; + if (hasLegacyLayout && hasEeLayout && countLooksPlausible(legacyMax) && !countLooksPlausible(eeMax)) + { + useLegacyLayout = true; + } + else if (hasLegacyLayout && hasEeLayout && countLooksPlausible(legacyMax) && countLooksPlausible(eeMax)) + { + // If both max values look valid, prefer the layout whose option field + // looks like a pointer/NULL payload. + const bool eeOptionLooksValid = looksLikeGuestPointerOrNull(eeOption); + const bool legacyOptionLooksValid = looksLikeGuestPointerOrNull(legacyOption); + if (!eeOptionLooksValid && legacyOptionLooksValid) + { + useLegacyLayout = true; + } + } + + if (useLegacyLayout && hasLegacyLayout) + { + out.max = legacyMax; + out.init = legacyInit; + out.attr = legacyAttr; + out.option = legacyOption; + } + else + { + if (!hasEeLayout) + { + return out; + } + out.max = eeMax; + out.init = eeInit; + out.attr = eeAttr; + out.option = eeOption; + } + + return out; + } + + void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 + if (paramAddr == 0u) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + uint32_t rawParams[6] = {}; + uint32_t availableWords = 0u; + for (uint32_t i = 0; i < 6u; ++i) + { + if (!readGuestU32Safe(rdram, paramAddr + (i * 4u), rawParams[i])) + { + break; + } + availableWords = i + 1u; + } + + if (availableWords < 3u) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + const DecodedSemaParams decoded = decodeCreateSemaParams(rawParams, availableWords); + int init = decoded.init; + int max = decoded.max; + uint32_t attr = decoded.attr; + uint32_t option = decoded.option; + + if (max <= 0) + { + max = 1; + } + if (init < 0) + { + init = 0; + } + if (init > max) + { + init = max; + } + + int id = 0; + auto info = std::make_shared(); + info->count = init; + info->maxCount = max; + info->initCount = init; + info->attr = attr; + info->option = option; + + { + std::lock_guard lock(g_sema_map_mutex); + for (int attempts = 0; attempts < 0x7FFF; ++attempts) + { + if (g_nextSemaId <= 0) + { + g_nextSemaId = 1; + } + + const int candidate = g_nextSemaId++; + if (candidate <= 0) + { + continue; + } + + if (g_semas.find(candidate) == g_semas.end()) + { + id = candidate; + break; + } + } + + if (id <= 0) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + g_semas.emplace(id, info); + } + RUNTIME_LOG("[CreateSema] id=" << id << " init=" << init << " max=" << max); + setReturnS32(ctx, id); + } + + void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int sid = static_cast(getRegU32(ctx, 4)); + std::shared_ptr sema; + + { + std::lock_guard lock(g_sema_map_mutex); + auto it = g_semas.find(sid); + if (it == g_semas.end()) + { + setReturnS32(ctx, KE_UNKNOWN_SEMID); + return; + } + sema = it->second; + g_semas.erase(it); + } + + { + std::lock_guard lock(sema->m); + sema->deleted = true; + } + sema->cv.notify_all(); + + setReturnS32(ctx, KE_OK); + } + + void iDeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + DeleteSema(rdram, ctx, runtime); + } + + void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int sid = static_cast(getRegU32(ctx, 4)); + auto sema = lookupSemaInfo(sid); + if (!sema) + { + setReturnS32(ctx, KE_UNKNOWN_SEMID); + return; + } + + int ret = KE_OK; + int beforeCount = 0; + int afterCount = 0; + { + std::lock_guard lock(sema->m); + beforeCount = sema->count; + if (sema->count >= sema->maxCount) + { + ret = KE_SEMA_OVF; + } + else + { + sema->count++; + sema->cv.notify_one(); + } + afterCount = sema->count; + } + + static std::atomic s_signalSemaLogs{0}; + const uint32_t sigLog = s_signalSemaLogs.fetch_add(1, std::memory_order_relaxed); + if (sigLog < 256u) + { + RUNTIME_LOG("[SignalSema] tid=" << g_currentThreadId + << " sid=" << sid + << " count=" << beforeCount << "->" << afterCount + << " ret=" << ret + << std::endl); + } + + setReturnS32(ctx, ret); + } + + void iSignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + SignalSema(rdram, ctx, runtime); + } + + void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int sid = static_cast(getRegU32(ctx, 4)); + auto sema = lookupSemaInfo(sid); + if (!sema) + { + 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) + { + static std::atomic s_waitSemaBlockLogs{0}; + const uint32_t blockLog = s_waitSemaBlockLogs.fetch_add(1, std::memory_order_relaxed); + if (blockLog < 256u) + { + RUNTIME_LOG("[WaitSema:block] tid=" << g_currentThreadId + << " sid=" << sid + << " pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << std::dec + << std::endl); + } + + if (info) + { + std::lock_guard tLock(info->m); + info->status = (info->suspendCount > 0) ? THS_WAITSUSPEND : THS_WAIT; + info->waitType = TSW_SEMA; + info->waitId = sid; + info->forceRelease = false; + } + + sema->waiters++; + { + PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); + sema->cv.wait(lock, [&]() + { + bool forced = info ? info->forceRelease.load() : false; + bool terminated = info ? info->terminated.load() : false; + return sema->count > 0 || sema->deleted || forced || terminated; // + }); + } + sema->waiters--; + if (sema->deleted) + { + ret = KE_WAIT_DELETE; + } + + if (info) + { + std::lock_guard tLock(info->m); + info->status = (info->suspendCount > 0) ? THS_SUSPEND : THS_RUN; + info->waitType = TSW_NONE; + info->waitId = 0; + if (info->forceRelease) + { + info->forceRelease = false; + ret = KE_RELEASE_WAIT; + } + } + + if (info && info->terminated.load()) + { + throw ThreadExitException(); + } + } + + if (ret == 0 && sema->count > 0) + { + sema->count--; + } + + static std::atomic s_waitSemaWakeLogs{0}; + const uint32_t wakeLog = s_waitSemaWakeLogs.fetch_add(1, std::memory_order_relaxed); + if (wakeLog < 256u) + { + RUNTIME_LOG("[WaitSema:wake] tid=" << g_currentThreadId + << " sid=" << sid + << " ret=" << ret + << " count=" << sema->count + << std::endl); + } + lock.unlock(); + waitWhileSuspended(info, runtime); + setReturnS32(ctx, ret); + } + + void PollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int sid = static_cast(getRegU32(ctx, 4)); + auto sema = lookupSemaInfo(sid); + if (!sema) + { + setReturnS32(ctx, KE_UNKNOWN_SEMID); + return; + } + + 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) + { + PollSema(rdram, ctx, runtime); + } + + 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, KE_UNKNOWN_SEMID); + return; + } + + ee_sema_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); + if (!status) + { + setReturnS32(ctx, KE_ERROR); + 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, KE_OK); + } + + void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + 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) + { + 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) + { + 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; + } + + uint32_t newBits = 0u; + { + std::lock_guard lock(info->m); + info->bits |= bits; + newBits = info->bits; + } + + static std::atomic s_setEventFlagLogs{0}; + const uint32_t setLog = s_setEventFlagLogs.fetch_add(1, std::memory_order_relaxed); + if (setLog < 256u) + { + RUNTIME_LOG("[SetEventFlag] tid=" << g_currentThreadId + << " eid=" << eid + << " bits=0x" << std::hex << bits + << " newBits=0x" << newBits + << std::dec << std::endl); + } + info->cv.notify_all(); + setReturnS32(ctx, 0); + } + + void iSetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + SetEventFlag(rdram, ctx, runtime); + } + + void ClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + ClearEventFlag(rdram, ctx, runtime); + } + + void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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()) + { + static std::atomic s_waitEventBlockLogs{0}; + const uint32_t evBlockLog = s_waitEventBlockLogs.fetch_add(1, std::memory_order_relaxed); + if (evBlockLog < 256u) + { + RUNTIME_LOG("[WaitEventFlag:block] tid=" << g_currentThreadId + << " eid=" << eid + << " waitBits=0x" << std::hex << waitBits + << " mode=0x" << mode + << " bits=0x" << info->bits + << " pc=0x" << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << std::dec + << std::endl); + } + + if (tInfo) + { + std::lock_guard tLock(tInfo->m); + tInfo->status = (tInfo->suspendCount > 0) ? THS_WAITSUSPEND : THS_WAIT; + tInfo->waitType = TSW_EVENT; + tInfo->waitId = eid; + tInfo->forceRelease = false; + } + + info->waiters++; + { + PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); + info->cv.wait(lock, satisfied); + } + info->waiters--; + + if (tInfo) + { + std::lock_guard tLock(tInfo->m); + tInfo->status = (tInfo->suspendCount > 0) ? THS_SUSPEND : 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) + { + if (resBitsPtr) + { + *resBitsPtr = info->bits; + } + + if (mode & WEF_CLEAR_ALL) + { + info->bits = 0; + } + else if (mode & WEF_CLEAR) + { + info->bits &= ~waitBits; + } + } + + static std::atomic s_waitEventWakeLogs{0}; + const uint32_t evWakeLog = s_waitEventWakeLogs.fetch_add(1, std::memory_order_relaxed); + if (evWakeLog < 256u) + { + RUNTIME_LOG("[WaitEventFlag:wake] tid=" << g_currentThreadId + << " eid=" << eid + << " ret=" << ret + << " bits=0x" << std::hex << info->bits + << std::dec + << std::endl); + } + + lock.unlock(); + waitWhileSuspended(tInfo, runtime); + setReturnS32(ctx, ret); + } + + void PollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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_ALL) + { + info->bits = 0; + } + else if (mode & WEF_CLEAR) + { + info->bits &= ~waitBits; + } + + setReturnS32(ctx, KE_OK); + } + + void iPollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + PollEventFlag(rdram, ctx, runtime); + } + + void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + ReferEventFlagStatus(rdram, ctx, runtime); + } + + void SetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + RUNTIME_LOG("[SetAlarm] ticks=" << ticks + << " handler=0x" << std::hex << handler + << " arg=0x" << arg << std::dec << std::endl); + ++logCount; + } + + if (!runtime || !handler || !runtime->hasFunction(handler)) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + 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 InitAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, 0); + } + + void iSetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + SetAlarm(rdram, ctx, runtime); + } + + void CancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + CancelAlarm(rdram, ctx, runtime); + } + + void ReleaseAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + CancelAlarm(rdram, ctx, runtime); + } + + void iReleaseAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + iCancelAlarm(rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Sync.h b/ps2xRuntime/src/lib/Kernel/Syscalls/Sync.h new file mode 100644 index 00000000..0d66da1b --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Sync.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iDeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iSignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void PollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iPollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void CreateEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DeleteEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iSetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void PollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iPollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void InitAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iSetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void CancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iCancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ReleaseAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iReleaseAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/System.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/System.cpp new file mode 100644 index 00000000..0752220b --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/System.cpp @@ -0,0 +1,959 @@ +#include "Common.h" +#include "System.h" + +namespace ps2_syscalls +{ + void GsSetCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int interlaced = getRegU32(ctx, 4); // $a0 - 0=non-interlaced, 1=interlaced + int videoMode = getRegU32(ctx, 5); // $a1 - 0=NTSC, 1=PAL, 2=VESA, 3=HiVision + int frameMode = getRegU32(ctx, 6); // $a2 - 0=field, 1=frame + + if (runtime) + { + auto &gs = runtime->memory().gs(); + const uint64_t smode2 = + (static_cast(interlaced) & 0x1ull) | + ((static_cast(frameMode) & 0x1ull) << 1); + + gs.smode2 = smode2; + + // Keep CRT1 enabled after the BIOS syscall selects a display mode. + if ((gs.pmode & 0x3ull) == 0ull) + { + gs.pmode |= 0x1ull; + } + } + + RUNTIME_LOG("PS2 GsSetCrt: interlaced=" << interlaced + << ", videoMode=" << videoMode + << ", frameMode=" << frameMode << std::endl); + + setReturnS32(ctx, 0); + } + + void SetGsCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + GsSetCrt(rdram, ctx, runtime); + } + + void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint64_t imr = 0; + if (runtime) + { + imr = runtime->memory().gs().imr; + } + + RUNTIME_LOG("PS2 GsGetIMR: Returning IMR=0x" << std::hex << imr << std::dec); + + setReturnU64(ctx, imr); // Return in $v0/$v1 + } + + void iGsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + GsGetIMR(rdram, ctx, runtime); + } + + void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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; + } + RUNTIME_LOG("PS2 GsPutIMR: Setting IMR=0x" << std::hex << newImr << std::dec); + setReturnU64(ctx, oldImr); + } + + void iGsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + GsPutIMR(rdram, ctx, runtime); + } + + void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int mode = getRegU32(ctx, 4); // $a0 - video mode (various flags) + + RUNTIME_LOG("PS2 GsSetVideoMode: mode=0x" << std::hex << mode << std::dec); + + // Do nothing for now. + } + + void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure + + if (!getMemPtr(rdram, paramAddr)) + { + std::cerr << "PS2 GetOsdConfigParam error: Invalid parameter address: 0x" + << std::hex << paramAddr << std::dec << std::endl; + setReturnS32(ctx, -1); + return; + } + + uint32_t *param = reinterpret_cast(getMemPtr(rdram, paramAddr)); + + ensureOsdConfigInitialized(); + uint32_t raw; + { + std::lock_guard lock(g_osd_mutex); + raw = g_osd_config_raw; + } + + *param = raw; + + setReturnS32(ctx, 0); + } + + void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure + + if (!getConstMemPtr(rdram, paramAddr)) + { + std::cerr << "PS2 SetOsdConfigParam error: Invalid parameter address: 0x" + << std::hex << paramAddr << std::dec << std::endl; + setReturnS32(ctx, -1); + return; + } + + 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); + } + + void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t bufAddr = getRegU32(ctx, 4); // $a0 + size_t bufSize = getRegU32(ctx, 5); // $a1 + char *hostBuf = reinterpret_cast(getMemPtr(rdram, bufAddr)); + const char *romName = "ROMVER 0100"; + + if (!hostBuf) + { + std::cerr << "GetRomName error: Invalid buffer address" << std::endl; + setReturnS32(ctx, -1); // Error + return; + } + if (bufSize == 0) + { + setReturnS32(ctx, 0); + return; + } + + strncpy(hostBuf, romName, bufSize - 1); + hostBuf[bufSize - 1] = '\0'; + + // returns the length of the string (excluding null?) or error + setReturnS32(ctx, (int32_t)strlen(hostBuf)); + } + + void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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* + + std::string secName = readGuestCStringBounded(rdram, secNameAddr, kLoadfileArgMaxBytes); + if (secName.empty()) + { + secName = "all"; + } + + 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) + { + // Use the same tracker as SifLoadModule so both APIs return the same module IDs. + SifLoadModule(rdram, ctx, runtime); + } + + void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + // a bit more detail mayber reomve old logic, lets get it more raw + std::cerr << "[Syscall TODO]" + << " encoded=0x" << std::hex << encodedSyscallId + << " v1=0x" << getRegU32(ctx, 3) + << " v0=0x" << getRegU32(ctx, 2) + << " a0=0x" << getRegU32(ctx, 4) + << " a1=0x" << getRegU32(ctx, 5) + << " a2=0x" << getRegU32(ctx, 6) + << " a3=0x" << getRegU32(ctx, 7) + << " pc=0x" << ctx->pc + << std::dec << std::endl; + + 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 + << ", 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; + + // Common syscalls: + // 0x04: Exit + // 0x06: LoadExecPS2 + // 0x07: ExecPS2 + if (syscallId == 0x04u) + { + std::cerr << " -> Syscall is Exit(), calling ExitThread stub." << std::endl; + ExitThread(rdram, ctx, runtime); + return; + } + + 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); + } + + static uint32_t computeBuiltinFindAddressResult(uint8_t *rdram, + uint32_t originalStart, + uint32_t originalEnd, + uint32_t target); + + bool dispatchSyscallOverride(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t handler = 0u; + { + std::lock_guard lock(g_syscall_override_mutex); + auto it = g_syscall_overrides.find(syscallNumber); + if (it == g_syscall_overrides.end()) + { + return false; + } + handler = it->second; + } + + if (!runtime || !ctx || handler == 0u) + { + return false; + } + + const uint32_t overrideA0 = getRegU32(ctx, 4); + const uint32_t overrideA1 = getRegU32(ctx, 5); + const uint32_t overrideA2 = getRegU32(ctx, 6); + const uint32_t overrideA3 = getRegU32(ctx, 7); + const uint32_t overridePc = ctx->pc; + const uint32_t overrideRa = getRegU32(ctx, 31); + + thread_local std::vector s_activeSyscallOverrides; + if (std::find(s_activeSyscallOverrides.begin(), s_activeSyscallOverrides.end(), syscallNumber) != s_activeSyscallOverrides.end()) + { + static std::atomic s_reentrantLogs{0u}; + constexpr uint32_t kMaxReentrantLogs = 32u; + const uint32_t logIndex = s_reentrantLogs.fetch_add(1u, std::memory_order_relaxed); + if (logIndex < kMaxReentrantLogs) + { + std::cerr << "[SyscallOverride:reentrant]" + << " syscall=0x" << std::hex << syscallNumber + << " handler=0x" << handler + << " pc=0x" << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << std::dec << std::endl; + } + return false; + } + + s_activeSyscallOverrides.push_back(syscallNumber); + struct ScopedActiveOverride + { + std::vector &active; + ~ScopedActiveOverride() + { + if (!active.empty()) + { + active.pop_back(); + } + } + } scopedActiveOverride{s_activeSyscallOverrides}; + + uint32_t retV0 = 0u; + const bool invoked = rpcInvokeFunction(rdram, + ctx, + runtime, + handler, + getRegU32(ctx, 4), + getRegU32(ctx, 5), + getRegU32(ctx, 6), + getRegU32(ctx, 7), + &retV0); + + if (syscallNumber == 0x83u) + { + const uint32_t builtinRet = computeBuiltinFindAddressResult(rdram, overrideA0, overrideA1, overrideA2); + const bool mismatch = (retV0 != builtinRet); + + static std::atomic s_findAddressOverrideLogs{0u}; + static std::atomic s_findAddressOverrideMismatchLogs{0u}; + constexpr uint32_t kMaxFindAddressOverrideLogs = 64u; + constexpr uint32_t kMaxFindAddressOverrideMismatchLogs = 128u; + + const uint32_t logIndex = s_findAddressOverrideLogs.fetch_add(1u, std::memory_order_relaxed); + const uint32_t mismatchIndex = mismatch + ? s_findAddressOverrideMismatchLogs.fetch_add(1u, std::memory_order_relaxed) + : 0u; + if (logIndex < kMaxFindAddressOverrideLogs || + (mismatch && mismatchIndex < kMaxFindAddressOverrideMismatchLogs)) + { + const uint32_t guestMinus20c = (retV0 != 0u) ? (retV0 - 0x20Cu) : 0u; + const uint32_t guestMinus168 = (retV0 != 0u) ? (retV0 - 0x168u) : 0u; + const uint32_t builtinMinus20c = (builtinRet != 0u) ? (builtinRet - 0x20Cu) : 0u; + const uint32_t builtinMinus168 = (builtinRet != 0u) ? (builtinRet - 0x168u) : 0u; + + std::cerr << "[Syscall83:override]" + << " handler=0x" << std::hex << handler + << " invoked=" << (invoked ? "true" : "false") + << " pc=0x" << overridePc + << " ra=0x" << overrideRa + << " a0=0x" << overrideA0 + << " a1=0x" << overrideA1 + << " a2=0x" << overrideA2 + << " a3=0x" << overrideA3 + << " guestRet=0x" << retV0 + << " builtinRet=0x" << builtinRet + << " guest-20c=0x" << guestMinus20c + << " builtin-20c=0x" << builtinMinus20c + << " guest-168=0x" << guestMinus168 + << " builtin-168=0x" << builtinMinus168 + << " match=" << (mismatch ? "false" : "true") + << std::dec << std::endl; + } + } + + if (!invoked) + { + static std::atomic s_fallbackLogs{0u}; + constexpr uint32_t kMaxFallbackLogs = 64u; + const uint32_t logIndex = s_fallbackLogs.fetch_add(1u, std::memory_order_relaxed); + if (logIndex < kMaxFallbackLogs) + { + std::cerr << "[SyscallOverride:fallback]" + << " syscall=0x" << std::hex << syscallNumber + << " handler=0x" << handler + << " pc=0x" << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << std::dec << std::endl; + } + return false; + } + + setReturnU32(ctx, retV0); + return true; + } + + static bool tryResolveGuestSyscallMirrorAddr(uint32_t syscallIndex, uint32_t &guestAddr) + { + const int64_t offsetBytes = + static_cast(static_cast(syscallIndex)) * static_cast(sizeof(uint32_t)); + const int64_t guestAddr64 = static_cast(kGuestSyscallTablePhysBase) + offsetBytes; + if (guestAddr64 < 0 || (guestAddr64 + static_cast(sizeof(uint32_t))) > static_cast(kGuestSyscallMirrorLimit)) + { + return false; + } + + guestAddr = static_cast(guestAddr64); + return true; + } + + static void writeGuestKernelWord(uint8_t *rdram, uint32_t guestAddr, uint32_t value) + { + if (!rdram) + { + return; + } + + if (uint8_t *ptr = getMemPtr(rdram, guestAddr)) + { + std::memcpy(ptr, &value, sizeof(value)); + } + } + + static void seedGuestSyscallTableProbeLocked(uint8_t *rdram) + { + writeGuestKernelWord(rdram, kGuestSyscallTableProbeBase + 0u, kGuestSyscallTableGuestBase >> 16); + writeGuestKernelWord(rdram, kGuestSyscallTableProbeBase + 8u, kGuestSyscallTableGuestBase & 0xFFFFu); + g_syscall_mirror_addrs.insert(kGuestSyscallTableProbeBase + 0u); + g_syscall_mirror_addrs.insert(kGuestSyscallTableProbeBase + 8u); + } + + static void mirrorGuestSyscallEntryLocked(uint8_t *rdram, uint32_t syscallIndex, uint32_t handler) + { + uint32_t guestAddr = 0u; + if (!tryResolveGuestSyscallMirrorAddr(syscallIndex, guestAddr)) + { + return; + } + + writeGuestKernelWord(rdram, guestAddr, handler); + if (handler == 0u) + { + g_syscall_mirror_addrs.erase(guestAddr); + return; + } + + g_syscall_mirror_addrs.insert(guestAddr); + } + + void initializeGuestKernelState(uint8_t *rdram) + { + if (!rdram) + { + return; + } + + std::lock_guard lock(g_syscall_override_mutex); + for (uint32_t guestAddr : g_syscall_mirror_addrs) + { + writeGuestKernelWord(rdram, guestAddr, 0u); + } + g_syscall_mirror_addrs.clear(); + + seedGuestSyscallTableProbeLocked(rdram); + + for (const auto &entry : g_syscall_overrides) + { + mirrorGuestSyscallEntryLocked(rdram, entry.first, entry.second); + } + } + + void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + const uint32_t syscallIndex = getRegU32(ctx, 4); + const uint32_t handler = getRegU32(ctx, 5); + + { + std::lock_guard lock(g_syscall_override_mutex); + if (handler == 0u) + { + g_syscall_overrides.erase(syscallIndex); + } + else + { + g_syscall_overrides[syscallIndex] = handler; + } + + mirrorGuestSyscallEntryLocked(rdram, syscallIndex, handler); + } + + setReturnS32(ctx, 0); + } + + // 0x3C SetupThread + // args: $a0 = gp, $a1 = stack, $a2 = stack_size, $a3 = args, $t0 = root_func + void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + const uint32_t gp = getRegU32(ctx, 4); + const uint32_t stack = getRegU32(ctx, 5); + const int32_t stackSizeSigned = static_cast(getRegU32(ctx, 6)); + const uint32_t currentSp = getRegU32(ctx, 29); + + if (gp != 0u) + { + setRegU32(ctx, 28, gp); + } + + uint32_t sp = currentSp; + if (stack == 0xFFFFFFFFu) + { + if (stackSizeSigned > 0) + { + const uint32_t requestedSize = static_cast(stackSizeSigned); + if (requestedSize < PS2_RAM_SIZE) + { + sp = PS2_RAM_SIZE - requestedSize; + } + else + { + sp = PS2_RAM_SIZE; + } + } + else + { + sp = PS2_RAM_SIZE; + } + } + else if (stack != 0u) + { + if (stackSizeSigned > 0) + { + sp = stack + static_cast(stackSizeSigned); + } + else + { + sp = stack; + } + } + + sp &= ~0xFu; + setReturnU32(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)); + } + + void GetMemorySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnU32(ctx, PS2_RAM_SIZE); + } + + static inline uint32_t normalizeKernelAlias(uint32_t addr) + { + if (addr >= 0x80000000u && addr < 0xC0000000u) + { + return addr & 0x1FFFFFFFu; + } + return addr; + } + + static uint32_t computeBuiltinFindAddressResult(uint8_t *rdram, + uint32_t originalStart, + uint32_t originalEnd, + uint32_t target) + { + uint32_t start = (originalStart + 3u) & ~0x3u; + uint32_t end = originalEnd & ~0x3u; + if (start >= end) + { + return 0u; + } + + const uint32_t targetNorm = normalizeKernelAlias(target); + for (uint32_t addr = start; addr < end; addr += sizeof(uint32_t)) + { + const uint8_t *entryPtr = getConstMemPtr(rdram, addr); + if (!entryPtr) + { + break; + } + + uint32_t entry = 0u; + std::memcpy(&entry, entryPtr, sizeof(entry)); + if (entry == target || normalizeKernelAlias(entry) == targetNorm) + { + return addr; + } + } + + return 0u; + } + + struct FindAddressWordSample + { + uint32_t addr = 0u; + uint32_t value = 0u; + }; + + struct FindAddressMatchSample + { + uint32_t addr = 0u; + uint32_t value = 0u; + bool aliasOnly = false; + }; + + static void logFindAddressDiagnostics(uint32_t callerPc, + uint32_t originalStart, + uint32_t originalEnd, + uint32_t alignedStart, + uint32_t alignedEnd, + uint32_t target, + uint32_t targetNorm, + bool found, + uint32_t resultAddr, + uint32_t scannedWords, + bool allZero, + bool aborted, + uint32_t abortedAddr, + const FindAddressWordSample *firstWords, + uint32_t firstWordCount, + const FindAddressWordSample *nonZeroWords, + uint32_t nonZeroWordCount, + const FindAddressMatchSample *matches, + uint32_t matchCount) + { + static std::atomic s_findAddressHitLogs{0u}; + static std::atomic s_findAddressMissLogs{0u}; + constexpr uint32_t kMaxFindAddressHitLogs = 16u; + constexpr uint32_t kMaxFindAddressMissLogs = 128u; + + std::atomic &counter = found ? s_findAddressHitLogs : s_findAddressMissLogs; + const uint32_t logIndex = counter.fetch_add(1u, std::memory_order_relaxed); + const uint32_t logLimit = found ? kMaxFindAddressHitLogs : kMaxFindAddressMissLogs; + if (logIndex >= logLimit) + { + return; + } + + std::cerr << "[FindAddress:" << (found ? "hit" : "miss") << "]" + << " pc=0x" << std::hex << callerPc + << " start=0x" << originalStart + << " end=0x" << originalEnd + << " alignedStart=0x" << alignedStart + << " alignedEnd=0x" << alignedEnd + << " target=0x" << target + << " targetNorm=0x" << targetNorm + << " result=0x" << resultAddr + << std::dec + << " scannedWords=" << scannedWords + << " allZero=" << (allZero ? "true" : "false") + << " aborted=" << (aborted ? "true" : "false"); + if (aborted) + { + std::cerr << " abortedAddr=0x" << std::hex << abortedAddr << std::dec; + } + std::cerr << std::endl; + + std::cerr << " firstWords:"; + if (firstWordCount == 0u) + { + std::cerr << " none"; + } + else + { + for (uint32_t i = 0; i < firstWordCount; ++i) + { + std::cerr << " [0x" << std::hex << firstWords[i].addr + << "]=0x" << firstWords[i].value; + } + std::cerr << std::dec; + } + std::cerr << std::endl; + + std::cerr << " nonZeroSample:"; + if (nonZeroWordCount == 0u) + { + std::cerr << " none"; + } + else + { + for (uint32_t i = 0; i < nonZeroWordCount; ++i) + { + std::cerr << " [0x" << std::hex << nonZeroWords[i].addr + << "]=0x" << nonZeroWords[i].value; + } + std::cerr << std::dec; + } + std::cerr << std::endl; + + std::cerr << " matches:"; + if (matchCount == 0u) + { + std::cerr << " none"; + } + else + { + for (uint32_t i = 0; i < matchCount; ++i) + { + std::cerr << " [0x" << std::hex << matches[i].addr + << "]=0x" << matches[i].value + << (matches[i].aliasOnly ? "(alias)" : "(exact)"); + } + std::cerr << std::dec; + } + std::cerr << std::endl; + } + + // 0x83 FindAddress: + // - a0: table start (inclusive) + // - a1: table end (exclusive) + // - a2: target address to locate inside the table (word entries) + // Returns the guest address of the matching word entry, or 0 if not found. + void FindAddress(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)runtime; + + constexpr uint32_t kFindAddressWordSamples = 8u; + constexpr uint32_t kFindAddressMatchSamples = 4u; + + const uint32_t originalStart = getRegU32(ctx, 4); + const uint32_t originalEnd = getRegU32(ctx, 5); + const uint32_t target = getRegU32(ctx, 6); + const uint32_t targetNorm = normalizeKernelAlias(target); + const uint32_t callerPc = ctx->pc; + + uint32_t start = originalStart; + uint32_t end = originalEnd; + + // Word-scan semantics: align the search window to uint32 boundaries. + start = (start + 3u) & ~0x3u; + end &= ~0x3u; + + if (start >= end) + { + logFindAddressDiagnostics(callerPc, + originalStart, + originalEnd, + start, + end, + target, + targetNorm, + false, + 0u, + 0u, + true, + false, + 0u, + nullptr, + 0u, + nullptr, + 0u, + nullptr, + 0u); + setReturnU32(ctx, 0u); + return; + } + + FindAddressWordSample firstWords[kFindAddressWordSamples]{}; + FindAddressWordSample nonZeroWords[kFindAddressWordSamples]{}; + FindAddressMatchSample matches[kFindAddressMatchSamples]{}; + uint32_t firstWordCount = 0u; + uint32_t nonZeroWordCount = 0u; + uint32_t matchCount = 0u; + uint32_t scannedWords = 0u; + uint32_t resultAddr = 0u; + uint32_t abortedAddr = 0u; + bool aborted = false; + bool allZero = true; + bool foundMatch = false; + + for (uint32_t addr = start; addr < end; addr += sizeof(uint32_t)) + { + const uint8_t *entryPtr = getConstMemPtr(rdram, addr); + if (!entryPtr) + { + aborted = true; + abortedAddr = addr; + break; + } + + uint32_t entry = 0; + std::memcpy(&entry, entryPtr, sizeof(entry)); + ++scannedWords; + + if (firstWordCount < kFindAddressWordSamples) + { + firstWords[firstWordCount++] = {addr, entry}; + } + + if (entry != 0u) + { + allZero = false; + if (nonZeroWordCount < kFindAddressWordSamples) + { + nonZeroWords[nonZeroWordCount++] = {addr, entry}; + } + } + + const bool exactMatch = (entry == target); + const bool aliasMatch = !exactMatch && (normalizeKernelAlias(entry) == targetNorm); + if (exactMatch || aliasMatch) + { + if (!foundMatch) + { + resultAddr = addr; + foundMatch = true; + } + if (matchCount < kFindAddressMatchSamples) + { + matches[matchCount++] = {addr, entry, aliasMatch}; + } + } + } + + logFindAddressDiagnostics(callerPc, + originalStart, + originalEnd, + start, + end, + target, + targetNorm, + foundMatch, + resultAddr, + scannedWords, + allZero, + aborted, + abortedAddr, + firstWords, + firstWordCount, + nonZeroWords, + nonZeroWordCount, + matches, + matchCount); + + setReturnU32(ctx, resultAddr); + } + + void Deci2Call(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + setReturnS32(ctx, KE_OK); + } + + // 0x5A QueryBootMode (stub): return 0 for now + void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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) + { + 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/ps2xRuntime/src/lib/Kernel/Syscalls/System.h b/ps2xRuntime/src/lib/Kernel/Syscalls/System.h new file mode 100644 index 00000000..b668620e --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/System.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + bool dispatchSyscallOverride(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GsSetCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetGsCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iGsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iGsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SifLoadElfPart(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 sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId); + void initializeGuestKernelState(uint8_t *rdram); + void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetupThread(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); + void GetMemorySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void FindAddress(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void Deci2Call(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void RegisterExitHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Thread.cpp b/ps2xRuntime/src/lib/Kernel/Syscalls/Thread.cpp new file mode 100644 index 00000000..e024063c --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Thread.cpp @@ -0,0 +1,1095 @@ +#include "Common.h" +#include "Thread.h" + +namespace ps2_syscalls +{ + static void applySuspendStatusLocked(ThreadInfo &info) + { + if (info.waitType != TSW_NONE) + { + info.status = THS_WAITSUSPEND; + } + else + { + info.status = THS_SUSPEND; + } + } + + 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 &) + { + } + } + } + + void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, KE_OK); + } + + void iFlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + FlushCache(rdram, ctx, runtime); + } + + void EnableCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, KE_OK); + } + + void DisableCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, KE_OK); + } + + void ResetEE(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + std::cerr << "Syscall: ResetEE - requesting runtime stop" << std::endl; + // runtime->requestStop(); + setReturnS32(ctx, KE_OK); + } + + void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, KE_OK); + } + + void InitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + // This is a common ps2sdk helper that some games link against. + setReturnS32(ctx, 1); + } + + void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam + if (paramAddr == 0u) + { + std::cerr << "CreateThread error: null ThreadParam pointer" << std::endl; + setReturnS32(ctx, KE_ERROR); + return; + } + + const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); + + if (!param) + { + std::cerr << "CreateThread error: invalid ThreadParam address 0x" << std::hex << paramAddr << std::dec << std::endl; + setReturnS32(ctx, KE_ERROR); + return; + } + + auto info = std::make_shared(); + info->attr = param[0]; + info->entry = param[1]; + info->stack = param[2]; + info->stackSize = param[3]; + + 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]; + if (info->priority == 0) + { + info->priority = 1; + } + if (info->priority >= 128) + { + info->priority = 127; + } + info->currentPriority = static_cast(info->priority); + + int id = 0; + { + std::lock_guard lock(g_thread_map_mutex); + // Keep IDs in the classic low range used by patched libkernel helpers. + for (int attempts = 0; attempts < 0xFE; ++attempts) + { + if (g_nextThreadId < 2 || g_nextThreadId > 0xFF) + { + g_nextThreadId = 2; + } + + const int candidate = g_nextThreadId; + g_nextThreadId = (g_nextThreadId >= 0xFF) ? 2 : (g_nextThreadId + 1); + + if (g_threads.find(candidate) == g_threads.end()) + { + id = candidate; + break; + } + } + + if (id == 0) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + g_threads[id] = info; + } + + RUNTIME_LOG("[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); + + setReturnS32(ctx, id); + } + + void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int tid = static_cast(getRegU32(ctx, 4)); // $a0 + if (tid == 0) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + + auto info = lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + uint32_t autoStackToFree = 0; + { + std::lock_guard lock(info->m); + if (info->started || info->status != THS_DORMANT) + { + setReturnS32(ctx, KE_NOT_DORMANT); + return; + } + + if (info->ownsStack && info->stack != 0) + { + autoStackToFree = info->stack; + info->stack = 0; + info->stackSize = 0; + info->ownsStack = false; + } + } + + { + std::lock_guard lock(g_thread_map_mutex); + g_threads.erase(tid); + } + + { + std::lock_guard lock(g_exit_handler_mutex); + g_exit_handlers.erase(tid); + } + + if (runtime && autoStackToFree != 0) + { + runtime->guestFree(autoStackToFree); + } + + setReturnS32(ctx, KE_OK); + } + + void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id + uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg + if (tid == 0) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + + auto info = lookupThreadInfo(tid); + if (!info) + { + std::cerr << "StartThread error: unknown thread id " << tid << std::endl; + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + if (!runtime || !runtime->hasFunction(info->entry)) + { + std::cerr << "[StartThread] entry 0x" << std::hex << info->entry << std::dec << " is not registered" << std::endl; + setReturnS32(ctx, KE_ERROR); + return; + } + if (runtime->isStopRequested()) + { + setReturnS32(ctx, KE_ERROR); + return; + } + + joinHostThreadById(tid); + + const uint32_t callerSp = getRegU32(ctx, 29); + const uint32_t callerGp = getRegU32(ctx, 28); + + { + std::lock_guard lock(info->m); + if (info->started || info->status != THS_DORMANT) + { + setReturnS32(ctx, KE_NOT_DORMANT); + return; + } + + info->started = true; + info->status = THS_READY; + info->arg = arg; + info->terminated = false; + info->forceRelease = false; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + info->suspendCount = 0; + if (info->stack == 0 && info->stackSize != 0) + { + const uint32_t autoStack = runtime->guestMalloc(info->stackSize, 16u); + if (autoStack != 0) + { + info->stack = autoStack; + info->ownsStack = true; + RUNTIME_LOG("[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; + } + } + + g_activeThreads.fetch_add(1, std::memory_order_relaxed); + try + { + std::thread worker([=]() mutable + { + { + std::string name = "PS2Thread_" + std::to_string(tid); + ThreadNaming::SetCurrentThreadName(name); + } + R5900Context threadCtxCopy{}; + R5900Context *threadCtx = &threadCtxCopy; + + { + std::lock_guard lock(info->m); + info->status = THS_RUN; + } + + uint32_t threadSp = callerSp; + if (info->stack) + { + const uint32_t stackSize = (info->stackSize != 0) ? info->stackSize : 0x800u; + threadSp = (info->stack + stackSize) & ~0xFu; + } + uint32_t threadGp = info->gp; + const uint32_t normalizedGp = threadGp & 0x1FFFFFFFu; + if (threadGp == 0 || normalizedGp < 0x10000u || normalizedGp >= PS2_RAM_SIZE) + { + threadGp = callerGp; + } + + 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; + + g_currentThreadId = tid; + + RUNTIME_LOG("[StartThread] id=" << tid + << " 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); + + bool exited = false; + try + { + uint32_t lastPc = 0xFFFFFFFFu; + uint32_t samePcCount = 0; + constexpr uint32_t kSamePcYieldMask = 0x3FFFu; + constexpr uint32_t kSamePcWarnInterval = 0x20000u; + uint64_t stepCount = 0u; + + while (runtime && !runtime->isStopRequested()) + { + ++stepCount; + if (info->terminated.load(std::memory_order_relaxed)) + { + throw ThreadExitException(); + } + + waitWhileSuspended(info, runtime); + + const uint32_t pc = threadCtx->pc; + if (pc == 0u) + { + break; + } + + if ((stepCount & 0x1FFFFFu) == 0u) + { + RUNTIME_LOG("[StartThread] id=" << tid + << " heartbeat pc=0x" << std::hex << pc + << " ra=0x" << GPR_U32(threadCtx, 31) + << " sp=0x" << GPR_U32(threadCtx, 29) + << " gp=0x" << GPR_U32(threadCtx, 28) + << std::dec << std::endl); + } + + if (pc == lastPc) + { + ++samePcCount; + if ((samePcCount & kSamePcYieldMask) == 0u) + { + std::this_thread::yield(); + } + if ((samePcCount % kSamePcWarnInterval) == 0u) + { + RUNTIME_LOG("[StartThread] id=" << tid + << " spinning at pc=0x" << std::hex << pc + << " ra=0x" << GPR_U32(threadCtx, 31) + << std::dec << std::endl); + } + } + else + { + samePcCount = 0; + lastPc = pc; + } + + PS2Runtime::RecompiledFunction step = runtime->lookupFunction(pc); + if (!step) + { + std::cerr << "[StartThread] id=" << tid << " missing function for pc=0x" + << std::hex << pc << std::dec << std::endl; + throw ThreadExitException(); + } + { + PS2Runtime::GuestExecutionScope guestExecution(runtime); + step(rdram, threadCtx, runtime); + } + } + } + catch (const ThreadExitException &) + { + exited = true; + } + catch (const std::exception &e) + { + std::cerr << "[StartThread] id=" << tid << " exception: " << e.what() << std::endl; + } + + if (!exited) + { + RUNTIME_LOG("[StartThread] id=" << tid << " returned (pc=0x" + << std::hex << threadCtx->pc << std::dec << ")" << std::endl); + } + + runExitHandlersForThread(tid, rdram, threadCtx, runtime); + + uint32_t detachedAutoStack = 0; + { + std::lock_guard lock(info->m); + info->started = false; + info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + info->suspendCount = 0; + info->forceRelease = false; + info->terminated = false; + } + + bool stillRegistered = false; + { + std::lock_guard lock(g_thread_map_mutex); + stillRegistered = (g_threads.find(tid) != g_threads.end()); + } + if (!stillRegistered) + { + // ExitDeleteThread removes the record immediately; reclaim auto stack here. + std::lock_guard lock(info->m); + if (info->ownsStack && info->stack != 0) + { + detachedAutoStack = info->stack; + info->stack = 0; + info->stackSize = 0; + info->ownsStack = false; + } + } + + if (detachedAutoStack != 0 && runtime) + { + runtime->guestFree(detachedAutoStack); + } + + // Notify anybody waiting for termination (like TerminateThread) + info->cv.notify_all(); + + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }); + registerHostThread(tid, std::move(worker)); + } + catch (const std::exception &e) + { + std::cerr << "[StartThread] failed to spawn host thread for tid=" << tid << ": " << e.what() << std::endl; + g_activeThreads.fetch_sub(1, std::memory_order_relaxed); + std::lock_guard lock(info->m); + info->started = false; + info->status = THS_DORMANT; + info->waitType = TSW_NONE; + info->waitId = 0; + info->wakeupCount = 0; + info->suspendCount = 0; + info->forceRelease = false; + info->terminated = false; + setReturnS32(ctx, KE_ERROR); + return; + } + + setReturnS32(ctx, KE_OK); + } + + void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + 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->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 = 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->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)); + if (tid == 0) + tid = g_currentThreadId; + + 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; + } + info->terminated = true; + info->forceRelease = true; + } + info->cv.notify_all(); + + if (tid == g_currentThreadId) + { + runExitHandlersForThread(tid, rdram, ctx, runtime); + throw ThreadExitException(); + } + else + { + // Block until the target thread actually finishes unwinding and becomes dormant + std::unique_lock lock(info->m); + { + PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); + info->cv.wait(lock, [&]() + { return !info->started && info->status == THS_DORMANT; }); + } + } + + setReturnS32(ctx, KE_OK); + } + + void SuspendThread(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, KE_UNKNOWN_THID); + return; + } + + { + std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, KE_DORMANT); + return; + } + info->suspendCount++; + applySuspendStatusLocked(*info); + } + info->cv.notify_all(); + + if (tid == g_currentThreadId) + { + std::unique_lock lock(info->m); + { + PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); + info->cv.wait(lock, [&]() + { return info->suspendCount == 0 || info->terminated.load(); }); + } + if (info->terminated.load()) + { + throw ThreadExitException(); + } + info->status = THS_RUN; + } + + setReturnS32(ctx, KE_OK); + } + + 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, KE_UNKNOWN_THID); + return; + } + + { + std::lock_guard lock(info->m); + if (info->status == THS_DORMANT) + { + setReturnS32(ctx, KE_DORMANT); + return; + } + if (info->suspendCount <= 0) + { + setReturnS32(ctx, KE_NOT_SUSPEND); + 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, KE_OK); + } + + void GetThreadId(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + setReturnS32(ctx, g_currentThreadId); + } + + 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, KE_UNKNOWN_THID); + return; + } + + ee_thread_status_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); + if (!status) + { + setReturnS32(ctx, KE_ERROR); + 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, KE_OK); + } + + void iReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ReferThreadStatus(rdram, ctx, runtime); + } + + void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + auto info = ensureCurrentThreadInfo(ctx); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + 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 + { + static std::atomic s_sleepBlockLogs{0}; + const uint32_t sleepBlockLog = s_sleepBlockLogs.fetch_add(1, std::memory_order_relaxed); + if (sleepBlockLog < 256u) + { + RUNTIME_LOG("[SleepThread:block] tid=" << g_currentThreadId + << " pc=0x" << std::hex << ctx->pc + << " ra=0x" << getRegU32(ctx, 31) + << std::dec << std::endl); + } + + info->status = THS_WAIT; + info->waitType = TSW_SLEEP; + info->waitId = 0; + info->forceRelease = false; + + { + PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); + 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; + } + } + + static std::atomic s_sleepWakeLogs{0}; + const uint32_t sleepWakeLog = s_sleepWakeLogs.fetch_add(1, std::memory_order_relaxed); + if (sleepWakeLog < 256u) + { + RUNTIME_LOG("[SleepThread:wake] tid=" << g_currentThreadId + << " ret=" << ret + << " wakeupCount=" << info->wakeupCount + << std::endl); + } + + lock.unlock(); + waitWhileSuspended(info, runtime); + setReturnS32(ctx, ret); + } + + void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int tid = static_cast(getRegU32(ctx, 4)); + if (tid == 0) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + if (tid == g_currentThreadId) + { + setReturnS32(ctx, KE_ILLEGAL_THID); + return; + } + + auto info = lookupThreadInfo(tid); + if (!info) + { + setReturnS32(ctx, KE_UNKNOWN_THID); + return; + } + + int newWakeupCount = 0; + int statusAfter = THS_DORMANT; + { + 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++; + } + newWakeupCount = info->wakeupCount; + statusAfter = info->status; + } + + static std::atomic s_wakeupLogs{0}; + const uint32_t wakeupLog = s_wakeupLogs.fetch_add(1, std::memory_order_relaxed); + if (wakeupLog < 256u) + { + RUNTIME_LOG("[WakeupThread] tid=" << g_currentThreadId + << " target=" << tid + << " status=" << statusAfter + << " wakeupCount=" << newWakeupCount + << std::endl); + } + setReturnS32(ctx, KE_OK); + } + + void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + WakeupThread(rdram, ctx, runtime); + } + + void CancelWakeupThread(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, KE_UNKNOWN_THID); + return; + } + + 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) + { + 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; + } + + 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)); + + if (tid == 0) + tid = g_currentThreadId; + + 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 (newPrio == 0) + { + newPrio = (info->currentPriority > 0) ? info->currentPriority : 1; + } + if (newPrio <= 0 || newPrio >= 128) + { + setReturnS32(ctx, KE_ILLEGAL_PRIORITY); + return; + } + + info->currentPriority = newPrio; + } + + setReturnS32(ctx, KE_OK); + } + + void iChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ChangeThreadPriority(rdram, ctx, runtime); + } + + void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + static int logCount = 0; + int prio = static_cast(getRegU32(ctx, 4)); + if (prio == 0) + { + auto current = ensureCurrentThreadInfo(ctx); + if (current) + { + std::lock_guard lock(current->m); + prio = (current->currentPriority > 0) ? current->currentPriority : 1; + } + } + if (logCount < 16) + { + RUNTIME_LOG("[RotateThreadReadyQueue] prio=" << prio); + ++logCount; + } + if (prio <= 0 || prio >= 128) + { + setReturnS32(ctx, KE_ILLEGAL_PRIORITY); + return; + } + + std::this_thread::yield(); + + setReturnS32(ctx, KE_OK); + } + + void iRotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + RotateThreadReadyQueue(rdram, ctx, runtime); + } + + void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + int tid = static_cast(getRegU32(ctx, 4)); + if (tid == 0 || tid == g_currentThreadId) + { + 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 || info->status == THS_WAITSUSPEND) + { + 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, KE_OK); + } + + void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + ReleaseWaitThread(rdram, ctx, runtime); + } +} diff --git a/ps2xRuntime/src/lib/Kernel/Syscalls/Thread.h b/ps2xRuntime/src/lib/Kernel/Syscalls/Thread.h new file mode 100644 index 00000000..e34aa052 --- /dev/null +++ b/ps2xRuntime/src/lib/Kernel/Syscalls/Thread.h @@ -0,0 +1,36 @@ +#pragma once + +#include "ps2_syscalls.h" + +namespace ps2_syscalls +{ + void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iFlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void EnableCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DisableCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ResetEE(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void InitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ExitDeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ResumeThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void GetThreadId(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void CancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iCancelWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iRotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); + void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); +} diff --git a/ps2xRuntime/src/lib/game_overrides.cpp b/ps2xRuntime/src/lib/game_overrides.cpp index 262f9738..bc557902 100644 --- a/ps2xRuntime/src/lib/game_overrides.cpp +++ b/ps2xRuntime/src/lib/game_overrides.cpp @@ -3,6 +3,7 @@ #include "ps2_runtime_calls.h" #include "ps2_stubs.h" #include "ps2_syscalls.h" +#include "ps2_log.h" #include #include #include @@ -115,10 +116,10 @@ namespace const std::string_view resolvedSyscall = ps2_runtime_calls::resolveSyscallName(handlerName); if (!resolvedSyscall.empty()) { -#define PS2_RESOLVE_SYSCALL(name) \ - if (resolvedSyscall == std::string_view{#name}) \ - { \ - return &ps2_syscalls::name; \ +#define PS2_RESOLVE_SYSCALL(name) \ + if (resolvedSyscall == std::string_view{#name}) \ + { \ + return &ps2_syscalls::name; \ } PS2_SYSCALL_LIST(PS2_RESOLVE_SYSCALL) #undef PS2_RESOLVE_SYSCALL @@ -127,10 +128,10 @@ namespace const std::string_view resolvedStub = ps2_runtime_calls::resolveStubName(handlerName); if (!resolvedStub.empty()) { -#define PS2_RESOLVE_STUB(name) \ - if (resolvedStub == std::string_view{#name}) \ - { \ - return &ps2_stubs::name; \ +#define PS2_RESOLVE_STUB(name) \ + if (resolvedStub == std::string_view{#name}) \ + { \ + return &ps2_stubs::name; \ } PS2_STUB_LIST(PS2_RESOLVE_STUB) #undef PS2_RESOLVE_STUB @@ -175,6 +176,10 @@ namespace ps2_game_overrides void applyMatching(PS2Runtime &runtime, const std::string &elfPath, uint32_t entry) { + ps2_syscalls::clearSoundDriverCompatLayout(); + ps2_syscalls::clearDtxCompatLayout(); + ps2_stubs::clearMpegCompatLayout(); + std::vector descriptors; { std::lock_guard lock(registryMutex()); @@ -233,14 +238,76 @@ namespace ps2_game_overrides const char *name = (descriptor.name && descriptor.name[0] != '\0') ? descriptor.name : "unnamed"; - std::cout << "[game_overrides] applying '" << name << "'" << std::endl; + RUNTIME_LOG("[game_overrides] applying '" << name << "'"); descriptor.apply(runtime); ++appliedCount; } if (appliedCount > 0) { - std::cout << "[game_overrides] applied " << appliedCount << " matching override(s)." << std::endl; + RUNTIME_LOG("[game_overrides] applied " << appliedCount << " matching override(s)."); } } } + +namespace +{ + void applyRecvxSoundDriverCompat(PS2Runtime &runtime) + { + (void)runtime; + + // Trying to explain a bit of Resident Evil Code: Veronica X sound-driver guest globals. + // Update these guest addresses/callback PCs when porting the override to another build: + // - checksum tables back the SE/MIDI status values mirrored through the snddrv RPC stubs + // - busyFlagAddr is the guest-side "work in progress" word cleared on completion + // - completion/clearBusy callbacks are guest PCs reached when async snddrv work finishes + PS2SoundDriverCompatLayout layout{}; + layout.primarySeCheckAddr = 0x01E0EF10u; + layout.primaryMidiCheckAddr = 0x01E0EF20u; + layout.fallbackSeCheckAddr = 0x01E1EF10u; + layout.fallbackMidiCheckAddr = 0x01E1EF20u; + layout.busyFlagAddr = 0x01E212C8u; + layout.completionCallbacks = {0x002EAC20u, 0x002EAC30u, 0x002FAC20u, 0x002FAC30u}; + layout.clearBusyCallbacks = {0x002EAC30u, 0x002FAC30u}; + ps2_syscalls::setSoundDriverCompatLayout(layout); + } + + void applyRecvxDtxCompat(PS2Runtime &runtime) + { + (void)runtime; + + // Trying to explain abit of Resident Evil Code: Veronica X DTX guest layout. + // Update these guest values when porting the middleware override to another build: + // - rpcSid identifies the DTX RPC service the guest binds/registers + // - urpc object/table addresses back the SJX/PS2RNA/SJRMT command tables + // - dispatcherFuncAddr is the guest-side DTX RPC handler used for URPC dispatch + PS2DtxCompatLayout layout{}; + layout.rpcSid = 0x7D000000u; + layout.urpcObjBase = 0x01F18000u; + layout.urpcObjLimit = 0x01F1FF00u; + layout.urpcObjStride = 0x20u; + layout.urpcFnTableBase = 0x0034FED0u; + layout.urpcObjTableBase = 0x0034FFD0u; + layout.dispatcherFuncAddr = 0x002FABC0u; + ps2_syscalls::setDtxCompatLayout(layout); + } + + void applyRecvxMpegCompat(PS2Runtime &runtime) + { + (void)runtime; + + // this is temporary so ignore for now + PS2MpegCompatLayout layout{}; + layout.mpegObjectAddr = 0x01E27140u; + layout.videoStateAddr = 0x01E271E8u; + layout.movieStateAddr = 0x01E21914u; + layout.syntheticFramesBeforeEnd = 1u; + layout.finishedVideoStateValue = 3u; + layout.finishedMovieStateValue = 3u; + ps2_stubs::setMpegCompatLayout(layout); + } + + PS2_REGISTER_GAME_OVERRIDE("RECVX sound-driver compat", "slus_201.84", 0u, 0u, &applyRecvxSoundDriverCompat); + PS2_REGISTER_GAME_OVERRIDE("RECVX DTX compat", "slus_201.84", 0u, 0u, &applyRecvxDtxCompat); + PS2_REGISTER_GAME_OVERRIDE("RECVX MPEG compat", "slus_201.84", 0u, 0u, &applyRecvxMpegCompat); +} diff --git a/ps2xRuntime/src/runner/games_database.cpp b/ps2xRuntime/src/lib/games_database.cpp similarity index 93% rename from ps2xRuntime/src/runner/games_database.cpp rename to ps2xRuntime/src/lib/games_database.cpp index e6fd1ec3..2e7755b5 100644 --- a/ps2xRuntime/src/runner/games_database.cpp +++ b/ps2xRuntime/src/lib/games_database.cpp @@ -32,12 +32,12 @@ static const std::unordered_map gameDatabase = { "SLUS-20703", "AirForce Delta Strike (USA)" }, { "SLES-50919", "Akira Psycho Ball (Europe)" }, { "SLPS-20150", "Akira Psycho Ball (Japan)" }, - { "SLES-50429", "Alex Ferguson’s Player Manager 2001 (Europe)" }, + { "SLES-50429", "Alex Ferguson�s Player Manager 2001 (Europe)" }, { "SLES-51792", "Aliens Versus Predator - Extinction (Europe)" }, { "SLUS-20147", "Aliens vs Predator - Extinction (USA)" }, { "SLPS-20181", "Alpine Racer 3 (Japan)" }, { "SLUS-21069", "American Chopper (USA)" }, - { "SLPM-65513", "Angel’s Feather (Japan)" }, + { "SLPM-65513", "Angel�s Feather (Japan)" }, { "SLPM-65027", "Anime Eikaiwa - 15 Shounen Hyouryuuki - Hitomi no Naka no Shounen (Japan)" }, { "SLPM-65029", "Anime Eikaiwa - Tondemo Nezumi Daikatsuyaku (Japan)" }, { "SLPM-65028", "Anime Eikaiwa - Tottoi (Japan)" }, @@ -50,7 +50,7 @@ static const std::unordered_map gameDatabase = { "SLUS-20498", "Auto Modellista (USA) (Volume 1.0) (Beta)" }, { "SLUS-28031", "Auto Modellista (USA) (Volume 2.0) (Beta)" }, { "SLUS-20642", "Auto Modellista (USA)" }, - { "SLPS-25140", "Baldur’s Gate - Dark Alliance (Japan)" }, + { "SLPS-25140", "Baldur�s Gate - Dark Alliance (Japan)" }, { "SLPM-62155", "Baseball 2002, The - Battle Ball Park Sengen (Japan)" }, { "SLPM-65180", "Baseball 2003, The - Battle Ball Park Sengen - Perfect Play Pro Yakyuu (Japan) (v1.05)" }, { "SLES-51756", "Batman - Rise of Sin Tzu (Europe)" }, @@ -112,15 +112,15 @@ static const std::unordered_map gameDatabase = { "SLUS-20485", "Dino Stalker (USA)" }, { "SLES-55392", "Disney Sing It (Europe)" }, { "SLES-55542", "Disney Sing It - Pop Hits (Europe)" }, - { "SLES-50042", "Disney’s Dinosaur (Europe)" }, - { "SLES-50043", "Disney’s Dinosaur (Europe)" }, - { "SLES-50048", "Disney’s Donald Duck - Quack Attack (Europe)" }, - { "SLES-50045", "Disney’s Jungle Book - Groove Party (Europe)" }, - { "SCES-50522", "Disney’s Peter Pan - The Legend of Never Land (Europe)" }, - { "SCES-50531", "Disney’s Peter Pan - The Legend of Never Land (Scandinavia)" }, - { "SLES-50350", "Disney’s Tarzan - Freeride (Europe)" }, - { "SCES-51176", "Disney’s Treasure Planet (Europe)" }, - { "SCUS-97146", "Disney’s Treasure Planet (USA)" }, + { "SLES-50042", "Disney�s Dinosaur (Europe)" }, + { "SLES-50043", "Disney�s Dinosaur (Europe)" }, + { "SLES-50048", "Disney�s Donald Duck - Quack Attack (Europe)" }, + { "SLES-50045", "Disney�s Jungle Book - Groove Party (Europe)" }, + { "SCES-50522", "Disney�s Peter Pan - The Legend of Never Land (Europe)" }, + { "SCES-50531", "Disney�s Peter Pan - The Legend of Never Land (Scandinavia)" }, + { "SLES-50350", "Disney�s Tarzan - Freeride (Europe)" }, + { "SCES-51176", "Disney�s Treasure Planet (Europe)" }, + { "SCUS-97146", "Disney�s Treasure Planet (USA)" }, { "SCES-50600", "Disney-Pixar Die Monster AG - Schreckens-Insel (Germany)" }, { "SCES-50597", "Disney-Pixar Monsters en Co. - Schrik Eiland (Netherlands)" }, { "SCES-50595", "Disney-Pixar Monsters, Inc. - Scare Island (Europe)" }, @@ -188,10 +188,10 @@ static const std::unordered_map gameDatabase = { "SLPS-25200", "Final Fantasy XI - Online (Japan)" }, { "SCUS-97271", "Final Fantasy XI - Online (USA) (Beta)" }, { "SCUS-97266", "Final Fantasy XI - Online (USA)" }, - { "SLPM-65288", "Final Fantasy XI - Zilart no Gen’ei (Japan) (All in One Pack 2003)" }, - { "SLPM-65287", "Final Fantasy XI - Zilart no Gen’ei (Japan)" }, - { "SLES-51418", "Fisherman’s Challenge (Europe)" }, - { "SLUS-20553", "Fisherman’s Challenge (USA)" }, + { "SLPM-65288", "Final Fantasy XI - Zilart no Gen�ei (Japan) (All in One Pack 2003)" }, + { "SLPM-65287", "Final Fantasy XI - Zilart no Gen�ei (Japan)" }, + { "SLES-51418", "Fisherman�s Challenge (Europe)" }, + { "SLUS-20553", "Fisherman�s Challenge (USA)" }, { "SLES-50259", "Flintstones in Viva Rock Vegas, The (Europe)" }, { "SLPS-25034", "Flower, Sun and Rain (Japan)" }, { "SLED-52852", "Forgotten Realms - Demon Stone (Europe) (Demo)" }, @@ -293,7 +293,7 @@ static const std::unordered_map gameDatabase = { "SLES-50039", "International Superstar Soccer (Europe)" }, { "SLPM-62075", "International Superstar Soccer 2 (Europe) (Beta)" }, { "SLUS-20913", "Inuyasha - The Secret of the Cursed Mask (USA)" }, - { "SLPM-65530", "J. League Pro Soccer Club o Tsukurou! ‘04 (Japan)" }, + { "SLPM-65530", "J. League Pro Soccer Club o Tsukurou! �04 (Japan)" }, { "SLPM-62217", "J. League Winning Eleven 6 (Japan)" }, { "SLES-50735", "Jade Cocoon 2 (Europe)" }, { "SCED-52952", "Jak 3 (Europe) (Demo)" }, @@ -324,13 +324,13 @@ static const std::unordered_map gameDatabase = { "SLPS-25386", "KOF - Maximum Impact (Japan)" }, { "SLUS-20923", "KOF - Maximum Impact (USA)" }, { "SCPS-11009", "Ka (Japan)" }, - { "SCPS-15045", "Ka 2 - Let’s Go Hawaii (Japan)" }, + { "SCPS-15045", "Ka 2 - Let�s Go Hawaii (Japan)" }, { "SLPM-62383", "Karaoke Revolution - Night Selection 2003 (Japan)" }, { "SLPM-62528", "Karaoke Revolution Family Pack (Japan)" }, { "SLES-52308", "Karaoke Stage (Europe)" }, - { "SLES-51200", "Kelly Slater’s Pro Surfer (Europe)" }, - { "SLES-51201", "Kelly Slater’s Pro Surfer (Europe)" }, - { "SLUS-20334", "Kelly Slater’s Pro Surfer (USA)" }, + { "SLES-51200", "Kelly Slater�s Pro Surfer (Europe)" }, + { "SLES-51201", "Kelly Slater�s Pro Surfer (Europe)" }, + { "SLUS-20334", "Kelly Slater�s Pro Surfer (USA)" }, { "SLES-50114", "Kengo - Master of Bushido (Europe)" }, { "SLUS-20021", "Kengo - Master of Bushido (USA)" }, { "SLPM-60177", "Kengou 2 (Japan) (Taikenban)" }, @@ -370,7 +370,7 @@ static const std::unordered_map gameDatabase = { "SLES-50131", "Le Mans 24 Hours (Europe)" }, { "SLUS-20207", "Le Mans 24 Hours (USA) (En,Fr,Es)" }, { "SLES-51415", "Legacy of Kain Defiance" }, - { "SLUS-20045", "Legend of Alon D’ar, The (USA)" }, + { "SLUS-20045", "Legend of Alon D�ar, The (USA)" }, { "SLES-51045", "Legends of Wrestling II (Europe)" }, { "SLUS-20507", "Legends of Wrestling II (USA)" }, { "SLES-50892", "Lethal Skies - Elite Pilot - Team SW (Europe)" }, @@ -411,7 +411,7 @@ static const std::unordered_map gameDatabase = { "SCES-51164", "Mark of Kri, The (Europe)" }, { "SCUS-97140", "Mark of Kri, The (USA)" }, { "SLUS-20722", "Maximo vs Army of Zin (USA)" }, - { "SCPS-11014", "McDonald’s Original Happy Disc (Japan)" }, + { "SCPS-11014", "McDonald�s Original Happy Disc (Japan)" }, { "SLPS-20031", "MechSmith, The - Run=Dim (Japan)" }, { "SLES-51873", "Medal of Honor - Rising Sun (Europe, Australia)" }, { "SLES-51875", "Medal of Honor - Rising Sun (Germany)" }, @@ -469,7 +469,7 @@ static const std::unordered_map gameDatabase = { "SLES-50712", "NHL Hitz 2003 (Europe)" }, { "SLUS-20438", "NHL Hitz 2003 (USA)" }, { "SLUS-20691", "NHL Hitz Pro (USA)" }, - { "SLPS-25276", "Natsu Yume Ya Wa - The Tale of a Midsummer Night’s Dream (Japan)" }, + { "SLPS-25276", "Natsu Yume Ya Wa - The Tale of a Midsummer Night�s Dream (Japan)" }, { "SLPS-25314", "Nebula - Echo Night (Japan)" }, { "?", "Need for Speed Most Wanted" }, { "SLUS-20537", "Nickelodeon Jimmy Neutron - Boy Genius (USA)" }, @@ -483,7 +483,7 @@ static const std::unordered_map gameDatabase = { "SLES-51913", "Onimusha - Blade Warriors (Europe)" }, { "SLES-50247", "Onimusha - Warlords (Europe)" }, { "SLUS-20018", "Onimusha - Warlords (USA) (En,Ja)" }, - { "SCPS-15038", "Operator’s Side (Japan)" }, + { "SCPS-15038", "Operator�s Side (Japan)" }, { "SLPM-65524", "Orange Pocket - Root (Japan)" }, { "SLPM-65005", "Ore ga Kantoku da! Gekitou Pennant Race (Japan)" }, { "SCPS-15017", "PaRappa the Rapper 2 (Japan) (En,Ja)" }, @@ -497,7 +497,7 @@ static const std::unordered_map gameDatabase = { "SLES-50252", "Penny Racers (Europe)" }, { "SLPS-25222", "Pia Carrot e Youkoso!! 3 - Round Summer (Japan)" }, { "SCPS-11014", "Piposaru 2001 (Japan)" }, - { "SLPM-65611", "Pizzicato Polka - Ensa Gen’ya (Japan)" }, + { "SLPM-65611", "Pizzicato Polka - Ensa Gen�ya (Japan)" }, { "SCPS-15063", "PoPoLoCrois - Tsuki no Okite no Bouken (Japan)" }, { "SLPS-20323", "Pochi to Nyaa (Japan)" }, { "SCES-51135", "Primal" }, @@ -539,7 +539,7 @@ static const std::unordered_map gameDatabase = { "SLES-50137", "Robot Warlords (France)" }, { "SLES-50138", "Robot Warlords (Germany)" }, { "SLES-50572", "Robot Wars - Arenas of Destruction (UK)" }, - { "SLPS-25005", "Rock’n Megastage (Japan)" }, + { "SLPS-25005", "Rock�n Megastage (Japan)" }, { "SLPM-99999", "Rockman X - Command Mission (Japan)" }, { "SLPM-65463", "Rocky (Japan)" }, { "SLES-52002", "Rogue Ops (Europe)" }, @@ -588,10 +588,10 @@ static const std::unordered_map gameDatabase = { "SLES-50608", "Shadow Man - 2econd Coming (Germany)" }, { "SLUS-20413", "Shadow Man - 2econd Coming (USA)" }, { "?", "Shadow of the Colossus (Europe)" }, - { "SLES-50400", "Shaun Palmer’s Pro Snowboarder (Europe)" }, - { "SLES-50401", "Shaun Palmer’s Pro Snowboarder (France)" }, - { "SLES-50402", "Shaun Palmer’s Pro Snowboarder (Germany)" }, - { "SLUS-20199", "Shaun Palmer’s Pro Snowboarder (USA)" }, + { "SLES-50400", "Shaun Palmer�s Pro Snowboarder (Europe)" }, + { "SLES-50401", "Shaun Palmer�s Pro Snowboarder (France)" }, + { "SLES-50402", "Shaun Palmer�s Pro Snowboarder (Germany)" }, + { "SLUS-20199", "Shaun Palmer�s Pro Snowboarder (USA)" }, { "SLPM-65334", "Shin Seiki Evangelion - Ayanami Ikusei Keikaku with Asuka Hokan Keikaku (Japan)" }, { "SLPM-65867", "Shin Seiki Evangelion - Koutetsu no Girlfriend 2nd (Japan)" }, { "SLPM-65391", "Shinki Gensou - Spectral Souls (Japan)" }, @@ -668,8 +668,8 @@ static const std::unordered_map gameDatabase = { "SLES-50419", "Supercar Street Challenge (Europe)" }, { "SLES-50421", "Supercar Street Challenge (Germany)" }, { "SLUS-20012", "Supercar Street Challenge (USA)" }, - { "SLES-50852", "Sven-Goeran Eriksson’s World Challenge (Europe)" }, - { "SLES-50794", "Sven-Goeran Eriksson’s World Manager 2002 (Europe)" }, + { "SLES-50852", "Sven-Goeran Eriksson�s World Challenge (Europe)" }, + { "SLES-50794", "Sven-Goeran Eriksson�s World Manager 2002 (Europe)" }, { "SLES-50033", "Swing Away Golf (Europe)" }, { "SLUS-20096", "Swing Away Golf (USA)" }, { "SLPM-65121", "Switch (Japan)" }, @@ -689,7 +689,7 @@ static const std::unordered_map gameDatabase = { "SLPM-65397", "Tennis no Oujisama - Kiss of Prince Ice (Japan)" }, { "SLPM-65323", "Tennis no Oujisama - Smash Hit! (Japan)" }, { "SLPM-62359", "Tennis no Oujisama - Smash Hit! Original Anime Game (Japan)" }, - { "SLPM-65371", "Tennis no Oujisama - Sweat & Tears 2 - Seishun Gakuen Teikyuusai ‘03 - Perfect Live (Japan)" }, + { "SLPM-65371", "Tennis no Oujisama - Sweat & Tears 2 - Seishun Gakuen Teikyuusai �03 - Perfect Live (Japan)" }, { "SLPS-20053", "Tenshi no Present - Marl Oukoku Monogatari (Japan) (Genteiban)" }, { "SLPS-20066", "Tenshi no Present - Marl Oukoku Monogatari (Japan)" }, { "SLPM-65598", "Tenshou Gakuen Gensouroku (Japan)" }, @@ -701,18 +701,18 @@ static const std::unordered_map gameDatabase = { "SLES-50078", "TimeSplitters (Europe)" }, { "SLUS-20090", "TimeSplitters (USA) (v1.10)" }, { "SLUS-20090", "TimeSplitters (USA) (v2.00)" }, - { "SLES-51181", "Tom Clancy’s Ghost Recon (Europe)" }, - { "SLES-51182", "Tom Clancy’s Ghost Recon (Germany)" }, - { "SLUS-20613", "Tom Clancy’s Ghost Recon (USA)" }, - { "SLED-51472", "Tom Clancy’s Splinter Cell (Europe) (Demo)" }, - { "SLUS-20652", "Tom Clancy’s Splinter Cell (USA)" }, - { "SLES-50400", "Tony Hawk’s Pro Skater 3 (Europe)" }, - { "SLES-50401", "Tony Hawk’s Pro Skater 3 (France)" }, - { "SLES-50402", "Tony Hawk’s Pro Skater 3 (Germany)" }, - { "SLUS-20199", "Tony Hawk’s Pro Skater 3 (USA) (Rev 1)" }, - { "SLUS-20199", "Tony Hawk’s Pro Skater 3 (USA)" }, - { "SLPS-99999", "Tony Hawk’s Pro Skater 4 (USA) (v1.02)" }, - { "SLPS-99999", "Tony Hawk’s Pro Skater 4 (USA) (v2.01)" }, + { "SLES-51181", "Tom Clancy�s Ghost Recon (Europe)" }, + { "SLES-51182", "Tom Clancy�s Ghost Recon (Germany)" }, + { "SLUS-20613", "Tom Clancy�s Ghost Recon (USA)" }, + { "SLED-51472", "Tom Clancy�s Splinter Cell (Europe) (Demo)" }, + { "SLUS-20652", "Tom Clancy�s Splinter Cell (USA)" }, + { "SLES-50400", "Tony Hawk�s Pro Skater 3 (Europe)" }, + { "SLES-50401", "Tony Hawk�s Pro Skater 3 (France)" }, + { "SLES-50402", "Tony Hawk�s Pro Skater 3 (Germany)" }, + { "SLUS-20199", "Tony Hawk�s Pro Skater 3 (USA) (Rev 1)" }, + { "SLUS-20199", "Tony Hawk�s Pro Skater 3 (USA)" }, + { "SLPS-99999", "Tony Hawk�s Pro Skater 4 (USA) (v1.02)" }, + { "SLPS-99999", "Tony Hawk�s Pro Skater 4 (USA) (v2.01)" }, { "SCED-52441", "Transformers (Europe) (Demo)" }, { "SLUS-20149", "Tribes - Aerial Assault (USA)" }, { "SLUS-20931", "Trigger Man (USA)" }, @@ -737,9 +737,9 @@ static const std::unordered_map gameDatabase = { "SLPS-20034", "Velvet File (Japan)" }, { "SLPS-25012", "Victorious Boxers (Japan)" }, { "SLPS-25129", "Victorious Boxers - Championship Version (Japan)" }, - { "SLES-50280", "Victorious Boxers - Ippo’s Road to Glory (Europe)" }, - { "SLUS-20282", "Victorious Boxers - Ippo’s Road to Glory (USA)" }, - { "SLPS-25287", "Victorious Boxers 2 - Ioop’s Road to Glory (Japan)" }, + { "SLES-50280", "Victorious Boxers - Ippo�s Road to Glory (Europe)" }, + { "SLUS-20282", "Victorious Boxers - Ippo�s Road to Glory (USA)" }, + { "SLPS-25287", "Victorious Boxers 2 - Ioop�s Road to Glory (Japan)" }, { "SLUS-20951", "Viewtiful Joe (USA)" }, { "SLES-51699", "Virtua Fighter - 10th Anniversary Edition (Europe)" }, { "SLES-51616", "Virtua Fighter 4 - Evolution (Europe)" }, @@ -756,7 +756,7 @@ static const std::unordered_map gameDatabase = { "SLES-51272", "Wakeboarding Unleashed featuring Shaun Murray (Europe)" }, { "SLES-51273", "Wakeboarding Unleashed featuring Shaun Murray (France)" }, { "SLUS-20418", "Wakeboarding Unleashed featuring Shaun Murray (USA)" }, - { "SLUS-20075", "Walt Disney’s The Jungle Book - Rhythm n’ Groove (USA)" }, + { "SLUS-20075", "Walt Disney�s The Jungle Book - Rhythm n� Groove (USA)" }, { "SLES-51973", "War Chess (Europe)" }, { "SCUS-97197", "War of the Monsters (USA)" }, { "SLES-50503", "Weakest Link, The (Europe)" }, diff --git a/ps2xRuntime/src/lib/ps2_audio.cpp b/ps2xRuntime/src/lib/ps2_audio.cpp index 720d2c96..6bb64949 100644 --- a/ps2xRuntime/src/lib/ps2_audio.cpp +++ b/ps2xRuntime/src/lib/ps2_audio.cpp @@ -1,58 +1,81 @@ -#include "ps2_audio.h" -#include "ps2_memory.h" -#include "raylib.h" +#include "runtime/ps2_audio.h" +#include "runtime/ps2_memory.h" +#include "ps2_host_backend.h" #include #include namespace { -std::vector buildWavFromPcm(const int16_t *pcm, size_t sampleCount, uint32_t sampleRate) -{ - const uint32_t dataSize = static_cast(sampleCount * 2); - const uint32_t fileSize = 36 + dataSize; - std::vector wav(8 + fileSize); + std::vector buildWavFromPcm(const int16_t *pcm, size_t sampleCount, uint32_t sampleRate) + { + const uint32_t dataSize = static_cast(sampleCount * 2); + const uint32_t fileSize = 36 + dataSize; + std::vector wav(8 + fileSize); - uint8_t *p = wav.data(); - p[0] = 'R'; p[1] = 'I'; p[2] = 'F'; p[3] = 'F'; - p[4] = static_cast(fileSize); - p[5] = static_cast(fileSize >> 8); - p[6] = static_cast(fileSize >> 16); - p[7] = static_cast(fileSize >> 24); - p[8] = 'W'; p[9] = 'A'; p[10] = 'V'; p[11] = 'E'; - p[12] = 'f'; p[13] = 'm'; p[14] = 't'; p[15] = ' '; - p[16] = 16; p[17] = 0; p[18] = 0; p[19] = 0; - p[20] = 1; p[21] = 0; - p[22] = 1; p[23] = 0; - p[24] = static_cast(sampleRate); - p[25] = static_cast(sampleRate >> 8); - p[26] = static_cast(sampleRate >> 16); - p[27] = static_cast(sampleRate >> 24); - const uint32_t byteRate = sampleRate * 2; - p[28] = static_cast(byteRate); - p[29] = static_cast(byteRate >> 8); - p[30] = static_cast(byteRate >> 16); - p[31] = static_cast(byteRate >> 24); - p[32] = 2; p[33] = 0; - p[34] = 16; p[35] = 0; - p[36] = 'd'; p[37] = 'a'; p[38] = 't'; p[39] = 'a'; - p[40] = static_cast(dataSize); - p[41] = static_cast(dataSize >> 8); - p[42] = static_cast(dataSize >> 16); - p[43] = static_cast(dataSize >> 24); - std::memcpy(p + 44, pcm, dataSize); - return wav; -} + uint8_t *p = wav.data(); + p[0] = 'R'; + p[1] = 'I'; + p[2] = 'F'; + p[3] = 'F'; + p[4] = static_cast(fileSize); + p[5] = static_cast(fileSize >> 8); + p[6] = static_cast(fileSize >> 16); + p[7] = static_cast(fileSize >> 24); + p[8] = 'W'; + p[9] = 'A'; + p[10] = 'V'; + p[11] = 'E'; + p[12] = 'f'; + p[13] = 'm'; + p[14] = 't'; + p[15] = ' '; + p[16] = 16; + p[17] = 0; + p[18] = 0; + p[19] = 0; + p[20] = 1; + p[21] = 0; + p[22] = 1; + p[23] = 0; + p[24] = static_cast(sampleRate); + p[25] = static_cast(sampleRate >> 8); + p[26] = static_cast(sampleRate >> 16); + p[27] = static_cast(sampleRate >> 24); + const uint32_t byteRate = sampleRate * 2; + p[28] = static_cast(byteRate); + p[29] = static_cast(byteRate >> 8); + p[30] = static_cast(byteRate >> 16); + p[31] = static_cast(byteRate >> 24); + p[32] = 2; + p[33] = 0; + p[34] = 16; + p[35] = 0; + p[36] = 'd'; + p[37] = 'a'; + p[38] = 't'; + p[39] = 'a'; + p[40] = static_cast(dataSize); + p[41] = static_cast(dataSize >> 8); + p[42] = static_cast(dataSize >> 16); + p[43] = static_cast(dataSize >> 24); + std::memcpy(p + 44, pcm, dataSize); + return wav; + } } namespace ps2_vag { -bool decode(const uint8_t *data, uint32_t sizeBytes, - std::vector &outPcm, uint32_t &outSampleRate); + bool decode(const uint8_t *data, uint32_t sizeBytes, + std::vector &outPcm, uint32_t &outSampleRate); } struct PS2AudioBackend::Impl { - struct TrackedSound { Sound snd; uint32_t sampleKey; }; + struct TrackedSound + { + Sound snd; + uint32_t sampleKey; + }; std::vector activeSounds; }; @@ -106,14 +129,18 @@ void PS2AudioBackend::onVagTransferFromBuffer(const uint8_t *data, uint32_t size m_sampleBank[physAddr] = sample; m_mostRecentSampleKey = physAddr; m_loadOrderSamples.push_back(std::move(sample)); + m_loadOrderSampleKeys.push_back(physAddr); constexpr size_t kMaxLoadOrderSamples = 32; if (m_loadOrderSamples.size() > kMaxLoadOrderSamples) + { m_loadOrderSamples.erase(m_loadOrderSamples.begin()); + m_loadOrderSampleKeys.erase(m_loadOrderSampleKeys.begin()); + } } namespace { -constexpr uint32_t LIBSD_CMD_SET_VOICE = 0x8010u; + constexpr uint32_t LIBSD_CMD_SET_VOICE = 0x8010u; } void PS2AudioBackend::onSoundCommand(uint32_t sid, uint32_t rpcNum, @@ -177,10 +204,12 @@ void PS2AudioBackend::play(uint32_t sampleAddr, float pitch, float volume, uint3 sampleToPlay = &it->second; sampleKey = it->first; } - else if (voiceIndex != 0xFFFFFFFFu && voiceIndex < m_loadOrderSamples.size()) + else if (voiceIndex != 0xFFFFFFFFu && + voiceIndex < m_loadOrderSamples.size() && + voiceIndex < m_loadOrderSampleKeys.size()) { sampleToPlay = &m_loadOrderSamples[voiceIndex]; - sampleKey = 0x1719740u + voiceIndex; + sampleKey = m_loadOrderSampleKeys[voiceIndex]; } else { @@ -199,6 +228,9 @@ void PS2AudioBackend::play(uint32_t sampleAddr, float pitch, float volume, uint3 void PS2AudioBackend::pruneFinishedSounds() { +#if defined(PLATFORM_VITA) + return; +#else auto &sounds = m_impl->activeSounds; auto it = sounds.begin(); while (it != sounds.end()) @@ -213,11 +245,20 @@ void PS2AudioBackend::pruneFinishedSounds() ++it; } } +#endif } void PS2AudioBackend::playDecodedSample(uint32_t sampleKey, DecodedSample &sample, float pitch, float volume, bool isBgm) { +#if defined(PLATFORM_VITA) + (void)sampleKey; + (void)sample; + (void)pitch; + (void)volume; + (void)isBgm; + return; +#else if (!m_audioReady || sample.pcm.empty()) return; @@ -263,6 +304,7 @@ void PS2AudioBackend::playDecodedSample(uint32_t sampleKey, DecodedSample &sampl SetSoundVolume(snd, volume); m_impl->activeSounds.push_back({snd, sampleKey}); PlaySound(snd); +#endif } void PS2AudioBackend::stop(uint32_t voiceId) @@ -273,10 +315,14 @@ void PS2AudioBackend::stop(uint32_t voiceId) void PS2AudioBackend::stopAll() { std::lock_guard lock(m_mutex); +#if defined(PLATFORM_VITA) + return; +#else for (auto &t : m_impl->activeSounds) { StopSound(t.snd); UnloadSound(t.snd); } m_impl->activeSounds.clear(); +#endif } diff --git a/ps2xRuntime/src/lib/ps2_audio_vag.cpp b/ps2xRuntime/src/lib/ps2_audio_vag.cpp index 12ba0ed9..5d29c2a1 100644 --- a/ps2xRuntime/src/lib/ps2_audio_vag.cpp +++ b/ps2xRuntime/src/lib/ps2_audio_vag.cpp @@ -1,112 +1,114 @@ -#include "ps2_memory.h" +#include "runtime/ps2_memory.h" #include #include #include namespace { -inline int16_t clamp16(int32_t v) -{ - if (v < -32768) return -32768; - if (v > 32767) return 32767; - return static_cast(v); -} + inline int16_t clamp16(int32_t v) + { + if (v < -32768) + return -32768; + if (v > 32767) + return 32767; + return static_cast(v); + } -inline int8_t signExtend4(uint8_t nibble) -{ - uint8_t s = nibble & 0x0F; - return static_cast((s & 8) ? static_cast(s | 0xF0) : static_cast(s)); -} + inline int8_t signExtend4(uint8_t nibble) + { + uint8_t s = nibble & 0x0F; + return static_cast((s & 8) ? static_cast(s | 0xF0) : static_cast(s)); + } } namespace ps2_vag { -bool decode(const uint8_t *data, uint32_t sizeBytes, - std::vector &outPcm, uint32_t &outSampleRate) -{ - if (!data || sizeBytes < 48) - return false; - - const uint32_t magic = (static_cast(data[0]) << 24) | - (static_cast(data[1]) << 16) | - (static_cast(data[2]) << 8) | - static_cast(data[3]); - if (magic != 0x56414770u) + bool decode(const uint8_t *data, uint32_t sizeBytes, + std::vector &outPcm, uint32_t &outSampleRate) { - const uint32_t magicLE = (static_cast(data[3]) << 24) | - (static_cast(data[2]) << 16) | - (static_cast(data[1]) << 8) | - static_cast(data[0]); - if (magicLE != 0x56414770u) + if (!data || sizeBytes < 48) return false; - } - uint32_t dataSize = (static_cast(data[0x0c]) << 24) | - (static_cast(data[0x0d]) << 16) | - (static_cast(data[0x0e]) << 8) | - static_cast(data[0x0f]); - outSampleRate = (static_cast(data[0x10]) << 24) | - (static_cast(data[0x11]) << 16) | - (static_cast(data[0x12]) << 8) | - static_cast(data[0x13]); - if (outSampleRate == 0) - outSampleRate = 44100; + const uint32_t magic = (static_cast(data[0]) << 24) | + (static_cast(data[1]) << 16) | + (static_cast(data[2]) << 8) | + static_cast(data[3]); + if (magic != 0x56414770u) + { + const uint32_t magicLE = (static_cast(data[3]) << 24) | + (static_cast(data[2]) << 16) | + (static_cast(data[1]) << 8) | + static_cast(data[0]); + if (magicLE != 0x56414770u) + return false; + } - const uint32_t numBlocks = (dataSize + 15) / 16; - outPcm.clear(); - outPcm.reserve(numBlocks * 28); + uint32_t dataSize = (static_cast(data[0x0c]) << 24) | + (static_cast(data[0x0d]) << 16) | + (static_cast(data[0x0e]) << 8) | + static_cast(data[0x0f]); + outSampleRate = (static_cast(data[0x10]) << 24) | + (static_cast(data[0x11]) << 16) | + (static_cast(data[0x12]) << 8) | + static_cast(data[0x13]); + if (outSampleRate == 0) + outSampleRate = 44100; - int16_t s1 = 0, s2 = 0; - const uint8_t *block = data + 48; + const uint32_t numBlocks = (dataSize + 15) / 16; + outPcm.clear(); + outPcm.reserve(numBlocks * 28); - for (uint32_t b = 0; b < numBlocks && (block + 16) <= data + sizeBytes; ++b, block += 16) - { - uint8_t shift = block[0] & 0x0F; - if (shift > 12) - shift = 9; - uint8_t filter = (block[0] >> 4) & 0x07; - if (filter > 4) - filter = 0; + int16_t s1 = 0, s2 = 0; + const uint8_t *block = data + 48; - for (int sampleIdx = 0; sampleIdx < 28; ++sampleIdx) + for (uint32_t b = 0; b < numBlocks && (block + 16) <= data + sizeBytes; ++b, block += 16) { - const uint8_t byte = block[2 + sampleIdx / 2]; - const uint8_t nibble = (sampleIdx & 1) ? (byte >> 4) : (byte & 0x0F); - const int8_t rawSample = signExtend4(nibble); - const int32_t shiftedSample = rawSample << (12 - shift); + uint8_t shift = block[0] & 0x0F; + if (shift > 12) + shift = 9; + uint8_t filter = (block[0] >> 4) & 0x07; + if (filter > 4) + filter = 0; - int32_t filteredSample; - const int32_t old = s1; - const int32_t older = s2; - switch (filter) + for (int sampleIdx = 0; sampleIdx < 28; ++sampleIdx) { - case 0: - filteredSample = shiftedSample; - break; - case 1: - filteredSample = shiftedSample + (60 * old + 32) / 64; - break; - case 2: - filteredSample = shiftedSample + (115 * old - 52 * older + 32) / 64; - break; - case 3: - filteredSample = shiftedSample + (98 * old - 55 * older + 32) / 64; - break; - case 4: - filteredSample = shiftedSample + (122 * old - 60 * older + 32) / 64; - break; - default: - filteredSample = shiftedSample; - break; - } + const uint8_t byte = block[2 + sampleIdx / 2]; + const uint8_t nibble = (sampleIdx & 1) ? (byte >> 4) : (byte & 0x0F); + const int8_t rawSample = signExtend4(nibble); + const int32_t shiftedSample = rawSample << (12 - shift); - const int16_t clamped = clamp16(filteredSample); - s2 = s1; - s1 = clamped; - outPcm.push_back(clamped); + int32_t filteredSample; + const int32_t old = s1; + const int32_t older = s2; + switch (filter) + { + case 0: + filteredSample = shiftedSample; + break; + case 1: + filteredSample = shiftedSample + (60 * old + 32) / 64; + break; + case 2: + filteredSample = shiftedSample + (115 * old - 52 * older + 32) / 64; + break; + case 3: + filteredSample = shiftedSample + (98 * old - 55 * older + 32) / 64; + break; + case 4: + filteredSample = shiftedSample + (122 * old - 60 * older + 32) / 64; + break; + default: + filteredSample = shiftedSample; + break; + } + + const int16_t clamped = clamp16(filteredSample); + s2 = s1; + s1 = clamped; + outPcm.push_back(clamped); + } } - } - return true; -} + return true; + } } diff --git a/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp b/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp index 12f169a3..3f5f292a 100644 --- a/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp +++ b/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp @@ -1,4 +1,5 @@ -#include "ps2_gif_arbiter.h" +#include "runtime/ps2_gif_arbiter.h" +#include "ps2_log.h" #include #include #include @@ -56,14 +57,14 @@ void GifArbiter::submit(GifPathId pathId, const uint8_t *data, uint32_t sizeByte uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); if (nreg == 0u) nreg = 16u; - std::cout << "[gif:submit] idx=" << debugIndex - << " path=" << pathName(pathId) - << " size=" << sizeBytes - << " nloop=" << nloop - << " flg=" << static_cast(flg) - << " nreg=" << nreg - << " directhl=" << static_cast(path2DirectHl ? 1u : 0u) - << std::endl; + RUNTIME_LOG("[gif:submit] idx=" << debugIndex + << " path=" << pathName(pathId) + << " size=" << sizeBytes + << " nloop=" << nloop + << " flg=" << static_cast(flg) + << " nreg=" << nreg + << " directhl=" << static_cast(path2DirectHl ? 1u : 0u) + << std::endl); } GifArbiterPacket pkt; @@ -109,15 +110,15 @@ void GifArbiter::drain() uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); if (nreg == 0u) nreg = 16u; - std::cout << "[gif:drain] idx=" << debugIndex - << " path=" << pathName(pkt.pathId) - << " size=" << pkt.data.size() - << " nloop=" << nloop - << " flg=" << static_cast(flg) - << " nreg=" << nreg - << " directhl=" << static_cast(pkt.path2DirectHl ? 1u : 0u) - << " path3image=" << static_cast(pkt.path3Image ? 1u : 0u) - << std::endl; + RUNTIME_LOG("[gif:drain] idx=" << debugIndex + << " path=" << pathName(pkt.pathId) + << " size=" << pkt.data.size() + << " nloop=" << nloop + << " flg=" << static_cast(flg) + << " nreg=" << nreg + << " directhl=" << static_cast(pkt.path2DirectHl ? 1u : 0u) + << " path3image=" << static_cast(pkt.path3Image ? 1u : 0u) + << std::endl); } m_processFn(pkt.data.data(), static_cast(pkt.data.size())); } diff --git a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp index 56a2fdb9..b02ecae0 100644 --- a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp @@ -1,8 +1,12 @@ -#include "ps2_gs_gpu.h" -#include "ps2_gs_common.h" -#include "ps2_gs_psmt4.h" -#include "ps2_gs_psmt8.h" -#include "ps2_memory.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_gs_common.h" +#include "runtime/ps2_gs_psmct16.h" +#include "runtime/ps2_gs_psmct32.h" +#include "runtime/ps2_gs_psmt4.h" +#include "runtime/ps2_gs_psmt8.h" +#include "ps2_log.h" +#include "ps2_syscalls.h" +#include "runtime/ps2_memory.h" #include #include #include @@ -13,6 +17,36 @@ namespace { + static constexpr uint32_t kDefaultDisplayWidth = 640u; + static constexpr uint32_t kDefaultDisplayHeight = 448u; + static constexpr uint32_t kHostFrameWidth = 640u; + static constexpr uint32_t kHostFrameHeight = 512u; + + uint16_t encodeFramePixelPSMCT16(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + return static_cast(((r >> 3) & 0x1Fu) | + (((g >> 3) & 0x1Fu) << 5) | + (((b >> 3) & 0x1Fu) << 10) | + ((a >= 0x40u) ? 0x8000u : 0u)); + } + + uint32_t addrPSMCT16Family(uint32_t basePtr, uint32_t width, uint8_t psm, uint32_t x, uint32_t y) + { + switch (psm) + { + case GS_PSM_CT16: + return GSPSMCT16::addrPSMCT16(basePtr, width, x, y); + case GS_PSM_CT16S: + return GSPSMCT16::addrPSMCT16S(basePtr, width, x, y); + case GS_PSM_Z16: + return GSPSMCT16::addrPSMZ16(basePtr, width, x, y); + case GS_PSM_Z16S: + return GSPSMCT16::addrPSMZ16S(basePtr, width, x, y); + default: + return 0u; + } + } + static inline uint64_t loadLE64(const uint8_t *p) { uint64_t v; @@ -20,6 +54,299 @@ namespace return v; } + void decodeDisplaySize(uint64_t display64, uint32_t &outWidth, uint32_t &outHeight) + { + const uint32_t dx = static_cast((display64 >> 0) & 0x0FFFu); + const uint32_t dy = static_cast((display64 >> 12) & 0x07FFu); + const uint32_t dw = static_cast((display64 >> 32) & 0x0FFFu); + const uint32_t dh = static_cast((display64 >> 44) & 0x07FFu); + const uint32_t magh = static_cast((display64 >> 23) & 0x0Fu); + + outWidth = (dw + 1u) / (magh + 1u); + outHeight = dh + 1u; + + if (outWidth < 64u || outHeight < 64u) + { + outWidth = kDefaultDisplayWidth; + outHeight = kDefaultDisplayHeight; + } + + outWidth = std::min(outWidth, kHostFrameWidth); + outHeight = std::min(outHeight, kHostFrameHeight); + } + + GSFrameReg decodeDisplayFrame(uint64_t dispfb64) + { + GSFrameReg frame{}; + frame.fbp = static_cast(dispfb64 & 0x1FFu); + frame.fbw = static_cast((dispfb64 >> 9) & 0x3Fu); + frame.psm = static_cast((dispfb64 >> 15) & 0x1Fu); + return frame; + } + + struct GSDisplayReadOrigin + { + uint32_t x = 0u; + uint32_t y = 0u; + }; + + GSDisplayReadOrigin decodeDisplayReadOrigin(uint64_t dispfb64) + { + GSDisplayReadOrigin origin{}; + origin.x = static_cast((dispfb64 >> 32) & 0x7FFu); + origin.y = static_cast((dispfb64 >> 43) & 0x7FFu); + return origin; + } + + bool hasDisplaySetup(uint64_t display64, const GSFrameReg &frame) + { + const uint32_t dw = static_cast((display64 >> 32) & 0x0FFFu); + const uint32_t dh = static_cast((display64 >> 44) & 0x07FFu); + const uint32_t magh = static_cast((display64 >> 23) & 0x0Fu); + return frame.fbw != 0u || dw != 0u || dh != 0u || magh != 0u; + } + + struct GSTransferTraversal + { + bool reverseX = false; + bool reverseY = false; + }; + + GSTransferTraversal decodeTransferTraversal(uint8_t dir) + { + GSTransferTraversal traversal{}; + switch (dir & 0x3u) + { + case 1u: + traversal.reverseY = true; + break; + case 2u: + traversal.reverseX = true; + break; + case 3u: + traversal.reverseX = true; + traversal.reverseY = true; + break; + default: + break; + } + return traversal; + } + + uint32_t transferCoord(uint32_t start, uint32_t extent, uint32_t index, bool reverse) + { + if (reverse && extent != 0u) + { + return start + (extent - 1u - index); + } + return start + index; + } + + struct GSPmodeState + { + bool enableCrt1 = false; + bool enableCrt2 = false; + bool mmod = false; + bool amod = false; + bool slbg = false; + uint8_t alp = 0u; + }; + + GSPmodeState decodePmode(uint64_t pmode64) + { + GSPmodeState pmode{}; + pmode.enableCrt1 = (pmode64 & 0x1ull) != 0ull; + pmode.enableCrt2 = (pmode64 & 0x2ull) != 0ull; + pmode.mmod = ((pmode64 >> 5) & 0x1ull) != 0ull; + pmode.amod = ((pmode64 >> 6) & 0x1ull) != 0ull; + pmode.slbg = ((pmode64 >> 7) & 0x1ull) != 0ull; + pmode.alp = static_cast((pmode64 >> 8) & 0xFFu); + return pmode; + } + + struct GSSmode2State + { + bool interlaced = false; + bool frameMode = true; + }; + + GSSmode2State decodeSMode2(uint64_t smode264) + { + GSSmode2State smode2{}; + smode2.interlaced = (smode264 & 0x1ull) != 0ull; + smode2.frameMode = ((smode264 >> 1) & 0x1ull) != 0ull; + return smode2; + } + + void applyFieldPresentation(std::vector &pixels, uint32_t width, uint32_t height, bool oddField) + { + if (pixels.empty() || width == 0u || height < 2u) + { + return; + } + + const std::vector source = pixels; + for (uint32_t y = 0; y < height; ++y) + { + uint32_t sourceY = ((y >> 1u) << 1u) + (oddField ? 1u : 0u); + if (sourceY >= height) + { + sourceY = height - 1u; + } + + const uint8_t *srcRow = source.data() + (sourceY * kHostFrameWidth * 4u); + uint8_t *dstRow = pixels.data() + (y * kHostFrameWidth * 4u); + std::memcpy(dstRow, srcRow, width * 4u); + } + } + + void normalizePresentationAlpha(std::vector &pixels, uint32_t width, uint32_t height) + { + if (pixels.empty() || width == 0u || height == 0u) + { + return; + } + + for (uint32_t y = 0; y < height; ++y) + { + uint8_t *row = pixels.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width; ++x) + { + row[x * 4u + 3u] = 255u; + } + } + } + + uint8_t blendPresentationChannel(uint8_t src, uint8_t dst, uint32_t factor) + { + const int delta = static_cast(src) - static_cast(dst); + return GSInternal::clampU8(static_cast(dst) + ((delta * static_cast(factor)) / 255)); + } + + uint32_t countNonBlackPixels(const std::vector &pixels, uint32_t width, uint32_t height) + { + uint32_t count = 0u; + for (uint32_t y = 0; y < height; ++y) + { + const uint8_t *row = pixels.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width; ++x) + { + const uint8_t r = row[x * 4u + 0u]; + const uint8_t g = row[x * 4u + 1u]; + const uint8_t b = row[x * 4u + 2u]; + if (r != 0u || g != 0u || b != 0u) + { + ++count; + } + } + } + return count; + } + + bool clearFramebufferRect(uint8_t *vram, + uint32_t vramSize, + const GSContext &ctx, + uint32_t rgba) + { + if (!vram || vramSize == 0u || ctx.frame.fbw == 0u) + { + return false; + } + + const uint32_t stride = GSInternal::fbStride(ctx.frame.fbw, ctx.frame.psm); + if (stride == 0u) + { + return false; + } + + const int x0 = std::max(0, ctx.scissor.x0); + const int x1 = std::max(x0, ctx.scissor.x1); + const int y0 = std::max(0, ctx.scissor.y0); + const int y1 = std::max(y0, ctx.scissor.y1); + const uint32_t base = ctx.frame.fbp * 8192u; + + uint8_t r = static_cast(rgba & 0xFFu); + uint8_t g = static_cast((rgba >> 8) & 0xFFu); + uint8_t b = static_cast((rgba >> 16) & 0xFFu); + uint8_t a = static_cast((rgba >> 24) & 0xFFu); + if ((ctx.fba & 0x1ull) != 0ull && ctx.frame.psm != GS_PSM_CT24) + { + a = static_cast(a | 0x80u); + } + + if (ctx.frame.psm == GS_PSM_CT32 || ctx.frame.psm == GS_PSM_CT24) + { + const uint32_t srcPixel = + static_cast(r) | + (static_cast(g) << 8) | + (static_cast(b) << 16) | + (static_cast(a) << 24); + const uint32_t widthBlocks = (ctx.frame.fbw != 0u) ? ctx.frame.fbw : 1u; + + for (int y = y0; y <= y1; ++y) + { + for (int x = x0; x <= x1; ++x) + { + const uint32_t off = + GSPSMCT32::addrPSMCT32(GSInternal::framePageBaseToBlock(ctx.frame.fbp), + widthBlocks, + static_cast(x), + static_cast(y)); + if (off + 4u > vramSize) + { + return true; + } + + uint32_t pixel = srcPixel; + if (ctx.frame.fbmsk != 0u) + { + uint32_t existing = 0u; + std::memcpy(&existing, vram + off, sizeof(existing)); + pixel = (pixel & ~ctx.frame.fbmsk) | (existing & ctx.frame.fbmsk); + } + std::memcpy(vram + off, &pixel, sizeof(pixel)); + } + } + return true; + } + + if (ctx.frame.psm == GS_PSM_CT16 || ctx.frame.psm == GS_PSM_CT16S) + { + const uint16_t srcPixel = encodeFramePixelPSMCT16(r, g, b, a); + const uint16_t mask = static_cast(ctx.frame.fbmsk & 0xFFFFu); + const uint32_t widthBlocks = (ctx.frame.fbw != 0u) ? ctx.frame.fbw : 1u; + const uint32_t basePtr = GSInternal::framePageBaseToBlock(ctx.frame.fbp); + + for (int y = y0; y <= y1; ++y) + { + for (int x = x0; x <= x1; ++x) + { + const uint32_t off = addrPSMCT16Family(basePtr, + widthBlocks, + ctx.frame.psm, + static_cast(x), + static_cast(y)); + if (off + 2u > vramSize) + { + return true; + } + + uint16_t pixel = srcPixel; + if (mask != 0u) + { + uint16_t existing = 0u; + std::memcpy(&existing, vram + off, sizeof(existing)); + pixel = static_cast((pixel & ~mask) | (existing & mask)); + } + std::memcpy(vram + off, &pixel, sizeof(pixel)); + } + } + return true; + } + + return false; + } + std::atomic s_debugGifPacketCount{0}; std::atomic s_debugGsRegisterCount{0}; std::atomic s_debugGsPackedVertexCount{0}; @@ -65,7 +392,7 @@ namespace case GS_PSM_CT32: case GS_PSM_Z32: { - const uint32_t off = base + ((y * width * 64u) + x) * 4u; + const uint32_t off = GSPSMCT32::addrPSMCT32(basePtr, width, x, y); if (off + 4u > vramSize) return 0u; uint32_t value = 0u; @@ -75,7 +402,7 @@ namespace case GS_PSM_CT24: case GS_PSM_Z24: { - const uint32_t off = base + ((y * width * 64u) + x) * 4u; + const uint32_t off = GSPSMCT32::addrPSMCT32(basePtr, width, x, y); if (off + 3u > vramSize) return 0u; return static_cast(vram[off + 0u]) | @@ -87,7 +414,7 @@ namespace case GS_PSM_Z16: case GS_PSM_Z16S: { - const uint32_t off = base + ((y * width * 64u) + x) * 2u; + const uint32_t off = addrPSMCT16Family(basePtr, width, psm, x, y); if (off + 2u > vramSize) return 0u; uint16_t value = 0u; @@ -130,7 +457,7 @@ namespace case GS_PSM_CT32: case GS_PSM_Z32: { - const uint32_t off = base + ((y * width * 64u) + x) * 4u; + const uint32_t off = GSPSMCT32::addrPSMCT32(basePtr, width, x, y); if (off + 4u > vramSize) return; std::memcpy(vram + off, &value, sizeof(value)); @@ -139,7 +466,7 @@ namespace case GS_PSM_CT24: case GS_PSM_Z24: { - const uint32_t off = base + ((y * width * 64u) + x) * 4u; + const uint32_t off = GSPSMCT32::addrPSMCT32(basePtr, width, x, y); if (off + 3u > vramSize) return; vram[off + 0u] = static_cast(value & 0xFFu); @@ -152,7 +479,7 @@ namespace case GS_PSM_Z16: case GS_PSM_Z16S: { - const uint32_t off = base + ((y * width * 64u) + x) * 2u; + const uint32_t off = addrPSMCT16Family(basePtr, width, psm, x, y); if (off + 2u > vramSize) return; const uint16_t value16 = static_cast(value & 0xFFFFu); @@ -203,6 +530,7 @@ void GS::init(uint8_t *vram, uint32_t vramSize, GSRegisters *privRegs) void GS::reset() { + std::lock_guard lock(m_stateMutex); std::memset(m_ctx, 0, sizeof(m_ctx)); m_prim = {}; m_curR = 0x80; @@ -216,6 +544,9 @@ void GS::reset() m_curV = 0; m_curFog = 0; m_prmodecont = true; + m_pabe = false; + m_texa = {0u, false, 0u}; + m_texclut = {0u, 0u, 0u}; m_bitbltbuf = {}; m_trxpos = {}; m_trxreg = {}; @@ -226,6 +557,16 @@ void GS::reset() m_vtxIndex = 0; m_localToHostBuffer.clear(); m_localToHostReadPos = 0; + m_preferredDisplaySourceFrame = {}; + m_preferredDisplayDestFbp = 0; + m_hasPreferredDisplaySource = false; + m_hostPresentationFrame.clear(); + m_hostPresentationWidth = 0u; + m_hostPresentationHeight = 0u; + m_hostPresentationDisplayFbp = 0u; + m_hostPresentationSourceFbp = 0u; + m_hostPresentationUsedPreferred = false; + m_hasHostPresentationFrame = false; for (int i = 0; i < 2; ++i) { @@ -242,6 +583,7 @@ GSContext &GS::activeContext() void GS::snapshotVRAM() { + std::lock_guard stateLock(m_stateMutex); if (!m_vram || m_vramSize == 0) return; std::lock_guard lock(m_snapshotMutex); @@ -257,10 +599,26 @@ const uint8_t *GS::lockDisplaySnapshot(uint32_t &outSize) outSize = 0; return nullptr; } + outSize = static_cast(m_displaySnapshot.size()); return m_displaySnapshot.data(); } +bool GS::getPreferredDisplaySource(GSFrameReg &outSource, uint32_t &outDestFbp) const +{ + std::lock_guard lock(m_stateMutex); + if (!m_hasPreferredDisplaySource) + { + outSource = {}; + outDestFbp = 0u; + return false; + } + + outSource = m_preferredDisplaySourceFrame; + outDestFbp = m_preferredDisplayDestFbp; + return true; +} + void GS::unlockDisplaySnapshot() { m_snapshotMutex.unlock(); @@ -276,30 +634,505 @@ void GS::refreshDisplaySnapshot() snapshotVRAM(); } -void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) +bool GS::copyFrameToHostRgbaUnlocked(const GSFrameReg &frame, + uint32_t width, + uint32_t height, + std::vector &outPixels, + bool preserveAlpha, + bool useLocalMemoryLayout, + bool frameBaseIsPages, + uint32_t sourceOriginX, + uint32_t sourceOriginY) const { - if (!data || sizeBytes < 16 || !m_vram) + if (!m_vram || m_vramSize == 0u) + { + return false; + } + + outPixels.assign(kHostFrameWidth * kHostFrameHeight * 4u, 0u); + + const uint32_t baseBytes = frameBaseIsPages ? (frame.fbp * 8192u) : (frame.fbp * 256u); + const uint32_t basePtr = frameBaseIsPages ? GSInternal::framePageBaseToBlock(frame.fbp) : frame.fbp; + const uint32_t fbwBlocks = frame.fbw ? frame.fbw : (kHostFrameWidth / 64u); + const uint32_t bytesPerPixel = (frame.psm == GS_PSM_CT16 || frame.psm == GS_PSM_CT16S) ? 2u : 4u; + const uint32_t strideBytes = fbwBlocks * 64u * bytesPerPixel; + + if (frame.psm == GS_PSM_CT32 || frame.psm == GS_PSM_CT24) + { + const uint32_t srcPixelBytes = (frame.psm == GS_PSM_CT24) ? 3u : 4u; + if (useLocalMemoryLayout) + { + for (uint32_t y = 0; y < height; ++y) + { + uint8_t *dstRow = outPixels.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width; ++x) + { + const uint32_t srcX = sourceOriginX + x; + const uint32_t srcY = sourceOriginY + y; + const uint32_t srcOff = GSPSMCT32::addrPSMCT32(basePtr, fbwBlocks, srcX, srcY); + if (srcOff + srcPixelBytes > m_vramSize) + { + return false; + } + + dstRow[x * 4u + 0u] = m_vram[srcOff + 0u]; + dstRow[x * 4u + 1u] = m_vram[srcOff + 1u]; + dstRow[x * 4u + 2u] = m_vram[srcOff + 2u]; + dstRow[x * 4u + 3u] = + (preserveAlpha && frame.psm != GS_PSM_CT24) ? m_vram[srcOff + 3u] : 255u; + } + } + return true; + } + + for (uint32_t y = 0; y < height; ++y) + { + const uint32_t dstOff = y * kHostFrameWidth * 4u; + uint8_t *dstRow = outPixels.data() + dstOff; + for (uint32_t x = 0; x < width; ++x) + { + const uint32_t srcX = sourceOriginX + x; + const uint32_t srcY = sourceOriginY + y; + const uint32_t srcOff = baseBytes + (srcY * strideBytes) + (srcX * srcPixelBytes); + if (srcOff + srcPixelBytes > m_vramSize) + { + return false; + } + + dstRow[x * 4u + 0u] = m_vram[srcOff + 0u]; + dstRow[x * 4u + 1u] = m_vram[srcOff + 1u]; + dstRow[x * 4u + 2u] = m_vram[srcOff + 2u]; + dstRow[x * 4u + 3u] = + (preserveAlpha && frame.psm != GS_PSM_CT24) ? m_vram[srcOff + 3u] : 255u; + } + } + return true; + } + + if (frame.psm == GS_PSM_CT16 || frame.psm == GS_PSM_CT16S) + { + if (useLocalMemoryLayout) + { + for (uint32_t y = 0; y < height; ++y) + { + const uint32_t dstOff = y * kHostFrameWidth * 4u; + uint8_t *dst = outPixels.data() + dstOff; + for (uint32_t x = 0; x < width; ++x) + { + const uint32_t srcX = sourceOriginX + x; + const uint32_t srcY = sourceOriginY + y; + const uint32_t srcOff = addrPSMCT16Family(basePtr, fbwBlocks, frame.psm, srcX, srcY); + if (srcOff + sizeof(uint16_t) > m_vramSize) + { + return false; + } + + uint16_t pixel = 0u; + std::memcpy(&pixel, m_vram + srcOff, sizeof(pixel)); + const uint32_t r = pixel & 31u; + const uint32_t g = (pixel >> 5) & 31u; + const uint32_t b = (pixel >> 10) & 31u; + dst[x * 4u + 0u] = static_cast((r << 3) | (r >> 2)); + dst[x * 4u + 1u] = static_cast((g << 3) | (g >> 2)); + dst[x * 4u + 2u] = static_cast((b << 3) | (b >> 2)); + dst[x * 4u + 3u] = preserveAlpha ? ((pixel & 0x8000u) ? 0x80u : 0x00u) : 255u; + } + } + return true; + } + + for (uint32_t y = 0; y < height; ++y) + { + const uint32_t dstOff = y * kHostFrameWidth * 4u; + uint8_t *dst = outPixels.data() + dstOff; + for (uint32_t x = 0; x < width; ++x) + { + const uint32_t srcX = sourceOriginX + x; + const uint32_t srcY = sourceOriginY + y; + const uint32_t srcOff = baseBytes + (srcY * strideBytes) + (srcX * 2u); + if (srcOff + sizeof(uint16_t) > m_vramSize) + { + return false; + } + + uint16_t pixel = 0u; + std::memcpy(&pixel, m_vram + srcOff, sizeof(pixel)); + const uint32_t r = pixel & 31u; + const uint32_t g = (pixel >> 5) & 31u; + const uint32_t b = (pixel >> 10) & 31u; + dst[x * 4u + 0u] = static_cast((r << 3) | (r >> 2)); + dst[x * 4u + 1u] = static_cast((g << 3) | (g >> 2)); + dst[x * 4u + 2u] = static_cast((b << 3) | (b >> 2)); + dst[x * 4u + 3u] = preserveAlpha ? ((pixel & 0x8000u) ? 0x80u : 0x00u) : 255u; + } + } + return true; + } + + return false; +} + +void GS::latchHostPresentationFrame() +{ + std::lock_guard lock(m_stateMutex); + + if (!m_privRegs || !m_vram || m_vramSize == 0u) + { + m_hostPresentationFrame.clear(); + m_hostPresentationWidth = 0u; + m_hostPresentationHeight = 0u; + m_hostPresentationDisplayFbp = 0u; + m_hostPresentationSourceFbp = 0u; + m_hostPresentationUsedPreferred = false; + m_hasHostPresentationFrame = false; return; + } - const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed); - if (packetIndex < 48u) + const GSPmodeState pmode = decodePmode(m_privRegs->pmode); + const GSSmode2State smode2 = decodeSMode2(m_privRegs->smode2); + const bool applyFieldMode = smode2.interlaced && !smode2.frameMode; + const bool oddField = (ps2_syscalls::GetCurrentVSyncTick() & 1ull) != 0ull; + const GSFrameReg displayFrame1 = decodeDisplayFrame(m_privRegs->dispfb1); + const GSFrameReg displayFrame2 = decodeDisplayFrame(m_privRegs->dispfb2); + const GSDisplayReadOrigin displayOrigin1 = decodeDisplayReadOrigin(m_privRegs->dispfb1); + const GSDisplayReadOrigin displayOrigin2 = decodeDisplayReadOrigin(m_privRegs->dispfb2); + + uint32_t width1 = 0u; + uint32_t height1 = 0u; + uint32_t width2 = 0u; + uint32_t height2 = 0u; + decodeDisplaySize(m_privRegs->display1, width1, height1); + decodeDisplaySize(m_privRegs->display2, width2, height2); + + const bool validCrt1 = pmode.enableCrt1 && hasDisplaySetup(m_privRegs->display1, displayFrame1); + const bool validCrt2 = pmode.enableCrt2 && hasDisplaySetup(m_privRegs->display2, displayFrame2); + + auto copyDisplaySource = [&](const GSFrameReg &displayFrame, + const GSDisplayReadOrigin &displayOrigin, + uint32_t width, + uint32_t height, + bool allowPreferred, + bool preserveAlpha, + GSFrameReg &selectedFrame, + std::vector &scratch, + bool &usedPreferred) -> bool { - const uint64_t tagLo = loadLE64(data); - const uint32_t nloop = static_cast(tagLo & 0x7FFFu); - const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); - uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); - if (nreg == 0u) - nreg = 16u; - std::cout << "[gs:gif] idx=" << packetIndex - << " size=" << sizeBytes - << " nloop=" << nloop - << " flg=" << static_cast(flg) - << " nreg=" << nreg - << " ctx0fbp=" << m_ctx[0].frame.fbp - << " ctx1fbp=" << m_ctx[1].frame.fbp - << std::endl; + selectedFrame = displayFrame; + scratch.clear(); + usedPreferred = false; + + if (allowPreferred && + m_hasPreferredDisplaySource && + m_preferredDisplayDestFbp == displayFrame.fbp && + (m_preferredDisplaySourceFrame.fbw != 0u || m_preferredDisplaySourceFrame.fbp != displayFrame.fbp)) + { + if (copyFrameToHostRgbaUnlocked(m_preferredDisplaySourceFrame, + width, + height, + scratch, + preserveAlpha, + true, + false, + 0u, + 0u)) + { + selectedFrame = m_preferredDisplaySourceFrame; + usedPreferred = true; + } + } + + if (scratch.empty() && + !copyFrameToHostRgbaUnlocked(displayFrame, + width, + height, + scratch, + preserveAlpha, + true, + true, + displayOrigin.x, + displayOrigin.y)) + { + return false; + } + + if (!usedPreferred && displayFrame.fbp == 0u && countNonBlackPixels(scratch, width, height) == 0u) + { + for (int contextIndex = 0; contextIndex < 2; ++contextIndex) + { + const GSFrameReg &candidate = m_ctx[contextIndex].frame; + if (candidate.fbp == selectedFrame.fbp && + candidate.fbw == selectedFrame.fbw && + candidate.psm == selectedFrame.psm) + { + continue; + } + + std::vector candidatePixels; + if (!copyFrameToHostRgbaUnlocked(candidate, + width, + height, + candidatePixels, + preserveAlpha, + true, + true, + 0u, + 0u)) + { + continue; + } + + if (countNonBlackPixels(candidatePixels, width, height) == 0u) + { + continue; + } + + selectedFrame = candidate; + scratch.swap(candidatePixels); + break; + } + } + + return true; + }; + + if (!validCrt1 && !validCrt2) + { + m_hostPresentationFrame.clear(); + m_hostPresentationWidth = 0u; + m_hostPresentationHeight = 0u; + m_hostPresentationDisplayFbp = 0u; + m_hostPresentationSourceFbp = 0u; + m_hostPresentationUsedPreferred = false; + m_hasHostPresentationFrame = false; + return; } + if (validCrt1 && validCrt2) + { + GSFrameReg selectedFrame1{}; + GSFrameReg selectedFrame2{}; + std::vector rc1; + std::vector rc2; + bool usedPreferred1 = false; + bool usedPreferred2 = false; + + const bool copiedCrt1 = copyDisplaySource(displayFrame1, displayOrigin1, width1, height1, false, true, selectedFrame1, rc1, usedPreferred1); + const bool copiedCrt2 = copyDisplaySource(displayFrame2, displayOrigin2, width2, height2, false, true, selectedFrame2, rc2, usedPreferred2); + + if (copiedCrt1 && copiedCrt2) + { + const uint32_t width = std::max(width1, width2); + const uint32_t height = std::max(height1, height2); + const uint8_t bgR = static_cast(m_privRegs->bgcolor & 0xFFu); + const uint8_t bgG = static_cast((m_privRegs->bgcolor >> 8) & 0xFFu); + const uint8_t bgB = static_cast((m_privRegs->bgcolor >> 16) & 0xFFu); + const uint8_t bgA = pmode.alp; + + std::vector merged(kHostFrameWidth * kHostFrameHeight * 4u, 0u); + for (uint32_t y = 0; y < height; ++y) + { + uint8_t *dstRow = merged.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width; ++x) + { + dstRow[x * 4u + 0u] = bgR; + dstRow[x * 4u + 1u] = bgG; + dstRow[x * 4u + 2u] = bgB; + dstRow[x * 4u + 3u] = bgA; + } + } + + if (!pmode.slbg) + { + for (uint32_t y = 0; y < height2; ++y) + { + const uint8_t *srcRow = rc2.data() + (y * kHostFrameWidth * 4u); + uint8_t *dstRow = merged.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width2; ++x) + { + dstRow[x * 4u + 0u] = srcRow[x * 4u + 0u]; + dstRow[x * 4u + 1u] = srcRow[x * 4u + 1u]; + dstRow[x * 4u + 2u] = srcRow[x * 4u + 2u]; + dstRow[x * 4u + 3u] = srcRow[x * 4u + 3u]; + } + } + } + + for (uint32_t y = 0; y < height1; ++y) + { + const uint8_t *srcRow = rc1.data() + (y * kHostFrameWidth * 4u); + uint8_t *dstRow = merged.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width1; ++x) + { + const uint8_t srcR = srcRow[x * 4u + 0u]; + const uint8_t srcG = srcRow[x * 4u + 1u]; + const uint8_t srcB = srcRow[x * 4u + 2u]; + const uint8_t srcA = srcRow[x * 4u + 3u]; + const uint8_t dstR = dstRow[x * 4u + 0u]; + const uint8_t dstG = dstRow[x * 4u + 1u]; + const uint8_t dstB = dstRow[x * 4u + 2u]; + const uint8_t dstA = dstRow[x * 4u + 3u]; + const uint32_t factor = pmode.mmod + ? static_cast(pmode.alp) + : std::min(255u, static_cast(srcA) * 2u); + + dstRow[x * 4u + 0u] = blendPresentationChannel(srcR, dstR, factor); + dstRow[x * 4u + 1u] = blendPresentationChannel(srcG, dstG, factor); + dstRow[x * 4u + 2u] = blendPresentationChannel(srcB, dstB, factor); + dstRow[x * 4u + 3u] = pmode.amod ? dstA : srcA; + } + } + + for (uint32_t y = 0; y < height; ++y) + { + uint8_t *row = merged.data() + (y * kHostFrameWidth * 4u); + for (uint32_t x = 0; x < width; ++x) + { + row[x * 4u + 3u] = 255u; + } + } + + if (applyFieldMode) + { + applyFieldPresentation(merged, width, height, oddField); + } + + m_hostPresentationFrame.swap(merged); + m_hostPresentationWidth = width; + m_hostPresentationHeight = height; + m_hostPresentationDisplayFbp = displayFrame1.fbp; + m_hostPresentationSourceFbp = selectedFrame1.fbp; + m_hostPresentationUsedPreferred = false; + m_hasHostPresentationFrame = true; + return; + } + } + + const GSFrameReg &displayFrame = validCrt1 ? displayFrame1 : displayFrame2; + const uint32_t width = validCrt1 ? width1 : width2; + const uint32_t height = validCrt1 ? height1 : height2; + + GSFrameReg selectedFrame = displayFrame; + std::vector scratch; + bool usedPreferred = false; + const GSDisplayReadOrigin &displayOrigin = validCrt1 ? displayOrigin1 : displayOrigin2; + if (!copyDisplaySource(displayFrame, displayOrigin, width, height, true, false, selectedFrame, scratch, usedPreferred)) + { + m_hostPresentationFrame.clear(); + m_hostPresentationWidth = 0u; + m_hostPresentationHeight = 0u; + m_hostPresentationDisplayFbp = displayFrame.fbp; + m_hostPresentationSourceFbp = 0u; + m_hostPresentationUsedPreferred = false; + m_hasHostPresentationFrame = false; + return; + } + + if (applyFieldMode) + { + applyFieldPresentation(scratch, width, height, oddField); + } + + normalizePresentationAlpha(scratch, width, height); + + m_hostPresentationFrame.swap(scratch); + m_hostPresentationWidth = width; + m_hostPresentationHeight = height; + m_hostPresentationDisplayFbp = displayFrame.fbp; + m_hostPresentationSourceFbp = selectedFrame.fbp; + m_hostPresentationUsedPreferred = usedPreferred; + m_hasHostPresentationFrame = true; +} + +bool GS::copyLatchedHostPresentationFrame(std::vector &outPixels, + uint32_t &outWidth, + uint32_t &outHeight, + uint32_t *outDisplayFbp, + uint32_t *outSourceFbp, + bool *outUsedPreferred) const +{ + std::lock_guard lock(m_stateMutex); + if (!m_hasHostPresentationFrame || m_hostPresentationFrame.empty()) + { + outPixels.clear(); + outWidth = 0u; + outHeight = 0u; + if (outDisplayFbp) + *outDisplayFbp = 0u; + if (outSourceFbp) + *outSourceFbp = 0u; + if (outUsedPreferred) + *outUsedPreferred = false; + return false; + } + + outWidth = m_hostPresentationWidth; + outHeight = m_hostPresentationHeight; + if (outDisplayFbp) + *outDisplayFbp = m_hostPresentationDisplayFbp; + if (outSourceFbp) + *outSourceFbp = m_hostPresentationSourceFbp; + if (outUsedPreferred) + *outUsedPreferred = m_hostPresentationUsedPreferred; + + const size_t packedRowBytes = static_cast(outWidth) * 4u; + outPixels.assign(packedRowBytes * static_cast(outHeight), 0u); + if (outWidth != 0u && outHeight != 0u) + { + const size_t sourceRowBytes = static_cast(kHostFrameWidth) * 4u; + for (uint32_t y = 0; y < outHeight; ++y) + { + const size_t srcOffset = static_cast(y) * sourceRowBytes; + const size_t dstOffset = static_cast(y) * packedRowBytes; + if (srcOffset + packedRowBytes > m_hostPresentationFrame.size() || + dstOffset + packedRowBytes > outPixels.size()) + { + outPixels.clear(); + outWidth = 0u; + outHeight = 0u; + if (outDisplayFbp) + *outDisplayFbp = 0u; + if (outSourceFbp) + *outSourceFbp = 0u; + if (outUsedPreferred) + *outUsedPreferred = false; + return false; + } + + std::memcpy(outPixels.data() + dstOffset, + m_hostPresentationFrame.data() + srcOffset, + packedRowBytes); + } + } + return true; +} + +void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes) +{ + std::lock_guard lock(m_stateMutex); + if (!data || sizeBytes < 16 || !m_vram) + return; + + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed); + if (packetIndex < 48u) + { + const uint64_t tagLo = loadLE64(data); + const uint32_t nloop = static_cast(tagLo & 0x7FFFu); + const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); + uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); + if (nreg == 0u) + nreg = 16u; + RUNTIME_LOG("[gs:gif] idx=" << packetIndex + << " size=" << sizeBytes + << " nloop=" << nloop + << " flg=" << static_cast(flg) + << " nreg=" << nreg + << " ctx0fbp=" << m_ctx[0].frame.fbp + << " ctx1fbp=" << m_ctx[1].frame.fbp + << std::endl); + } + }); + if (sizeBytes >= 16) { const uint64_t tagLo = loadLE64(data); @@ -413,19 +1246,21 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) uint32_t z = static_cast((hi >> 4) & 0xFFFFFF); uint8_t f = static_cast((hi >> 36) & 0xFF); bool adk = ((hi >> 47) & 1) != 0; - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - std::cout << "[gs:packed-xyzf] idx=" << debugIndex - << " x=" << x - << " y=" << y - << " z=0x" << std::hex << z - << std::dec - << " fog=" << static_cast(f) - << " kick=" << static_cast(!adk ? 1u : 0u) - << " prim=" << static_cast(m_prim.type) - << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyzf] idx=" << debugIndex + << " x=" << x + << " y=" << y + << " z=0x" << std::hex << z + << std::dec + << " fog=" << static_cast(f) + << " kick=" << static_cast(!adk ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(x) / 16.0f; vtx.y = static_cast(y) / 16.0f; @@ -449,18 +1284,20 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) uint16_t y = static_cast((lo >> 32) & 0xFFFF); uint32_t z = static_cast(hi & 0xFFFFFFFF); bool adk = ((hi >> 47) & 1) != 0; - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - std::cout << "[gs:packed-xyz] idx=" << debugIndex - << " x=" << x - << " y=" << y - << " z=0x" << std::hex << z - << std::dec - << " kick=" << static_cast(!adk ? 1u : 0u) - << " prim=" << static_cast(m_prim.type) - << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyz] idx=" << debugIndex + << " x=" << x + << " y=" << y + << " z=0x" << std::hex << z + << std::dec + << " kick=" << static_cast(!adk ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(x) / 16.0f; vtx.y = static_cast(y) / 16.0f; @@ -483,16 +1320,18 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) break; case 0x0C: { - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - std::cout << "[gs:packed-xyzf3] idx=" << debugIndex - << " x=" << static_cast(lo & 0xFFFFu) - << " y=" << static_cast((lo >> 32) & 0xFFFFu) - << " kick=0" - << " prim=" << static_cast(m_prim.type) - << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyzf3] idx=" << debugIndex + << " x=" << static_cast(lo & 0xFFFFu) + << " y=" << static_cast((lo >> 32) & 0xFFFFu) + << " kick=0" + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(lo & 0xFFFF) / 16.0f; vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; @@ -512,16 +1351,18 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) } case 0x0D: { - const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 64u) - { - std::cout << "[gs:packed-xyz3] idx=" << debugIndex - << " x=" << static_cast(lo & 0xFFFFu) - << " y=" << static_cast((lo >> 32) & 0xFFFFu) - << " kick=0" - << " prim=" << static_cast(m_prim.type) - << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 64u) + { + RUNTIME_LOG("[gs:packed-xyz3] idx=" << debugIndex + << " x=" << static_cast(lo & 0xFFFFu) + << " y=" << static_cast((lo >> 32) & 0xFFFFu) + << " kick=0" + << " prim=" << static_cast(m_prim.type) + << std::endl); + } + }); GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(lo & 0xFFFF) / 16.0f; vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; @@ -555,6 +1396,7 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi) void GS::writeRegister(uint8_t regAddr, uint64_t value) { + std::lock_guard lock(m_stateMutex); const bool interestingReg = regAddr == GS_REG_PRIM || regAddr == GS_REG_RGBAQ || @@ -566,6 +1408,10 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) regAddr == GS_REG_XYZF3 || regAddr == GS_REG_TEX0_1 || regAddr == GS_REG_TEX0_2 || + regAddr == GS_REG_TEX2_1 || + regAddr == GS_REG_TEX2_2 || + regAddr == GS_REG_TEXCLUT || + regAddr == GS_REG_TEXA || regAddr == GS_REG_XYOFFSET_1 || regAddr == GS_REG_XYOFFSET_2 || regAddr == GS_REG_SCISSOR_1 || @@ -581,18 +1427,20 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) regAddr == GS_REG_TRXREG || regAddr == GS_REG_TRXDIR; - if (interestingReg) - { - const uint32_t debugIndex = s_debugGsRegisterCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 128u) + PS2_IF_AGRESSIVE_LOGS({ + if (interestingReg) { - std::cout << "[gs:reg] idx=" << debugIndex - << " reg=0x" << std::hex << static_cast(regAddr) - << " value=0x" << value - << std::dec - << std::endl; + const uint32_t debugIndex = s_debugGsRegisterCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 128u) + { + RUNTIME_LOG("[gs:reg] idx=" << debugIndex + << " reg=0x" << std::hex << static_cast(regAddr) + << " value=0x" << value + << std::dec + << std::endl); + } } - } + }); const bool isCopyRelevantReg = regAddr == GS_REG_PRIM || @@ -600,21 +1448,24 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) regAddr == GS_REG_TEX1_2 || regAddr == GS_REG_ALPHA_2 || regAddr == GS_REG_TEST_2 || + regAddr == GS_REG_PABE || regAddr == GS_REG_FRAME_2 || regAddr == GS_REG_XYOFFSET_2 || regAddr == GS_REG_SCISSOR_2; - if (isCopyRelevantReg && - s_debugCopyRegCount.fetch_add(1u, std::memory_order_relaxed) < 64u) - { - std::cout << "[gs:copy-reg] reg=0x" - << std::hex << static_cast(regAddr) - << " value=0x" << value - << std::dec - << " primCtxt=" << static_cast(m_prim.ctxt) - << " ctx0fbp=" << m_ctx[0].frame.fbp - << " ctx1fbp=" << m_ctx[1].frame.fbp - << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + if (isCopyRelevantReg && + s_debugCopyRegCount.fetch_add(1u, std::memory_order_relaxed) < 64u) + { + RUNTIME_LOG("[gs:copy-reg] reg=0x" + << std::hex << static_cast(regAddr) + << " value=0x" << value + << std::dec + << " primCtxt=" << static_cast(m_prim.ctxt) + << " ctx0fbp=" << m_ctx[0].frame.fbp + << " ctx1fbp=" << m_ctx[1].frame.fbp + << std::endl); + } + }); switch (regAddr) { @@ -737,7 +1588,17 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) } case GS_REG_TEX2_1: case GS_REG_TEX2_2: + { + int ci = (regAddr == GS_REG_TEX2_2) ? 1 : 0; + auto &t = m_ctx[ci].tex0; + t.psm = static_cast((value >> 20) & 0x3F); + t.cbp = static_cast((value >> 37) & 0x3FFF); + t.cpsm = static_cast((value >> 51) & 0xF); + t.csm = static_cast((value >> 55) & 0x1); + t.csa = static_cast((value >> 56) & 0x1F); + t.cld = static_cast((value >> 61) & 0x7); break; + } case GS_REG_XYOFFSET_1: case GS_REG_XYOFFSET_2: { @@ -762,6 +1623,11 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) m_prim.fix = ((value >> 10) & 1) != 0; } break; + case GS_REG_TEXCLUT: + m_texclut.cbw = static_cast(value & 0x3Fu); + m_texclut.cou = static_cast((value >> 6) & 0x3Fu); + m_texclut.cov = static_cast((value >> 12) & 0x3FFu); + break; case GS_REG_SCISSOR_1: case GS_REG_SCISSOR_2: { @@ -858,14 +1724,15 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) processImageData(buf, 8); break; } + case GS_REG_PABE: + m_pabe = (value & 1u) != 0u; + break; case GS_REG_TEXFLUSH: - case GS_REG_TEXCLUT: case GS_REG_SCANMSK: case GS_REG_FOGCOL: case GS_REG_DIMX: case GS_REG_DTHE: case GS_REG_COLCLAMP: - case GS_REG_PABE: case GS_REG_MIPTBP1_1: case GS_REG_MIPTBP1_2: case GS_REG_MIPTBP2_1: @@ -873,17 +1740,22 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) break; case GS_REG_TEXA: { - const uint32_t texaIndex = s_debugTexaWriteCount.fetch_add(1u, std::memory_order_relaxed); - if (texaIndex < 24u) - { - std::cout << "[gs:texa] idx=" << texaIndex - << " value=0x" << std::hex << value - << " ta0=0x" << ((value >> 0) & 0xFFu) - << " aem=" << ((value >> 15) & 0x1u) - << " ta1=0x" << ((value >> 32) & 0xFFu) - << std::dec - << std::endl; - } + m_texa.ta0 = static_cast(value & 0xFFu); + m_texa.aem = ((value >> 15) & 0x1u) != 0u; + m_texa.ta1 = static_cast((value >> 32) & 0xFFu); + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t texaIndex = s_debugTexaWriteCount.fetch_add(1u, std::memory_order_relaxed); + if (texaIndex < 24u) + { + RUNTIME_LOG("[gs:texa] idx=" << texaIndex + << " value=0x" << std::hex << value + << " ta0=0x" << ((value >> 0) & 0xFFu) + << " aem=" << ((value >> 15) & 0x1u) + << " ta1=0x" << ((value >> 32) & 0xFFu) + << std::dec + << std::endl); + } + }); break; } case GS_REG_SIGNAL: @@ -925,6 +1797,14 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value) if (m_privRegs) m_privRegs->display1 = value; break; + case 0x5b: + if (m_privRegs) + m_privRegs->dispfb2 = value; + break; + case 0x5c: + if (m_privRegs) + m_privRegs->display2 = value; + break; case 0x5f: if (m_privRegs) m_privRegs->bgcolor = value; @@ -957,42 +1837,45 @@ void GS::performLocalToLocalTransfer() const uint32_t ssay = m_trxpos.ssay; const uint32_t dsax = m_trxpos.dsax; const uint32_t dsay = m_trxpos.dsay; + const GSTransferTraversal traversal = decodeTransferTraversal(m_trxpos.dir); const bool formatAware = (spsm == dpsm) && supportsFormatAwareLocalCopy(spsm); - if ((spsm == GS_PSM_T4 || dpsm == GS_PSM_T4) && - s_debugLocalCopyCount.fetch_add(1u, std::memory_order_relaxed) < 96u) + if (rrw == 0u || rrh == 0u) { - std::cout << "[gs:l2l] sbp=" << sbp - << " dbp=" << dbp - << " sbw=" << static_cast(sbw) - << " dbw=" << static_cast(dbw) - << " spsm=0x" << std::hex << static_cast(spsm) - << " dpsm=0x" << static_cast(dpsm) << std::dec - << " ss=(" << ssax << "," << ssay << ")" - << " ds=(" << dsax << "," << dsay << ")" - << " rr=(" << rrw << "," << rrh << ")" - << " formatAware=" << (formatAware ? 1 : 0) << std::endl; + return; } - if (formatAware) - { - std::vector staged; - staged.reserve(static_cast(rrw) * static_cast(rrh)); - - for (uint32_t row = 0; row < rrh; ++row) + PS2_IF_AGRESSIVE_LOGS({ + if ((spsm == GS_PSM_T4 || dpsm == GS_PSM_T4) && + s_debugLocalCopyCount.fetch_add(1u, std::memory_order_relaxed) < 96u) { - for (uint32_t col = 0; col < rrw; ++col) - { - staged.push_back(readTransferPixel(m_vram, m_vramSize, sbp, sbw, spsm, ssax + col, ssay + row)); - } + RUNTIME_LOG("[gs:l2l] sbp=" << sbp + << " dbp=" << dbp + << " sbw=" << static_cast(sbw) + << " dbw=" << static_cast(dbw) + << " spsm=0x" << std::hex << static_cast(spsm) + << " dpsm=0x" << static_cast(dpsm) << std::dec + << " ss=(" << ssax << "," << ssay << ")" + << " ds=(" << dsax << "," << dsay << ")" + << " rr=(" << rrw << "," << rrh << ")" + << " dir=" << static_cast(m_trxpos.dir) + << " formatAware=" << (formatAware ? 1 : 0) << std::endl); } + }); - size_t idx = 0; + if (formatAware) + { for (uint32_t row = 0; row < rrh; ++row) { - for (uint32_t col = 0; col < rrw; ++col, ++idx) + const uint32_t srcY = transferCoord(ssay, rrh, row, traversal.reverseY); + const uint32_t dstY = transferCoord(dsay, rrh, row, traversal.reverseY); + for (uint32_t col = 0; col < rrw; ++col) { - writeTransferPixel(m_vram, m_vramSize, dbp, dbw, dpsm, dsax + col, dsay + row, staged[idx]); + const uint32_t srcX = transferCoord(ssax, rrw, col, traversal.reverseX); + const uint32_t dstX = transferCoord(dsax, rrw, col, traversal.reverseX); + const uint32_t pixel = + readTransferPixel(m_vram, m_vramSize, sbp, sbw, spsm, srcX, srcY); + writeTransferPixel(m_vram, m_vramSize, dbp, dbw, dpsm, dstX, dstY, pixel); } } } @@ -1009,26 +1892,25 @@ void GS::performLocalToLocalTransfer() const uint32_t srcStride = static_cast(sbw) * 64u * srcBpp; const uint32_t dstStride = static_cast(dbw) * 64u * dstBpp; const uint32_t copyBpp = (srcBpp < dstBpp) ? srcBpp : dstBpp; - const uint32_t rowBytes = rrw * copyBpp; - if (dstBase > srcBase) - { - for (int row = static_cast(rrh) - 1; row >= 0; --row) - { - const uint32_t srcOff = srcBase + (ssay + static_cast(row)) * srcStride + ssax * srcBpp; - const uint32_t dstOff = dstBase + (dsay + static_cast(row)) * dstStride + dsax * dstBpp; - if (srcOff + rowBytes <= m_vramSize && dstOff + rowBytes <= m_vramSize) - std::memmove(m_vram + dstOff, m_vram + srcOff, rowBytes); - } - } - else + uint8_t pixelBytes[4] = {}; + for (uint32_t row = 0; row < rrh; ++row) { - for (uint32_t row = 0; row < rrh; ++row) + const uint32_t srcY = transferCoord(ssay, rrh, row, traversal.reverseY); + const uint32_t dstY = transferCoord(dsay, rrh, row, traversal.reverseY); + for (uint32_t col = 0; col < rrw; ++col) { - const uint32_t srcOff = srcBase + (ssay + row) * srcStride + ssax * srcBpp; - const uint32_t dstOff = dstBase + (dsay + row) * dstStride + dsax * dstBpp; - if (srcOff + rowBytes <= m_vramSize && dstOff + rowBytes <= m_vramSize) - std::memmove(m_vram + dstOff, m_vram + srcOff, rowBytes); + const uint32_t srcX = transferCoord(ssax, rrw, col, traversal.reverseX); + const uint32_t dstX = transferCoord(dsax, rrw, col, traversal.reverseX); + const uint32_t srcOff = srcBase + srcY * srcStride + srcX * srcBpp; + const uint32_t dstOff = dstBase + dstY * dstStride + dstX * dstBpp; + if (srcOff + copyBpp > m_vramSize || dstOff + copyBpp > m_vramSize) + { + continue; + } + + std::memcpy(pixelBytes, m_vram + srcOff, copyBpp); + std::memcpy(m_vram + dstOff, pixelBytes, copyBpp); } } } @@ -1045,15 +1927,17 @@ void GS::vertexKick(bool drawing) ++m_vtxCount; ++m_vtxIndex; - const uint32_t debugIndex = s_debugGsVertexKickCount.fetch_add(1, std::memory_order_relaxed); - if (debugIndex < 96u) - { - std::cout << "[gs:kick] idx=" << debugIndex - << " drawing=" << static_cast(drawing ? 1u : 0u) - << " prim=" << static_cast(m_prim.type) - << " vtxCount=" << m_vtxCount - << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t debugIndex = s_debugGsVertexKickCount.fetch_add(1, std::memory_order_relaxed); + if (debugIndex < 96u) + { + RUNTIME_LOG("[gs:kick] idx=" << debugIndex + << " drawing=" << static_cast(drawing ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << " vtxCount=" << m_vtxCount + << std::endl); + } + }); if (!drawing) return; @@ -1217,14 +2101,11 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes) } else if (dpsm == GS_PSM_CT24 || dpsm == GS_PSM_Z24) { - uint32_t storageBpp = 4; uint32_t transferBpp = 3; - uint32_t storageStride = stridePixels * storageBpp; uint32_t offset = 0; while (offset < sizeBytes && m_hwregY < rrh) { - uint32_t dstY = dsay + m_hwregY; uint32_t pixelsLeft = rrw - m_hwregX; uint32_t srcBytesLeft = pixelsLeft * transferBpp; uint32_t bytesAvail = sizeBytes - offset; @@ -1235,15 +2116,20 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes) if (pixelsToCopy == 0) break; - uint32_t dstOff = base + dstY * storageStride + (dsax + m_hwregX) * storageBpp; - if (dstOff + pixelsToCopy * storageBpp <= m_vramSize && pixelsToCopy > 0) + if (pixelsToCopy > 0) { for (uint32_t p = 0; p < pixelsToCopy; ++p) { - m_vram[dstOff + p * 4 + 0] = data[offset + p * 3 + 0]; - m_vram[dstOff + p * 4 + 1] = data[offset + p * 3 + 1]; - m_vram[dstOff + p * 4 + 2] = data[offset + p * 3 + 2]; - m_vram[dstOff + p * 4 + 3] = 0x80; + const uint32_t vx = dsax + m_hwregX + p; + const uint32_t vy = dsay + m_hwregY; + const uint32_t dstOff = GSPSMCT32::addrPSMCT32(dbp, dbw, vx, vy); + if (dstOff + 4u <= m_vramSize) + { + m_vram[dstOff + 0u] = data[offset + p * 3u + 0u]; + m_vram[dstOff + 1u] = data[offset + p * 3u + 1u]; + m_vram[dstOff + 2u] = data[offset + p * 3u + 2u]; + m_vram[dstOff + 3u] = 0x80u; + } } } @@ -1274,11 +2160,25 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes) if (bytesLeft > bytesAvail) bytesLeft = (bytesAvail / bytesPerPixel) * bytesPerPixel; - uint32_t dstOff = base + dstY * strideBytes + (dsax + m_hwregX) * bytesPerPixel; - if (dstOff + bytesLeft <= m_vramSize && bytesLeft > 0) - std::memcpy(m_vram + dstOff, data + offset, bytesLeft); - uint32_t pixelsCopied = bytesLeft / bytesPerPixel; + if ((dpsm == GS_PSM_CT32 || dpsm == GS_PSM_Z32) && pixelsCopied > 0) + { + for (uint32_t p = 0; p < pixelsCopied; ++p) + { + const uint32_t vx = dsax + m_hwregX + p; + const uint32_t vy = dstY; + const uint32_t dstOff = GSPSMCT32::addrPSMCT32(dbp, dbw, vx, vy); + if (dstOff + 4u <= m_vramSize) + std::memcpy(m_vram + dstOff, data + offset + p * 4u, 4u); + } + } + else + { + uint32_t dstOff = base + dstY * strideBytes + (dsax + m_hwregX) * bytesPerPixel; + if (dstOff + bytesLeft <= m_vramSize && bytesLeft > 0) + std::memcpy(m_vram + dstOff, data + offset, bytesLeft); + } + offset += bytesLeft; m_hwregX += pixelsCopied; if (m_hwregX >= rrw) @@ -1354,16 +2254,14 @@ void GS::performLocalToHostToBuffer() } else if (spsm == GS_PSM_CT24 || spsm == GS_PSM_Z24) { - uint32_t storageBpp = 4; uint32_t transferBpp = 3; - uint32_t storageStride = stridePixels * storageBpp; m_localToHostBuffer.reserve(rrw * rrh * transferBpp); for (uint32_t y = 0; y < rrh; ++y) { for (uint32_t x = 0; x < rrw; ++x) { - uint32_t srcOff = base + (ssay + y) * storageStride + (ssax + x) * storageBpp; + uint32_t srcOff = GSPSMCT32::addrPSMCT32(sbp, sbw, ssax + x, ssay + y); if (srcOff + 4 <= m_vramSize) { m_localToHostBuffer.push_back(m_vram[srcOff + 0]); @@ -1384,18 +2282,48 @@ void GS::performLocalToHostToBuffer() for (uint32_t y = 0; y < rrh; ++y) { - uint32_t srcOff = base + (ssay + y) * strideBytes + ssax * bytesPerPixel; - if (srcOff + rowBytes <= m_vramSize) + if (spsm == GS_PSM_CT32 || spsm == GS_PSM_Z32) + { + for (uint32_t x = 0; x < rrw; ++x) + { + const uint32_t srcOff = GSPSMCT32::addrPSMCT32(sbp, sbw, ssax + x, ssay + y); + if (srcOff + 4u <= m_vramSize) + { + m_localToHostBuffer.push_back(m_vram[srcOff + 0u]); + m_localToHostBuffer.push_back(m_vram[srcOff + 1u]); + m_localToHostBuffer.push_back(m_vram[srcOff + 2u]); + m_localToHostBuffer.push_back(m_vram[srcOff + 3u]); + } + } + } + else { - for (uint32_t i = 0; i < rowBytes; ++i) - m_localToHostBuffer.push_back(m_vram[srcOff + i]); + uint32_t srcOff = base + (ssay + y) * strideBytes + ssax * bytesPerPixel; + if (srcOff + rowBytes <= m_vramSize) + { + for (uint32_t i = 0; i < rowBytes; ++i) + m_localToHostBuffer.push_back(m_vram[srcOff + i]); + } } } } } +bool GS::clearFramebufferContext(uint32_t contextIndex, uint32_t rgba) +{ + std::lock_guard lock(m_stateMutex); + return clearFramebufferRect(m_vram, m_vramSize, m_ctx[(contextIndex != 0u) ? 1 : 0], rgba); +} + +bool GS::clearActiveFramebuffer(uint32_t rgba) +{ + std::lock_guard lock(m_stateMutex); + return clearFramebufferRect(m_vram, m_vramSize, activeContext(), rgba); +} + uint32_t GS::consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) { + std::lock_guard lock(m_stateMutex); if (!dst || maxBytes == 0) return 0; size_t avail = m_localToHostBuffer.size() - m_localToHostReadPos; diff --git a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp index f6658ee9..de26dc60 100644 --- a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp @@ -1,8 +1,11 @@ -#include "ps2_gs_rasterizer.h" -#include "ps2_gs_gpu.h" -#include "ps2_gs_common.h" -#include "ps2_gs_psmt4.h" -#include "ps2_gs_psmt8.h" +#include "runtime/ps2_gs_rasterizer.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_gs_common.h" +#include "runtime/ps2_gs_psmct16.h" +#include "runtime/ps2_gs_psmct32.h" +#include "runtime/ps2_gs_psmt4.h" +#include "runtime/ps2_gs_psmt8.h" +#include "ps2_log.h" #include #include #include @@ -30,6 +33,36 @@ namespace return r | (g << 8) | (b << 16) | (a << 24); } + uint32_t applyTexa(const GSTexaReg &texa, uint8_t psm, uint32_t texel) + { + if (psm == GS_PSM_CT32) + return texel; + + const uint8_t r = static_cast(texel & 0xFFu); + const uint8_t g = static_cast((texel >> 8) & 0xFFu); + const uint8_t b = static_cast((texel >> 16) & 0xFFu); + const bool rgbZero = r == 0u && g == 0u && b == 0u; + uint8_t a = static_cast((texel >> 24) & 0xFFu); + + switch (psm) + { + case GS_PSM_CT24: + a = (texa.aem && rgbZero) ? 0u : texa.ta0; + break; + case GS_PSM_CT16: + case GS_PSM_CT16S: + if ((a & 0x80u) != 0u) + a = texa.ta1; + else + a = (texa.aem && rgbZero) ? 0u : texa.ta0; + break; + default: + break; + } + + return (texel & 0x00FFFFFFu) | (static_cast(a) << 24); + } + uint16_t encodePSMCT16(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return static_cast(((r >> 3) & 0x1Fu) | @@ -38,11 +71,27 @@ namespace ((a >= 0x40u) ? 0x8000u : 0u)); } + uint32_t addrPSMCT16Family(uint32_t basePtr, uint32_t width, uint8_t psm, uint32_t x, uint32_t y) + { + switch (psm) + { + case GS_PSM_CT16: + return GSPSMCT16::addrPSMCT16(basePtr, width, x, y); + case GS_PSM_CT16S: + return GSPSMCT16::addrPSMCT16S(basePtr, width, x, y); + case GS_PSM_Z16: + return GSPSMCT16::addrPSMZ16(basePtr, width, x, y); + case GS_PSM_Z16S: + return GSPSMCT16::addrPSMZ16S(basePtr, width, x, y); + default: + return 0u; + } + } + std::atomic s_debugPrimitiveCount{0}; std::atomic s_debugPixelCount{0}; std::atomic s_debugContext1PrimitiveCount{0}; std::atomic s_debugFbp150PixelCount{0}; - bool passesAlphaTest(uint64_t testReg, uint8_t alpha) { if ((testReg & 0x1u) == 0u) @@ -118,47 +167,40 @@ namespace uint8_t tb, uint8_t ta) { - const bool useTextureRgb = tex.tcc != 0u; - TextureCombineResult out{vr, vg, vb, ta}; + const bool textureHasAlpha = tex.tcc != 0u; + TextureCombineResult out{tr, tg, tb, textureHasAlpha ? ta : va}; switch (tex.tfx) { - case 0: - if (useTextureRgb) - { - out.r = clampU8((tr * vr) >> 7); - out.g = clampU8((tg * vg) >> 7); - out.b = clampU8((tb * vb) >> 7); - } - out.a = clampU8((ta * va) >> 7); + case 0: // MODULATE + out.r = clampU8((tr * vr) >> 7); + out.g = clampU8((tg * vg) >> 7); + out.b = clampU8((tb * vb) >> 7); + out.a = textureHasAlpha ? clampU8((ta * va) >> 7) : va; break; - case 1: - if (useTextureRgb) - { - out.r = tr; - out.g = tg; - out.b = tb; - } - out.a = ta; + case 1: // DECAL + out.r = tr; + out.g = tg; + out.b = tb; + out.a = textureHasAlpha ? ta : va; break; - case 2: - case 3: - if (useTextureRgb) - { - out.r = clampU8((tr * vr) >> 7); - out.g = clampU8((tg * vg) >> 7); - out.b = clampU8((tb * vb) >> 7); - } - out.a = ta; + case 2: // HIGHLIGHT + out.r = clampU8(((tr * vr) >> 7) + va); + out.g = clampU8(((tg * vg) >> 7) + va); + out.b = clampU8(((tb * vb) >> 7) + va); + out.a = textureHasAlpha ? clampU8(ta + va) : va; + break; + case 3: // HIGHLIGHT2 + out.r = clampU8(((tr * vr) >> 7) + va); + out.g = clampU8(((tg * vg) >> 7) + va); + out.b = clampU8(((tb * vb) >> 7) + va); + out.a = textureHasAlpha ? ta : va; break; default: - if (useTextureRgb) - { - out.r = tr; - out.g = tg; - out.b = tb; - } - out.a = ta; + out.r = tr; + out.g = tg; + out.b = tb; + out.a = textureHasAlpha ? ta : va; break; } @@ -187,102 +229,134 @@ namespace return clutIndex; } -} -void GSRasterizer::drawPrimitive(GS *gs) -{ - const uint32_t primitiveIndex = s_debugPrimitiveCount.fetch_add(1, std::memory_order_relaxed); - if (primitiveIndex < 64u) + bool tex1UsesLinearFilter(uint64_t tex1) { - const auto &ctx = gs->activeContext(); - std::cout << "[gs:prim] idx=" << primitiveIndex - << " type=" << static_cast(gs->m_prim.type) - << " tme=" << static_cast(gs->m_prim.tme) - << " abe=" << static_cast(gs->m_prim.abe) - << " fst=" << static_cast(gs->m_prim.fst) - << " ctxt=" << static_cast(gs->m_prim.ctxt) - << " fbp=" << ctx.frame.fbp - << " fbw=" << ctx.frame.fbw - << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec - << " tex0=(" - << "tbp0=" << ctx.tex0.tbp0 - << " tbw=" << static_cast(ctx.tex0.tbw) - << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec - << " tw=" << static_cast(ctx.tex0.tw) - << " th=" << static_cast(ctx.tex0.th) - << " tcc=" << static_cast(ctx.tex0.tcc) - << " tfx=" << static_cast(ctx.tex0.tfx) - << " cbp=" << ctx.tex0.cbp - << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec - << " csm=" << static_cast(ctx.tex0.csm) - << " csa=" << static_cast(ctx.tex0.csa) - << ")" - << " ofx=" << (ctx.xyoffset.ofx >> 4) - << " ofy=" << (ctx.xyoffset.ofy >> 4) - << " scissor=(" << ctx.scissor.x0 - << "," << ctx.scissor.y0 - << ")-(" << ctx.scissor.x1 - << "," << ctx.scissor.y1 << ")" - << " test=0x" << std::hex << ctx.test - << " alpha=0x" << ctx.alpha - << std::dec - << " v0=(" << gs->m_vtxQueue[0].x << "," << gs->m_vtxQueue[0].y << ")" - << " uv0=(" << (gs->m_vtxQueue[0].u >> 4) << "," << (gs->m_vtxQueue[0].v >> 4) << ")" - << " stq0=(" << gs->m_vtxQueue[0].s << "," << gs->m_vtxQueue[0].t << "," << gs->m_vtxQueue[0].q << ")" - << " v1=(" << gs->m_vtxQueue[1].x << "," << gs->m_vtxQueue[1].y << ")" - << " uv1=(" << (gs->m_vtxQueue[1].u >> 4) << "," << (gs->m_vtxQueue[1].v >> 4) << ")" - << " stq1=(" << gs->m_vtxQueue[1].s << "," << gs->m_vtxQueue[1].t << "," << gs->m_vtxQueue[1].q << ")" - << " v2=(" << gs->m_vtxQueue[2].x << "," << gs->m_vtxQueue[2].y << ")" - << " uv2=(" << (gs->m_vtxQueue[2].u >> 4) << "," << (gs->m_vtxQueue[2].v >> 4) << ")" - << " stq2=(" << gs->m_vtxQueue[2].s << "," << gs->m_vtxQueue[2].t << "," << gs->m_vtxQueue[2].q << ")" - << " rgba0=(" << static_cast(gs->m_vtxQueue[0].r) << "," - << static_cast(gs->m_vtxQueue[0].g) << "," - << static_cast(gs->m_vtxQueue[0].b) << "," - << static_cast(gs->m_vtxQueue[0].a) << ")" - << " rgba1=(" << static_cast(gs->m_vtxQueue[1].r) << "," - << static_cast(gs->m_vtxQueue[1].g) << "," - << static_cast(gs->m_vtxQueue[1].b) << "," - << static_cast(gs->m_vtxQueue[1].a) << ")" - << " rgba2=(" << static_cast(gs->m_vtxQueue[2].r) << "," - << static_cast(gs->m_vtxQueue[2].g) << "," - << static_cast(gs->m_vtxQueue[2].b) << "," - << static_cast(gs->m_vtxQueue[2].a) << ")" - << std::endl; + const uint8_t mmag = static_cast((tex1 >> 5) & 0x1u); + const uint8_t mmin = static_cast((tex1 >> 6) & 0x7u); + return mmag != 0u || mmin == 1u || (mmin & 0x4u) != 0u; + } + + uint8_t lerpChannel(uint8_t c00, uint8_t c10, uint8_t c01, uint8_t c11, float fx, float fy) + { + const float top = static_cast(c00) + (static_cast(c10) - static_cast(c00)) * fx; + const float bottom = static_cast(c01) + (static_cast(c11) - static_cast(c01)) * fx; + return clampU8(static_cast(std::lround(top + (bottom - top) * fy))); } +} +void GSRasterizer::drawPrimitive(GS *gs) +{ const auto &ctx = gs->activeContext(); - if ((gs->m_prim.ctxt != 0u || ctx.frame.fbp == 150u) && - s_debugContext1PrimitiveCount.fetch_add(1u, std::memory_order_relaxed) < 32u) - { - std::cout << "[gs:copy-prim]" - << " type=" << static_cast(gs->m_prim.type) - << " tme=" << static_cast(gs->m_prim.tme) - << " abe=" << static_cast(gs->m_prim.abe) - << " fst=" << static_cast(gs->m_prim.fst) - << " ctxt=" << static_cast(gs->m_prim.ctxt) - << " fbp=" << ctx.frame.fbp - << " fbw=" << ctx.frame.fbw - << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec - << " tex0=(" - << "tbp0=" << ctx.tex0.tbp0 - << " tbw=" << static_cast(ctx.tex0.tbw) - << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec - << " tcc=" << static_cast(ctx.tex0.tcc) - << " tfx=" << static_cast(ctx.tex0.tfx) - << " cbp=" << ctx.tex0.cbp - << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec - << " csm=" << static_cast(ctx.tex0.csm) - << " csa=" << static_cast(ctx.tex0.csa) - << ")" - << " ofx=" << (ctx.xyoffset.ofx >> 4) - << " ofy=" << (ctx.xyoffset.ofy >> 4) - << " scissor=(" << ctx.scissor.x0 - << "," << ctx.scissor.y0 - << ")-(" << ctx.scissor.x1 - << "," << ctx.scissor.y1 << ")" - << " test=0x" << std::hex << ctx.test - << " alpha=0x" << ctx.alpha - << std::dec << std::endl; + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t primitiveIndex = s_debugPrimitiveCount.fetch_add(1u, std::memory_order_relaxed); + if (primitiveIndex < 64u) + { + std::cout << "[gs:prim] idx=" << primitiveIndex + << " type=" << static_cast(gs->m_prim.type) + << " tme=" << static_cast(gs->m_prim.tme) + << " abe=" << static_cast(gs->m_prim.abe) + << " fst=" << static_cast(gs->m_prim.fst) + << " ctxt=" << static_cast(gs->m_prim.ctxt) + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " tex0=(" + << "tbp0=" << ctx.tex0.tbp0 + << " tbw=" << static_cast(ctx.tex0.tbw) + << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec + << " tw=" << static_cast(ctx.tex0.tw) + << " th=" << static_cast(ctx.tex0.th) + << " tcc=" << static_cast(ctx.tex0.tcc) + << " tfx=" << static_cast(ctx.tex0.tfx) + << " cbp=" << ctx.tex0.cbp + << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec + << " csm=" << static_cast(ctx.tex0.csm) + << " csa=" << static_cast(ctx.tex0.csa) + << ")" + << " texclut=(" + << "cbw=" << static_cast(gs->m_texclut.cbw) + << " cou=" << static_cast(gs->m_texclut.cou) + << " cov=" << gs->m_texclut.cov + << ")" + << " ofx=" << (ctx.xyoffset.ofx >> 4) + << " ofy=" << (ctx.xyoffset.ofy >> 4) + << " scissor=(" << ctx.scissor.x0 + << "," << ctx.scissor.y0 + << ")-(" << ctx.scissor.x1 + << "," << ctx.scissor.y1 << ")" + << " test=0x" << std::hex << ctx.test + << " alpha=0x" << ctx.alpha + << std::dec + << " v0=(" << gs->m_vtxQueue[0].x << "," << gs->m_vtxQueue[0].y << ")" + << " uv0=(" << (gs->m_vtxQueue[0].u >> 4) << "," << (gs->m_vtxQueue[0].v >> 4) << ")" + << " stq0=(" << gs->m_vtxQueue[0].s << "," << gs->m_vtxQueue[0].t << "," << gs->m_vtxQueue[0].q << ")" + << " v1=(" << gs->m_vtxQueue[1].x << "," << gs->m_vtxQueue[1].y << ")" + << " uv1=(" << (gs->m_vtxQueue[1].u >> 4) << "," << (gs->m_vtxQueue[1].v >> 4) << ")" + << " stq1=(" << gs->m_vtxQueue[1].s << "," << gs->m_vtxQueue[1].t << "," << gs->m_vtxQueue[1].q << ")" + << " v2=(" << gs->m_vtxQueue[2].x << "," << gs->m_vtxQueue[2].y << ")" + << " uv2=(" << (gs->m_vtxQueue[2].u >> 4) << "," << (gs->m_vtxQueue[2].v >> 4) << ")" + << " stq2=(" << gs->m_vtxQueue[2].s << "," << gs->m_vtxQueue[2].t << "," << gs->m_vtxQueue[2].q << ")" + << " rgba0=(" << static_cast(gs->m_vtxQueue[0].r) << "," + << static_cast(gs->m_vtxQueue[0].g) << "," + << static_cast(gs->m_vtxQueue[0].b) << "," + << static_cast(gs->m_vtxQueue[0].a) << ")" + << " rgba1=(" << static_cast(gs->m_vtxQueue[1].r) << "," + << static_cast(gs->m_vtxQueue[1].g) << "," + << static_cast(gs->m_vtxQueue[1].b) << "," + << static_cast(gs->m_vtxQueue[1].a) << ")" + << " rgba2=(" << static_cast(gs->m_vtxQueue[2].r) << "," + << static_cast(gs->m_vtxQueue[2].g) << "," + << static_cast(gs->m_vtxQueue[2].b) << "," + << static_cast(gs->m_vtxQueue[2].a) << ")" + << std::endl; + } + }); + + PS2_IF_AGRESSIVE_LOGS({ + if ((gs->m_prim.ctxt != 0u || ctx.frame.fbp == 150u) && + s_debugContext1PrimitiveCount.fetch_add(1u, std::memory_order_relaxed) < 32u) + { + std::cout << "[gs:copy-prim]" + << " type=" << static_cast(gs->m_prim.type) + << " tme=" << static_cast(gs->m_prim.tme) + << " abe=" << static_cast(gs->m_prim.abe) + << " fst=" << static_cast(gs->m_prim.fst) + << " ctxt=" << static_cast(gs->m_prim.ctxt) + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " tex0=(" + << "tbp0=" << ctx.tex0.tbp0 + << " tbw=" << static_cast(ctx.tex0.tbw) + << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec + << " tcc=" << static_cast(ctx.tex0.tcc) + << " tfx=" << static_cast(ctx.tex0.tfx) + << " cbp=" << ctx.tex0.cbp + << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec + << " csm=" << static_cast(ctx.tex0.csm) + << " csa=" << static_cast(ctx.tex0.csa) + << ")" + << " texclut=(" + << "cbw=" << static_cast(gs->m_texclut.cbw) + << " cou=" << static_cast(gs->m_texclut.cou) + << " cov=" << gs->m_texclut.cov + << ")" + << " ofx=" << (ctx.xyoffset.ofx >> 4) + << " ofy=" << (ctx.xyoffset.ofy >> 4) + << " scissor=(" << ctx.scissor.x0 + << "," << ctx.scissor.y0 + << ")-(" << ctx.scissor.x1 + << "," << ctx.scissor.y1 << ")" + << " test=0x" << std::hex << ctx.test + << " alpha=0x" << ctx.alpha + << std::dec << std::endl; + } + }); + + if (gs->m_hasPreferredDisplaySource && ctx.frame.fbp == gs->m_preferredDisplayDestFbp) + { + gs->m_hasPreferredDisplaySource = false; } switch (gs->m_prim.type) @@ -324,47 +398,69 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_ if (!alphaTest.writeFramebuffer) return; - uint32_t fbBase = ctx.frame.fbp * 8192u; - uint32_t stride = fbStride(ctx.frame.fbw, ctx.frame.psm); - if (stride == 0) - return; + const uint32_t widthBlocks = (ctx.frame.fbw != 0u) ? ctx.frame.fbw : 1u; + const uint32_t bytesPerPixel = + (ctx.frame.psm == GS_PSM_CT16 || ctx.frame.psm == GS_PSM_CT16S) ? 2u : 4u; + + uint32_t off = 0u; + if (ctx.frame.psm == GS_PSM_CT32 || ctx.frame.psm == GS_PSM_CT24) + { + off = GSPSMCT32::addrPSMCT32(GSInternal::framePageBaseToBlock(ctx.frame.fbp), + widthBlocks, + static_cast(x), + static_cast(y)); + } + else + { + off = addrPSMCT16Family(GSInternal::framePageBaseToBlock(ctx.frame.fbp), + widthBlocks, + ctx.frame.psm, + static_cast(x), + static_cast(y)); + } - const uint32_t bytesPerPixel = std::max(1u, bitsPerPixel(ctx.frame.psm) / 8u); - uint32_t off = fbBase + static_cast(y) * stride + static_cast(x) * bytesPerPixel; if (off + bytesPerPixel > gs->m_vramSize) return; - const uint32_t pixelIndex = s_debugPixelCount.fetch_add(1, std::memory_order_relaxed); - if (pixelIndex < 32u) - { - std::cout << "[gs:pixel] idx=" << pixelIndex - << " xy=(" << x << "," << y << ")" - << " rgba=(" << static_cast(r) << "," - << static_cast(g) << "," - << static_cast(b) << "," - << static_cast(a) << ")" - << " fbp=" << ctx.frame.fbp - << " fbw=" << ctx.frame.fbw - << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec - << " off=0x" << std::hex << off << std::dec - << std::endl; - } - - if (ctx.frame.fbp == 150u && - s_debugFbp150PixelCount.fetch_add(1u, std::memory_order_relaxed) < 32u) - { - std::cout << "[gs:fbp150-pixel]" - << " xy=(" << x << "," << y << ")" - << " rgba=(" << static_cast(r) << "," - << static_cast(g) << "," - << static_cast(b) << "," - << static_cast(a) << ")" - << " scissor=(" << ctx.scissor.x0 - << "," << ctx.scissor.y0 - << ")-(" << ctx.scissor.x1 - << "," << ctx.scissor.y1 << ")" - << " off=0x" << std::hex << off << std::dec << std::endl; - } + PS2_IF_AGRESSIVE_LOGS({ + const uint32_t pixelIndex = s_debugPixelCount.fetch_add(1, std::memory_order_relaxed); + if (pixelIndex < 32u) + { + std::cout << "[gs:pixel] idx=" << pixelIndex + << " xy=(" << x << "," << y << ")" + << " rgba=(" << static_cast(r) << "," + << static_cast(g) << "," + << static_cast(b) << "," + << static_cast(a) << ")" + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " off=0x" << std::hex << off << std::dec + << std::endl; + } + }); + + PS2_IF_AGRESSIVE_LOGS({ + if (ctx.frame.fbp == 150u && + s_debugFbp150PixelCount.fetch_add(1u, std::memory_order_relaxed) < 32u) + { + std::cout << "[gs:fbp150-pixel]" + << " xy=(" << x << "," << y << ")" + << " rgba=(" << static_cast(r) << "," + << static_cast(g) << "," + << static_cast(b) << "," + << static_cast(a) << ")" + << " scissor=(" << ctx.scissor.x0 + << "," << ctx.scissor.y0 + << ")-(" << ctx.scissor.x1 + << "," << ctx.scissor.y1 << ")" + << " off=0x" << std::hex << off << std::dec << std::endl; + } + }); + + const uint8_t srcR = r; + const uint8_t srcG = g; + const uint8_t srcB = b; if (gs->m_prim.abe) { @@ -384,30 +480,47 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_ uint8_t db = (existing >> 16) & 0xFF; uint8_t da = (existing >> 24) & 0xFF; - uint64_t alphaReg = ctx.alpha; - uint8_t asel = alphaReg & 3; - uint8_t bsel = (alphaReg >> 2) & 3; - uint8_t csel = (alphaReg >> 4) & 3; - uint8_t dsel = (alphaReg >> 6) & 3; - uint8_t fix = static_cast((alphaReg >> 32) & 0xFF); - - auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int + // PABE disables alpha blending when the source alpha MSB is clear. + if (!(gs->m_pabe && (a & 0x80u) == 0u)) { - if (sel == 0) - return cs; - if (sel == 1) - return cd; - return 0; - }; - int cAlpha = (csel == 0) ? a : (csel == 1) ? da - : fix; - - r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr)); - g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg)); - b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db)); + uint64_t alphaReg = ctx.alpha; + uint8_t asel = alphaReg & 3; + uint8_t bsel = (alphaReg >> 2) & 3; + uint8_t csel = (alphaReg >> 4) & 3; + uint8_t dsel = (alphaReg >> 6) & 3; + uint8_t fix = static_cast((alphaReg >> 32) & 0xFF); + + auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int + { + if (sel == 0) + return cs; + if (sel == 1) + return cd; + return 0; + }; + int cAlpha = (csel == 0) ? a : (csel == 1) ? da + : fix; + + r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr)); + g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg)); + b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db)); + } + else + { + r = srcR; + g = srcG; + b = srcB; + } } uint32_t mask = ctx.frame.fbmsk; + if (!alphaTest.preserveDestinationAlpha && + (ctx.fba & 0x1ull) != 0ull && + ctx.frame.psm != GS_PSM_CT24) + { + a = static_cast(a | 0x80u); + } + if (bytesPerPixel == 2u) { uint16_t pixel = encodePSMCT16(r, g, b, a); @@ -444,9 +557,7 @@ uint32_t GSRasterizer::readTexelPSMCT32(GS *gs, uint32_t tbp0, uint32_t tbw, int { if (tbw == 0) tbw = 1; - uint32_t base = tbp0 * 256u; - uint32_t stride = tbw * 64u * 4u; - uint32_t off = base + static_cast(texV) * stride + static_cast(texU) * 4u; + uint32_t off = GSPSMCT32::addrPSMCT32(tbp0, tbw, static_cast(texU), static_cast(texV)); if (off + 4 > gs->m_vramSize) return 0xFFFF00FFu; uint32_t texel; @@ -458,9 +569,8 @@ uint32_t GSRasterizer::readTexelPSMCT16(GS *gs, uint32_t tbp0, uint32_t tbw, int { if (tbw == 0) tbw = 1; - uint32_t base = tbp0 * 256u; - uint32_t stride = tbw * 64u * 2u; - uint32_t off = base + static_cast(texV) * stride + static_cast(texU) * 2u; + const uint8_t psm = gs->activeContext().tex0.psm; + uint32_t off = addrPSMCT16Family(tbp0, tbw, psm, static_cast(texU), static_cast(texV)); if (off + 2 > gs->m_vramSize) return 0xFFFF00FFu; uint16_t texel; @@ -490,31 +600,29 @@ uint32_t GSRasterizer::lookupCLUT(GS *gs, uint8_t csa, uint8_t sourcePsm) { - uint32_t clutBase = cbp * 256u; const uint32_t clutIndex = resolveClutIndex(index, csm, csa, sourcePsm); + const uint32_t clutWidth = (gs->m_texclut.cbw != 0u) ? static_cast(gs->m_texclut.cbw) : 1u; + const uint32_t clutX = static_cast(gs->m_texclut.cou) + (clutIndex & 0x0Fu); + const uint32_t clutY = static_cast(gs->m_texclut.cov) + (clutIndex >> 4); if (cpsm == GS_PSM_CT32 || cpsm == GS_PSM_CT24) { - uint32_t off = clutBase + clutIndex * 4u; + const uint32_t off = GSPSMCT32::addrPSMCT32(cbp, clutWidth, clutX, clutY); if (off + 4 > gs->m_vramSize) return 0xFFFF00FFu; uint32_t color; std::memcpy(&color, gs->m_vram + off, 4); - return color; + return applyTexa(gs->m_texa, cpsm, color); } if (cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S) { - uint32_t off = clutBase + clutIndex * 2u; + uint32_t off = addrPSMCT16Family(cbp, clutWidth, cpsm, clutX, clutY); if (off + 2 > gs->m_vramSize) return 0xFFFF00FFu; uint16_t c16; std::memcpy(&c16, gs->m_vram + off, 2); - uint32_t r = ((c16 >> 0) & 0x1Fu) << 3; - uint32_t g = ((c16 >> 5) & 0x1Fu) << 3; - uint32_t b = ((c16 >> 10) & 0x1Fu) << 3; - uint32_t a = (c16 & 0x8000u) ? 0x80u : 0u; - return r | (g << 8) | (b << 16) | (a << 24); + return applyTexa(gs->m_texa, cpsm, decodePSMCT16(c16)); } return 0xFFFF00FFu; @@ -528,46 +636,94 @@ uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, float q, uint16_t int texW = 1 << tex.tw; int texH = 1 << tex.th; - int texU, texV; + float texUf, texVf; if (gs->m_prim.fst) { - texU = static_cast(u >> 4); - texV = static_cast(v >> 4); + texUf = static_cast(u) / 16.0f; + texVf = static_cast(v) / 16.0f; } else { const float invQ = 1.0f / fabsQ(q); - texU = static_cast(s * invQ * static_cast(texW)); - texV = static_cast(t * invQ * static_cast(texH)); + texUf = s * invQ * static_cast(texW); + texVf = t * invQ * static_cast(texH); } - texU = clampInt(texU, 0, texW - 1); - texV = clampInt(texV, 0, texH - 1); + auto samplePoint = [&](int sampleU, int sampleV) -> uint32_t + { + sampleU = clampInt(sampleU, 0, texW - 1); + sampleV = clampInt(sampleV, 0, texH - 1); - if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24) - return readTexelPSMCT32(gs, tex.tbp0, tex.tbw, texU, texV); + if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24) + return applyTexa(gs->m_texa, tex.psm, readTexelPSMCT32(gs, tex.tbp0, tex.tbw, sampleU, sampleV)); - if (tex.psm == GS_PSM_CT16 || tex.psm == GS_PSM_CT16S) - return readTexelPSMCT16(gs, tex.tbp0, tex.tbw, texU, texV); + if (tex.psm == GS_PSM_CT16 || tex.psm == GS_PSM_CT16S) + return applyTexa(gs->m_texa, tex.psm, readTexelPSMCT16(gs, tex.tbp0, tex.tbw, sampleU, sampleV)); - if (tex.psm == GS_PSM_T4) - { - uint32_t idx = readTexelPSMT4(gs, tex.tbp0, tex.tbw, texU, texV); - return lookupCLUT(gs, static_cast(idx), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); - } + if (tex.psm == GS_PSM_T4) + { + uint32_t idx = readTexelPSMT4(gs, tex.tbp0, tex.tbw, sampleU, sampleV); + return lookupCLUT(gs, static_cast(idx), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); + } + + if (tex.psm == GS_PSM_T8) + { + if (tex.tbw == 0) + return 0xFFFF00FFu; + uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw, static_cast(sampleU), static_cast(sampleV)); + if (off >= gs->m_vramSize) + return 0xFFFF00FFu; + uint8_t idx = gs->m_vram[off]; + return lookupCLUT(gs, idx, tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); + } + + return 0xFFFF00FFu; + }; - if (tex.psm == GS_PSM_T8) + if (!tex1UsesLinearFilter(ctx.tex1)) { - if (tex.tbw == 0) - return 0xFFFF00FFu; - uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw, static_cast(texU), static_cast(texV)); - if (off >= gs->m_vramSize) - return 0xFFFF00FFu; - uint8_t idx = gs->m_vram[off]; - return lookupCLUT(gs, idx, tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); + return samplePoint(static_cast(texUf), static_cast(texVf)); } - return 0xFFFF00FFu; + const float sampleU = texUf - 0.5f; + const float sampleV = texVf - 0.5f; + const int u0 = static_cast(std::floor(sampleU)); + const int v0 = static_cast(std::floor(sampleV)); + const int u1 = u0 + 1; + const int v1 = v0 + 1; + const float fx = sampleU - static_cast(u0); + const float fy = sampleV - static_cast(v0); + + const uint32_t c00 = samplePoint(u0, v0); + const uint32_t c10 = samplePoint(u1, v0); + const uint32_t c01 = samplePoint(u0, v1); + const uint32_t c11 = samplePoint(u1, v1); + + const uint8_t r = lerpChannel(static_cast(c00 & 0xFFu), + static_cast(c10 & 0xFFu), + static_cast(c01 & 0xFFu), + static_cast(c11 & 0xFFu), + fx, fy); + const uint8_t g = lerpChannel(static_cast((c00 >> 8) & 0xFFu), + static_cast((c10 >> 8) & 0xFFu), + static_cast((c01 >> 8) & 0xFFu), + static_cast((c11 >> 8) & 0xFFu), + fx, fy); + const uint8_t b = lerpChannel(static_cast((c00 >> 16) & 0xFFu), + static_cast((c10 >> 16) & 0xFFu), + static_cast((c01 >> 16) & 0xFFu), + static_cast((c11 >> 16) & 0xFFu), + fx, fy); + const uint8_t a = lerpChannel(static_cast((c00 >> 24) & 0xFFu), + static_cast((c10 >> 24) & 0xFFu), + static_cast((c01 >> 24) & 0xFFu), + static_cast((c11 >> 24) & 0xFFu), + fx, fy); + + return static_cast(r) | + (static_cast(g) << 8) | + (static_cast(b) << 16) | + (static_cast(a) << 24); } void GSRasterizer::drawSprite(GS *gs) @@ -589,31 +745,60 @@ void GSRasterizer::drawSprite(GS *gs) if (y0 > y1) std::swap(y0, y1); + const int unclippedX0 = x0; + const int unclippedY0 = y0; + const int spanX = std::max(1, x1 - x0); + const int spanY = std::max(1, y1 - y0); + const int unclippedX1 = unclippedX0 + spanX - 1; + const int unclippedY1 = unclippedY0 + spanY - 1; + // If the sprite rectangle is fully outside scissor, nothing should render. - if (x1 < ctx.scissor.x0 || x0 > ctx.scissor.x1 || - y1 < ctx.scissor.y0 || y0 > ctx.scissor.y1) + if (unclippedX1 < ctx.scissor.x0 || unclippedX0 > ctx.scissor.x1 || + unclippedY1 < ctx.scissor.y0 || unclippedY0 > ctx.scissor.y1) { // maybe a log here idk ? return; } - x0 = clampInt(x0, ctx.scissor.x0, ctx.scissor.x1); - y0 = clampInt(y0, ctx.scissor.y0, ctx.scissor.y1); - x1 = clampInt(x1, ctx.scissor.x0, ctx.scissor.x1); - y1 = clampInt(y1, ctx.scissor.y0, ctx.scissor.y1); + const int drawX0 = clampInt(unclippedX0, ctx.scissor.x0, ctx.scissor.x1); + const int drawY0 = clampInt(unclippedY0, ctx.scissor.y0, ctx.scissor.y1); + const int drawX1 = clampInt(unclippedX1, ctx.scissor.x0, ctx.scissor.x1); + const int drawY1 = clampInt(unclippedY1, ctx.scissor.y0, ctx.scissor.y1); + + const uint64_t alphaReg = ctx.alpha; + const uint8_t alphaMode = static_cast(alphaReg & 0xFFu); + const uint8_t alphaFix = static_cast((alphaReg >> 32) & 0xFFu); + const bool looksLikeDisplayCopy = + gs->m_prim.tme && + gs->m_prim.abe && + gs->m_prim.fst && + gs->m_prim.ctxt && + ctx.frame.fbp != ctx.tex0.tbp0 && + alphaMode == 0x64u && + (alphaFix == 0x60u || alphaFix == 0x80u) && + unclippedX0 <= 0 && + unclippedY0 <= 0 && + unclippedX1 >= 639 && + unclippedY1 >= 447; + if (looksLikeDisplayCopy) + { + gs->m_preferredDisplaySourceFrame = {ctx.tex0.tbp0, ctx.tex0.tbw, ctx.tex0.psm, 0u}; + gs->m_preferredDisplayDestFbp = ctx.frame.fbp; + gs->m_hasPreferredDisplaySource = true; + } uint8_t r = v1.r, g = v1.g, b = v1.b, a = v1.a; if (gs->m_prim.tme) { - const auto &tex = ctx.tex0; + const auto &tex = ctx.tex0; int texW = 1 << tex.tw; int texH = 1 << tex.th; if (texW == 0) texW = 1; if (texH == 0) texH = 1; - + float u0f, v0f, u1f, v1f; if (gs->m_prim.fst) { @@ -632,45 +817,36 @@ void GSRasterizer::drawSprite(GS *gs) v1f = (v1.t / q1) * static_cast(texH); } - float spriteW = static_cast(x1 - x0); - float spriteH = static_cast(y1 - y0); + float spriteW = static_cast(spanX); + float spriteH = static_cast(spanY); if (spriteW < 1.0f) spriteW = 1.0f; if (spriteH < 1.0f) spriteH = 1.0f; - for (int y = y0; y <= y1; ++y) + for (int y = drawY0; y <= drawY1; ++y) { - float ty = (static_cast(y - y0) + 0.5f) / spriteH; + float ty = (static_cast(y - unclippedY0) + 0.5f) / spriteH; float texVf = v0f + (v1f - v0f) * ty; - int tv = clampInt(static_cast(texVf), 0, texH - 1); - for (int x = x0; x <= x1; ++x) + for (int x = drawX0; x <= drawX1; ++x) { - float tx = (static_cast(x - x0) + 0.5f) / spriteW; + float tx = (static_cast(x - unclippedX0) + 0.5f) / spriteW; float texUf = u0f + (u1f - u0f) * tx; - int tu = clampInt(static_cast(texUf), 0, texW - 1); - - uint32_t texel; - uint32_t sampleIndexValue = 0u; - if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24) - texel = readTexelPSMCT32(gs, tex.tbp0, tex.tbw, tu, tv); - else if (tex.psm == GS_PSM_CT16 || tex.psm == GS_PSM_CT16S) - texel = readTexelPSMCT16(gs, tex.tbp0, tex.tbw, tu, tv); - else if (tex.psm == GS_PSM_T4) + uint32_t texel = 0xFFFF00FFu; + if (gs->m_prim.fst) { - sampleIndexValue = readTexelPSMT4(gs, tex.tbp0, tex.tbw, tu, tv); - texel = lookupCLUT(gs, static_cast(sampleIndexValue), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); + const uint16_t sampleU = static_cast(clampInt(static_cast(std::lround(texUf * 16.0f)), 0, 0xFFFF)); + const uint16_t sampleV = static_cast(clampInt(static_cast(std::lround(texVf * 16.0f)), 0, 0xFFFF)); + texel = sampleTexture(gs, 0.0f, 0.0f, 1.0f, sampleU, sampleV); } - else if (tex.psm == GS_PSM_T8) + else { - uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw ? tex.tbw : 1u, - static_cast(tu), static_cast(tv)); - sampleIndexValue = (off < gs->m_vramSize) ? gs->m_vram[off] : 0u; - texel = lookupCLUT(gs, static_cast(sampleIndexValue), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm); + texel = sampleTexture(gs, + texUf / static_cast(texW), + texVf / static_cast(texH), + 1.0f, 0u, 0u); } - else - texel = 0xFFFF00FFu; uint8_t tr = static_cast(texel & 0xFF); uint8_t tg = static_cast((texel >> 8) & 0xFF); @@ -684,8 +860,8 @@ void GSRasterizer::drawSprite(GS *gs) } else { - for (int y = y0; y <= y1; ++y) - for (int x = x0; x <= x1; ++x) + for (int y = drawY0; y <= drawY1; ++y) + for (int x = drawX0; x <= drawX1; ++x) writePixel(gs, x, y, r, g, b, a); } } @@ -723,6 +899,7 @@ void GSRasterizer::drawTriangle(GS *gs) const float winding = (denom < 0.0f) ? -1.0f : 1.0f; const float invAbsDenom = 1.0f / std::fabs(denom); + constexpr float kEdgeEpsilon = 1.0e-4f; for (int y = minY; y <= maxY; ++y) { @@ -735,7 +912,7 @@ void GSRasterizer::drawTriangle(GS *gs) float w1 = (((fy2 - fy0) * (px - fx2) + (fx0 - fx2) * (py - fy2)) * winding) * invAbsDenom; float w2 = 1.0f - w0 - w1; - if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f) + if (w0 < -kEdgeEpsilon || w1 < -kEdgeEpsilon || w2 < -kEdgeEpsilon) continue; uint8_t r, g, b, a; diff --git a/ps2xRuntime/src/lib/ps2_iop.cpp b/ps2xRuntime/src/lib/ps2_iop.cpp index 41ce06d6..e2665098 100644 --- a/ps2xRuntime/src/lib/ps2_iop.cpp +++ b/ps2xRuntime/src/lib/ps2_iop.cpp @@ -1,4 +1,8 @@ -#include "ps2_iop.h" +#include "runtime/ps2_iop.h" +#include "runtime/ps2_iop_audio.h" +#include "runtime/ps2_memory.h" +#include "ps2_runtime.h" +#include "Kernel/Syscalls/RPC.h" ps2_iop::ps2_iop() { @@ -14,9 +18,39 @@ void ps2_iop::reset() { } -bool ps2_iop::handleRPC(uint32_t /*sid*/, uint32_t /*rpcNum*/, - uint32_t /*sendBufAddr*/, uint32_t /*sendSize*/, - uint32_t /*recvBufAddr*/, uint32_t /*recvSize*/) +bool ps2_iop::handleRPC(PS2Runtime *runtime, + uint32_t sid, uint32_t rpcNum, + uint32_t sendBufAddr, uint32_t sendSize, + uint32_t recvBufAddr, uint32_t recvSize, + uint32_t &resultPtr, + bool &signalNowaitCompletion) { + resultPtr = 0u; + signalNowaitCompletion = false; + + if (!runtime || !m_rdram) + { + return false; + } + + if (ps2_syscalls::handleSoundDriverRpcService(m_rdram, runtime, + sid, rpcNum, + sendBufAddr, sendSize, + recvBufAddr, recvSize, + resultPtr, + signalNowaitCompletion)) + { + return true; + } + + if (sid == IOP_SID_LIBSD) + { + const uint8_t *sendPtr = sendBufAddr ? getConstMemPtr(m_rdram, sendBufAddr) : nullptr; + uint8_t *recvPtr = recvBufAddr ? getMemPtr(m_rdram, recvBufAddr) : nullptr; + ps2_iop_audio::handleLibSdRpc(runtime, sid, rpcNum, sendPtr, sendSize, recvPtr, recvSize); + resultPtr = recvBufAddr; + return true; + } + return false; } diff --git a/ps2xRuntime/src/lib/ps2_iop_audio.cpp b/ps2xRuntime/src/lib/ps2_iop_audio.cpp index 259209ef..a3fa0ee3 100644 --- a/ps2xRuntime/src/lib/ps2_iop_audio.cpp +++ b/ps2xRuntime/src/lib/ps2_iop_audio.cpp @@ -1,14 +1,14 @@ -#include "ps2_iop_audio.h" +#include "runtime/ps2_iop_audio.h" #include "ps2_runtime.h" namespace ps2_iop_audio { -void handleLibSdRpc(PS2Runtime *runtime, uint32_t sid, uint32_t rpcNum, - const uint8_t *sendBuf, uint32_t sendSize, - uint8_t *recvBuf, uint32_t recvSize) -{ - if (!runtime) - return; - runtime->audioBackend().onSoundCommand(sid, rpcNum, sendBuf, sendSize, recvBuf, recvSize); -} + void handleLibSdRpc(PS2Runtime *runtime, uint32_t sid, uint32_t rpcNum, + const uint8_t *sendBuf, uint32_t sendSize, + uint8_t *recvBuf, uint32_t recvSize) + { + if (!runtime) + return; + runtime->audioBackend().onSoundCommand(sid, rpcNum, sendBuf, sendSize, recvBuf, recvSize); + } } diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index 67121677..c1afdf33 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -1,4 +1,6 @@ -#include "ps2_memory.h" +#include "runtime/ps2_memory.h" +#include "ps2_log.h" +#include #include #include #include @@ -167,6 +169,10 @@ bool PS2Memory::initialize(size_t ramSize) m_gsWriteCount.store(0, std::memory_order_relaxed); m_vifWriteCount.store(0, std::memory_order_relaxed); m_codeRegions.clear(); + m_path3Masked = false; + m_path3MaskedFifo.clear(); + m_vif1PendingPath2ImageQwc = 0u; + m_vif1PendingPath2DirectHl = false; try { @@ -195,6 +201,8 @@ bool PS2Memory::initialize(size_t ramSize) memset(&gs_regs, 0, sizeof(gs_regs)); gs_regs.dispfb1 = (0ULL << 0) | (10ULL << 9) | (0ULL << 15) | (0ULL << 32) | (0ULL << 43); gs_regs.display1 = (0ULL << 0) | (0ULL << 12) | (0ULL << 23) | (0ULL << 27) | (639ULL << 32) | (447ULL << 44); + gs_regs.dispfb2 = gs_regs.dispfb1; + gs_regs.display2 = gs_regs.display1; // Allocate GS VRAM (4MB) m_gsVRAM = new uint8_t[PS2_GS_VRAM_SIZE]; @@ -702,17 +710,13 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) { if (address == 0x10002010) { + m_ioRegisters[address] = value & ~(1u << 31); if (value & (1u << 30)) { m_ioRegisters[0x10002000] = 0; - m_ioRegisters[0x10002010] = 0; m_ioRegisters[0x10002020] = 0; m_ioRegisters[0x10002030] = 0; } - else - { - m_ioRegisters[address] = value & ~(1u << 31); - } } else { @@ -747,10 +751,12 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) switch (address) { - case 0x10003C10u: // VIF1_FBRST + case 0x10003C10u: // VIF1_FBRST if (value & 0x1u) // RST { std::memset(&vif1_regs, 0, sizeof(vif1_regs)); + m_vif1PendingPath2ImageQwc = 0u; + m_vif1PendingPath2DirectHl = false; } if (value & 0x8u) // STC { @@ -862,15 +868,8 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) const uint64_t bytes64 = static_cast(qwCount) * 16ull; uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(bytes64); const bool scratch = isScratchpad(srcAddr); - uint32_t src = 0; - try - { - src = translateAddress(srcAddr); - } - catch (...) - { - return; - } + uint32_t src = 0; + src = translateAddress(srcAddr); const uint8_t *base2; uint32_t maxSz2; if (scratch) @@ -892,10 +891,27 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) chainBuf.insert(chainBuf.end(), base2 + src, base2 + src + bytes); }; + auto appendCompactVif1TagData = [&](uint32_t localTagAddr, uint32_t qwCount) + { + uint32_t tagPhys = 0u; + const bool tagScratch = isScratchpad(localTagAddr); + tagPhys = translateAddress(localTagAddr); + + const uint8_t *localBase = tagScratch ? m_scratchpad : m_rdram; + const uint32_t localMax = tagScratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE; + if (tagPhys + 16u > localMax) + return; + + // VIF1 packet helpers embed 8 bytes of VIF stream in the DMAtag's upper half. + chainBuf.insert(chainBuf.end(), localBase + tagPhys + 8u, localBase + tagPhys + 16u); + appendData(localTagAddr + 16u, qwCount); + }; + int tagsProcessed = 0; while (tagsProcessed < kMaxChainTags) { + const uint32_t currentTagAddr = tagAddr; const bool tagInSPR = isScratchpad(tagAddr); uint32_t physTag = 0; try @@ -998,7 +1014,15 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) } if (hasPayload) - appendData(dataAddr, tagQwc); + { + const bool compactVif1LocalPayload = + (channelBase == 0x10009000u) && + (id == 1u || id == 2u || id == 5u || id == 6u || id == 7u); + if (compactVif1LocalPayload) + appendCompactVif1TagData(currentTagAddr, tagQwc); + else + appendData(dataAddr, tagQwc); + } if (irq && tieEnabled) endChain = true; if (endChain) @@ -1019,10 +1043,18 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value) pt.qwc = 0; pt.chainData = std::move(chainBuf); if (channelBase == 0x1000A000) + { m_pendingGifTransfers.push_back(std::move(pt)); + } else if (channelBase == 0x10009000) + { m_pendingVif1Transfers.push_back(std::move(pt)); + } } + // else if (channelBase == 0x10009000u) + // { + + // } } else if (qwc > 0) { @@ -1367,7 +1399,7 @@ void PS2Memory::registerCodeRegion(uint32_t start, uint32_t end) region.modified.resize(sizeInWords, false); m_codeRegions.push_back(region); - std::cout << "Registered code region: " << std::hex << start << " - " << end << std::dec << std::endl; + RUNTIME_LOG("Registered code region: " << std::hex << start << " - " << end << std::dec); } bool PS2Memory::isAddressInRegion(uint32_t address, const CodeRegion ®ion) @@ -1413,7 +1445,7 @@ void PS2Memory::markModified(uint32_t address, uint32_t size) if (bitIndex < region.modified.size()) { region.modified[bitIndex] = true; - std::cout << "Marked code at " << std::hex << addr << std::dec << " as modified" << std::endl; + RUNTIME_LOG("Marked code at " << std::hex << addr << std::dec << " as modified"); } } } diff --git a/ps2xRuntime/src/lib/ps2_pad.cpp b/ps2xRuntime/src/lib/ps2_pad.cpp index cc99e1c9..10e3baf0 100644 --- a/ps2xRuntime/src/lib/ps2_pad.cpp +++ b/ps2xRuntime/src/lib/ps2_pad.cpp @@ -1,28 +1,28 @@ -#include "ps2_pad.h" -#include "raylib.h" +#include "runtime/ps2_pad.h" +#include "ps2_host_backend.h" #include namespace { -constexpr uint8_t kPadAnalogMarker = 0x73; -constexpr uint8_t kPadStickCenter = 0x80; + constexpr uint8_t kPadAnalogMarker = 0x73; + constexpr uint8_t kPadStickCenter = 0x80; -constexpr uint16_t PAD_LEFT = 0x0080u; -constexpr uint16_t PAD_DOWN = 0x0040u; -constexpr uint16_t PAD_RIGHT = 0x0020u; -constexpr uint16_t PAD_UP = 0x0010u; -constexpr uint16_t PAD_START = 0x0008u; -constexpr uint16_t PAD_R3 = 0x0004u; -constexpr uint16_t PAD_L3 = 0x0002u; -constexpr uint16_t PAD_SELECT = 0x0001u; -constexpr uint16_t PAD_SQUARE = 0x8000u; -constexpr uint16_t PAD_CROSS = 0x4000u; -constexpr uint16_t PAD_CIRCLE = 0x2000u; -constexpr uint16_t PAD_TRIANGLE = 0x1000u; -constexpr uint16_t PAD_R1 = 0x0800u; -constexpr uint16_t PAD_L1 = 0x0400u; -constexpr uint16_t PAD_R2 = 0x0200u; -constexpr uint16_t PAD_L2 = 0x0100u; + constexpr uint16_t PAD_LEFT = 0x0080u; + constexpr uint16_t PAD_DOWN = 0x0040u; + constexpr uint16_t PAD_RIGHT = 0x0020u; + constexpr uint16_t PAD_UP = 0x0010u; + constexpr uint16_t PAD_START = 0x0008u; + constexpr uint16_t PAD_R3 = 0x0004u; + constexpr uint16_t PAD_L3 = 0x0002u; + constexpr uint16_t PAD_SELECT = 0x0001u; + constexpr uint16_t PAD_SQUARE = 0x8000u; + constexpr uint16_t PAD_CROSS = 0x4000u; + constexpr uint16_t PAD_CIRCLE = 0x2000u; + constexpr uint16_t PAD_TRIANGLE = 0x1000u; + constexpr uint16_t PAD_R1 = 0x0800u; + constexpr uint16_t PAD_L1 = 0x0400u; + constexpr uint16_t PAD_R2 = 0x0200u; + constexpr uint16_t PAD_L2 = 0x0100u; } bool PSPadBackend::readState(int /*port*/, int /*slot*/, uint8_t *data, size_t size) @@ -40,26 +40,43 @@ bool PSPadBackend::readState(int /*port*/, int /*slot*/, uint8_t *data, size_t s uint16_t btns = 0xFFFFu; constexpr int kGamepad = 0; const bool useGamepad = IsGamepadAvailable(kGamepad); - auto clearBit = [&btns](uint16_t mask) { btns &= ~mask; }; + auto clearBit = [&btns](uint16_t mask) + { btns &= ~mask; }; if (useGamepad) { - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)) clearBit(PAD_UP); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)) clearBit(PAD_DOWN); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)) clearBit(PAD_LEFT); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)) clearBit(PAD_RIGHT); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)) clearBit(PAD_CROSS); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)) clearBit(PAD_CIRCLE); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)) clearBit(PAD_SQUARE); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)) clearBit(PAD_TRIANGLE); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)) clearBit(PAD_L1); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)) clearBit(PAD_R1); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_2)) clearBit(PAD_L2); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_2)) clearBit(PAD_R2); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)) clearBit(PAD_START); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)) clearBit(PAD_SELECT); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_THUMB)) clearBit(PAD_L3); - if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) clearBit(PAD_R3); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)) + clearBit(PAD_UP); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)) + clearBit(PAD_DOWN); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)) + clearBit(PAD_LEFT); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)) + clearBit(PAD_RIGHT); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)) + clearBit(PAD_CROSS); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)) + clearBit(PAD_CIRCLE); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)) + clearBit(PAD_SQUARE); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)) + clearBit(PAD_TRIANGLE); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)) + clearBit(PAD_L1); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)) + clearBit(PAD_R1); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_2)) + clearBit(PAD_L2); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_2)) + clearBit(PAD_R2); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)) + clearBit(PAD_START); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)) + clearBit(PAD_SELECT); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_THUMB)) + clearBit(PAD_L3); + if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) + clearBit(PAD_R3); float lx = GetGamepadAxisMovement(kGamepad, GAMEPAD_AXIS_LEFT_X); float ly = GetGamepadAxisMovement(kGamepad, GAMEPAD_AXIS_LEFT_Y); @@ -72,20 +89,34 @@ bool PSPadBackend::readState(int /*port*/, int /*slot*/, uint8_t *data, size_t s } else { - if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) clearBit(PAD_UP); - if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) clearBit(PAD_DOWN); - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) clearBit(PAD_LEFT); - if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) clearBit(PAD_RIGHT); - if (IsKeyDown(KEY_ENTER) || IsKeyDown(KEY_SPACE)) clearBit(PAD_CROSS); - if (IsKeyDown(KEY_ESCAPE)) clearBit(PAD_CIRCLE); - if (IsKeyDown(KEY_KP_0) || IsKeyDown(KEY_Z)) clearBit(PAD_SQUARE); - if (IsKeyDown(KEY_KP_1) || IsKeyDown(KEY_X)) clearBit(PAD_TRIANGLE); - if (IsKeyDown(KEY_Q)) clearBit(PAD_L1); - if (IsKeyDown(KEY_E)) clearBit(PAD_R1); - if (IsKeyDown(KEY_LEFT_SHIFT)) clearBit(PAD_L2); - if (IsKeyDown(KEY_RIGHT_SHIFT)) clearBit(PAD_R2); - if (IsKeyDown(KEY_ENTER)) clearBit(PAD_START); - if (IsKeyDown(KEY_TAB)) clearBit(PAD_SELECT); + if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) + clearBit(PAD_UP); + if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) + clearBit(PAD_DOWN); + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) + clearBit(PAD_LEFT); + if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) + clearBit(PAD_RIGHT); + if (IsKeyDown(KEY_ENTER) || IsKeyDown(KEY_SPACE)) + clearBit(PAD_CROSS); + if (IsKeyDown(KEY_ESCAPE)) + clearBit(PAD_CIRCLE); + if (IsKeyDown(KEY_KP_0) || IsKeyDown(KEY_Z)) + clearBit(PAD_SQUARE); + if (IsKeyDown(KEY_KP_1) || IsKeyDown(KEY_X)) + clearBit(PAD_TRIANGLE); + if (IsKeyDown(KEY_Q)) + clearBit(PAD_L1); + if (IsKeyDown(KEY_E)) + clearBit(PAD_R1); + if (IsKeyDown(KEY_LEFT_SHIFT)) + clearBit(PAD_L2); + if (IsKeyDown(KEY_RIGHT_SHIFT)) + clearBit(PAD_R2); + if (IsKeyDown(KEY_ENTER)) + clearBit(PAD_START); + if (IsKeyDown(KEY_TAB)) + clearBit(PAD_SELECT); } data[2] = static_cast(btns & 0xFF); diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 3b0ed96b..98348406 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -1,8 +1,16 @@ #include "ps2_runtime.h" -#include "ps2_syscalls.h" +#include "ps2_log.h" #include "ps2_stubs.h" +#include "ps2_syscalls.h" #include "game_overrides.h" #include "ps2_runtime_macros.h" +#include "runtime/ps2_gs_gpu.h" +#include "ThreadNaming.h" +#include "Kernel/Stubs/Audio.h" +#include "Kernel/Stubs/GS.h" +#include "Kernel/Stubs/MPEG.h" +#include "ps2_host_backend.h" + #include #include #include @@ -15,9 +23,6 @@ #include #include #include -#include "raylib.h" -#include "ps2_gs_gpu.h" -#include namespace ps2_stubs { @@ -30,9 +35,17 @@ namespace ps2_stubs #define PT_LOAD 1 // Loadable segment static constexpr int FB_WIDTH = 640; -static constexpr int FB_HEIGHT = 448; +static constexpr int FB_HEIGHT = 512; +static constexpr int DEFAULT_DISPLAY_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); +#if defined(PLATFORM_VITA) +static constexpr int HOST_WINDOW_WIDTH = 960; +static constexpr int HOST_WINDOW_HEIGHT = 544; +#else +static constexpr int HOST_WINDOW_WIDTH = FB_WIDTH; +static constexpr int HOST_WINDOW_HEIGHT = DEFAULT_DISPLAY_HEIGHT; +#endif struct ElfHeader { uint32_t magic; @@ -84,6 +97,45 @@ namespace constexpr uint32_t EXCEPTION_VECTOR_TLB_REFILL = 0x80000000u; constexpr uint32_t EXCEPTION_VECTOR_BOOT = 0xBFC00200u; + struct HostFrameProbePoint + { + uint32_t x; + uint32_t y; + }; + + constexpr HostFrameProbePoint kGhostProbePoints[] = { + {220u, 176u}, + {260u, 208u}, + {320u, 208u}, + {260u, 240u}, + {320u, 240u}, + {260u, 272u}, + {320u, 272u}, + }; + + uint32_t sampleHostFramePixel(const std::vector &pixels, + uint32_t width, + uint32_t height, + uint32_t x, + uint32_t y) + { + if (x >= width || y >= height) + { + return 0u; + } + + const size_t offset = (static_cast(y) * static_cast(width) + static_cast(x)) * 4u; + if (offset + 4u > pixels.size()) + { + return 0u; + } + + return static_cast(pixels[offset + 0u]) | + (static_cast(pixels[offset + 1u]) << 8) | + (static_cast(pixels[offset + 2u]) << 16) | + (static_cast(pixels[offset + 3u]) << 24); + } + struct DispatchHistory { std::array pcs{}; @@ -201,6 +253,19 @@ namespace return {}; } +#if defined(PLATFORM_VITA) + const std::string generic = path.generic_string(); + const std::size_t colon = generic.find(':'); + if (colon != std::string::npos && colon != 0u) + { + const std::size_t slash = generic.find_first_of("/\\"); + if (slash == std::string::npos || colon < slash) + { + return path.lexically_normal(); + } + } +#endif + std::error_code ec; const std::filesystem::path absolute = std::filesystem::absolute(path, ec); if (ec) @@ -355,286 +420,119 @@ PS2Runtime::GuestExecutionReleaseScope::~GuestExecutionReleaseScope() } } -static void UploadFrame(Texture2D &tex, PS2Runtime *rt) +static void UploadFrame(Texture2D &tex, PS2Runtime *rt, uint32_t &outWidth, uint32_t &outHeight) { - // For now lets keep the display snapshot in sync with rasterized VRAM so the host frame - rt->gs().refreshDisplaySnapshot(); + static uint64_t s_lastPresentationTick = std::numeric_limits::max(); + static bool s_hasLatchedInitialFrame = false; + static uint32_t s_lastDisplayFbp = std::numeric_limits::max(); + static uint32_t s_lastSourceFbp = std::numeric_limits::max(); + static bool s_lastPreferred = false; + static uint32_t s_lastWidth = 0u; + static uint32_t s_lastHeight = 0u; - const GSRegisters &gs = rt->memory().gs(); - - uint32_t dispfb = static_cast(gs.dispfb1 & 0xFFFFFFFFULL); - uint32_t fbp = dispfb & 0x1FF; - uint32_t fbw = (dispfb >> 9) & 0x3F; - uint32_t psm = (dispfb >> 15) & 0x1F; - - uint64_t display64 = gs.display1; - uint32_t dw = static_cast((display64 >> 32) & 0xFFF); - uint32_t dh = static_cast((display64 >> 44) & 0x7FF); - - uint32_t width = (dw + 1); - uint32_t height = (dh + 1); - if (width < 64 || height < 64) + const uint64_t currentTick = ps2_syscalls::GetCurrentVSyncTick(); + if (!s_hasLatchedInitialFrame || currentTick != s_lastPresentationTick) { - width = FB_WIDTH; - height = FB_HEIGHT; + rt->gs().latchHostPresentationFrame(); + s_lastPresentationTick = currentTick; + s_hasLatchedInitialFrame = true; } - if (width > FB_WIDTH) - width = FB_WIDTH; - if (height > FB_HEIGHT) - height = FB_HEIGHT; - - uint8_t *rdram = rt->memory().getRDRAM(); - uint8_t *gsvram = rt->memory().getGSVRAM(); - - uint32_t snapSize = 0; - const uint8_t *snapVram = rt->gs().lockDisplaySnapshot(snapSize); - const uint8_t *vramSrc = (snapVram && snapSize > 0) ? snapVram : gsvram; - auto fillScratchFromFrame = [&](uint32_t srcFbp, - uint32_t srcFbw, - uint32_t srcPsm, - std::vector &outScratch) -> bool - { - outScratch.assign(FB_WIDTH * FB_HEIGHT * 4u, 0u); - - const uint32_t baseBytes = srcFbp * 8192u; - const uint32_t bytesPerPixel = (srcPsm == 2u || srcPsm == 0x0Au) ? 2u : 4u; - const uint32_t strideBytes = (srcFbw ? srcFbw : (FB_WIDTH / 64u)) * 64u * bytesPerPixel; - - if (srcPsm == 0u) - { - for (uint32_t y = 0; y < height; ++y) - { - uint32_t srcOff = baseBytes + y * strideBytes; - uint32_t dstOff = y * FB_WIDTH * 4u; - uint32_t copyW = width * 4u; - if (srcOff + copyW <= PS2_GS_VRAM_SIZE && vramSrc) - { - std::memcpy(&outScratch[dstOff], vramSrc + srcOff, copyW); - } - else - { - uint32_t rdramIdx = srcOff & PS2_RAM_MASK; - if (rdramIdx + copyW > PS2_RAM_SIZE) - { - copyW = PS2_RAM_SIZE - rdramIdx; - } - std::memcpy(&outScratch[dstOff], rdram + rdramIdx, copyW); - } - uint8_t *row = outScratch.data() + dstOff; - for (uint32_t x = 0; x < width; ++x) - { - row[x * 4u + 3u] = 255u; - } - } - return true; - } - - if (srcPsm == 2u || srcPsm == 0x0Au) - { - const uint32_t srcLineBytes = width * 2u; - for (uint32_t y = 0; y < height; ++y) - { - uint32_t srcOff = baseBytes + y * strideBytes; - uint32_t dstOff = y * FB_WIDTH * 4u; - const uint8_t *src = nullptr; - if (srcOff + srcLineBytes <= PS2_GS_VRAM_SIZE && vramSrc) - { - src = vramSrc + srcOff; - } - else if ((srcOff & PS2_RAM_MASK) + srcLineBytes <= PS2_RAM_SIZE) - { - src = rdram + (srcOff & PS2_RAM_MASK); - } - if (!src) - { - continue; - } - uint8_t *dst = outScratch.data() + dstOff; - for (uint32_t x = 0; x < width; ++x) - { - uint16_t p = *reinterpret_cast(src + x * 2u); - uint32_t r = (p >> 10) & 31u; - uint32_t g = (p >> 5) & 31u; - uint32_t b = p & 31u; - dst[x * 4u + 0u] = static_cast((r << 3) | (r >> 2)); - dst[x * 4u + 1u] = static_cast((g << 3) | (g >> 2)); - dst[x * 4u + 2u] = static_cast((b << 3) | (b >> 2)); - dst[x * 4u + 3u] = 255u; - } - } - return true; - } - - return false; - }; - - auto analyzeScratch = [&](const std::vector &scratchBuf, - uint32_t &outNonBlack, - uint32_t &outFirstColor, - uint32_t &outFirstX, - uint32_t &outFirstY) - { - outNonBlack = 0u; - outFirstColor = 0u; - outFirstX = 0u; - outFirstY = 0u; - - for (uint32_t y = 0; y < height; ++y) - { - const uint8_t *row = scratchBuf.data() + y * FB_WIDTH * 4u; - for (uint32_t x = 0; x < width; ++x) - { - const uint8_t r = row[x * 4u + 0u]; - const uint8_t g = row[x * 4u + 1u]; - const uint8_t b = row[x * 4u + 2u]; - if (r != 0u || g != 0u || b != 0u) - { - ++outNonBlack; - if (outFirstColor == 0u) - { - outFirstColor = static_cast(r) | - (static_cast(g) << 8) | - (static_cast(b) << 16); - outFirstX = x; - outFirstY = y; - } - } - } - } - }; - - auto countLinearPageNonBlack = [&](uint32_t probeFbp) -> uint32_t - { - if (!vramSrc) - { - return 0u; - } - const uint32_t probeBaseBytes = probeFbp * 8192u; - const uint32_t probeStrideBytes = 10u * 64u * 4u; - uint32_t count = 0u; - for (uint32_t py = 0; py < height; ++py) - { - const uint32_t srcOff = probeBaseBytes + py * probeStrideBytes; - if (srcOff + width * 4u > PS2_GS_VRAM_SIZE) - { - break; - } - const uint8_t *row = vramSrc + srcOff; - for (uint32_t px = 0; px < width; ++px) - { - const uint8_t r = row[px * 4u + 0u]; - const uint8_t g = row[px * 4u + 1u]; - const uint8_t b = row[px * 4u + 2u]; - if (r != 0u || g != 0u || b != 0u) - { - ++count; - } - } - } - return count; - }; std::vector scratch; - if (!fillScratchFromFrame(fbp, fbw, psm, scratch)) + uint32_t width = 0u; + uint32_t height = 0u; + uint32_t displayFbp = 0u; + uint32_t sourceFbp = 0u; + bool usedPreferredDisplaySource = false; + if (!rt->gs().copyLatchedHostPresentationFrame(scratch, + width, + height, + &displayFbp, + &sourceFbp, + &usedPreferredDisplaySource)) { - rt->gs().unlockDisplaySnapshot(); Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, MAGENTA); UpdateTexture(tex, blank.data); UnloadImage(blank); + outWidth = FB_WIDTH; + outHeight = DEFAULT_DISPLAY_HEIGHT; return; } - uint32_t selectedFbp = fbp; - uint32_t selectedFbw = fbw; - uint32_t selectedPsm = psm; - uint32_t nonBlack = 0u; - uint32_t firstColor = 0u; - uint32_t firstX = 0u; - uint32_t firstY = 0u; - analyzeScratch(scratch, nonBlack, firstColor, firstX, firstY); - - int fallbackContext = -1; - const bool allowFallbackPresentation = (fbp == 0u); - if (allowFallbackPresentation && nonBlack == 0u) - { - for (int contextIndex = 0; contextIndex < 2; ++contextIndex) - { - const GSFrameReg &candidate = rt->gs().getContextFrame(contextIndex); - if (candidate.fbp == selectedFbp && - candidate.fbw == selectedFbw && - candidate.psm == selectedPsm) - { - continue; - } - - std::vector candidateScratch; - if (!fillScratchFromFrame(candidate.fbp, candidate.fbw, candidate.psm, candidateScratch)) - { - continue; - } - - uint32_t candidateNonBlack = 0u; - uint32_t candidateFirstColor = 0u; - uint32_t candidateFirstX = 0u; - uint32_t candidateFirstY = 0u; - analyzeScratch(candidateScratch, candidateNonBlack, candidateFirstColor, candidateFirstX, candidateFirstY); - if (candidateNonBlack == 0u) - { - continue; - } - - scratch.swap(candidateScratch); - selectedFbp = candidate.fbp; - selectedFbw = candidate.fbw; - selectedPsm = candidate.psm; - nonBlack = candidateNonBlack; - firstColor = candidateFirstColor; - firstX = candidateFirstX; - firstY = candidateFirstY; - fallbackContext = contextIndex; - break; - } - } - - rt->gs().unlockDisplaySnapshot(); - - static uint32_t s_uploadDebugCount = 0u; - static uint32_t s_lastLoggedFbp = std::numeric_limits::max(); - static uint32_t s_lastLoggedNonBlack = std::numeric_limits::max(); - const bool shouldProbe = (s_uploadDebugCount < 96u) || (fbp != s_lastLoggedFbp); - if (shouldProbe || fallbackContext >= 0) - { - if (s_uploadDebugCount < 96u || selectedFbp != s_lastLoggedFbp || nonBlack != s_lastLoggedNonBlack || fallbackContext >= 0) + PS2_IF_AGRESSIVE_LOGS({ + static uint32_t s_uploadDebugCount = 0u; + if (s_uploadDebugCount < 128u || + displayFbp != s_lastDisplayFbp || + sourceFbp != s_lastSourceFbp || + usedPreferredDisplaySource != s_lastPreferred || + width != s_lastWidth || + height != s_lastHeight) { - const uint32_t page0NonBlack = countLinearPageNonBlack(0u); - const uint32_t page150NonBlack = countLinearPageNonBlack(150u); std::cout << "[frame:upload] idx=" << s_uploadDebugCount - << " fbp=" << selectedFbp - << " fbw=" << selectedFbw - << " psm=0x" << std::hex << selectedPsm << std::dec + << " tick=" << currentTick + << " displayFbp=" << displayFbp + << " sourceFbp=" << sourceFbp << " size=" << width << "x" << height - << " nonBlack=" << nonBlack - << " page0=" << page0NonBlack - << " page150=" << page150NonBlack - << " allowFallback=" << static_cast(allowFallbackPresentation ? 1u : 0u); - if (fallbackContext >= 0) - { - std::cout << " displayFbp=" << fbp - << " fallbackCtx=" << fallbackContext; - } - if (firstColor != 0u) + << " preferred=" << static_cast(usedPreferredDisplaySource ? 1u : 0u) + << std::endl; + } + static uint32_t s_probeDebugCount = 0u; + if (s_probeDebugCount < 32u || + displayFbp != s_lastDisplayFbp || + sourceFbp != s_lastSourceFbp || + usedPreferredDisplaySource != s_lastPreferred) + { + std::cout << "[frame:probe] idx=" << s_probeDebugCount + << " tick=" << currentTick + << " displayFbp=" << displayFbp + << " sourceFbp=" << sourceFbp + << " preferred=" << static_cast(usedPreferredDisplaySource ? 1u : 0u); + for (const auto &probe : kGhostProbePoints) { - std::cout << " first=(" << firstX << "," << firstY << ")" - << " rgb=0x" << std::hex << firstColor << std::dec; + if (probe.x >= width || probe.y >= height) + { + continue; + } + + const uint32_t pixel = sampleHostFramePixel(scratch, width, height, probe.x, probe.y); + std::cout << " host[" << probe.x << "," << probe.y << "]=0x" + << std::hex << pixel << std::dec; } std::cout << std::endl; + ++s_probeDebugCount; } - s_lastLoggedFbp = selectedFbp; - s_lastLoggedNonBlack = nonBlack; ++s_uploadDebugCount; + }); + s_lastDisplayFbp = displayFbp; + s_lastSourceFbp = sourceFbp; + s_lastPreferred = usedPreferredDisplaySource; + s_lastWidth = width; + s_lastHeight = height; + + std::vector uploadBuffer(DEFAULT_FB_SIZE, 0u); + if (!scratch.empty() && width != 0u && height != 0u) + { + const uint32_t copyWidth = std::min(width, FB_WIDTH); + const uint32_t copyHeight = std::min(height, FB_HEIGHT); + const size_t srcRowBytes = static_cast(width) * 4u; + const size_t dstRowBytes = static_cast(FB_WIDTH) * 4u; + const size_t copyRowBytes = static_cast(copyWidth) * 4u; + for (uint32_t y = 0; y < copyHeight; ++y) + { + const size_t srcOffset = static_cast(y) * srcRowBytes; + const size_t dstOffset = static_cast(y) * dstRowBytes; + if (srcOffset + copyRowBytes > scratch.size() || + dstOffset + copyRowBytes > uploadBuffer.size()) + { + break; + } + std::memcpy(uploadBuffer.data() + dstOffset, scratch.data() + srcOffset, copyRowBytes); + } } - UpdateTexture(tex, scratch.data()); + UpdateTexture(tex, uploadBuffer.data()); + outWidth = width; + outHeight = height; } PS2Runtime::PS2Runtime() @@ -661,28 +559,54 @@ PS2Runtime::PS2Runtime() PS2Runtime::~PS2Runtime() { - requestStop(); - ps2_syscalls::detachAllGuestHostThreads(); - if (IsWindowReady()) + try { - CloseWindow(); - } + requestStop(); + ps2_syscalls::detachAllGuestHostThreads(); +#if defined(PLATFORM_VITA) + m_audioBackend.stopAll(); + m_audioBackend.setAudioReady(false); +#else + if (IsAudioDeviceReady()) + { + CloseAudioDevice(); + m_audioBackend.setAudioReady(false); + } +#endif + if (IsWindowReady()) + { + CloseWindow(); + } - m_loadedModules.clear(); + m_loadedModules.clear(); - m_functionTable.clear(); + m_functionTable.clear(); + } + catch (const std::exception &e) + { + std::cerr << "[~PS2Runtime] cleanup exception: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "[~PS2Runtime] cleanup exception: unknown" << std::endl; + } } -bool PS2Runtime::initialize(const char *title) +bool PS2Runtime::syncCoreSubsystems() { - if (!m_memory.initialize()) + uint8_t *const rdram = m_memory.getRDRAM(); + uint8_t *const gsVram = m_memory.getGSVRAM(); + if (!rdram || !gsVram) { - std::cerr << "Failed to initialize PS2 memory" << std::endl; return false; } - m_gs.init(m_memory.getGSVRAM(), static_cast(PS2_GS_VRAM_SIZE), &m_memory.gs()); - m_gs.reset(); + if (m_boundRdram == rdram && m_boundGSVram == gsVram) + { + return true; + } + + m_gs.init(gsVram, static_cast(PS2_GS_VRAM_SIZE), &m_memory.gs()); m_gifArbiter.setProcessPacketFn([this](const uint8_t *data, uint32_t size) { m_gs.processGIFPacket(data, size); }); m_memory.setGifArbiter(&m_gifArbiter); @@ -694,21 +618,55 @@ bool PS2Runtime::initialize(const char *title) { m_vu1.resume(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE, m_memory.getVU1Data(), PS2_VU1_DATA_SIZE, m_gs, &m_memory, itop, 65536); }); - - m_iop.init(m_memory.getRDRAM()); + m_iop.init(rdram); m_iop.reset(); - - SetConfigFlags(FLAG_WINDOW_RESIZABLE); - InitWindow(FB_WIDTH, FB_HEIGHT, title); - InitAudioDevice(); - m_audioBackend.setAudioReady(IsAudioDeviceReady()); - SetTargetFPS(60); - m_vu1.reset(); + m_boundRdram = rdram; + m_boundGSVram = gsVram; return true; } +bool PS2Runtime::initialize(const char *title) +{ + try + { + if (!m_memory.initialize()) + { + std::cerr << "Failed to initialize PS2 memory" << std::endl; + return false; + } + + if (!syncCoreSubsystems()) + { + std::cerr << "Failed to bind runtime core subsystems" << std::endl; + return false; + } + +#if defined(PLATFORM_VITA) + InitWindow(HOST_WINDOW_WIDTH, HOST_WINDOW_HEIGHT, title); // raylib vita does not support audio +#else + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + InitWindow(HOST_WINDOW_WIDTH, HOST_WINDOW_HEIGHT, title); + InitAudioDevice(); + m_audioBackend.setAudioReady(IsAudioDeviceReady()); +#endif + SetTargetFPS(60); + + return true; + } + catch (const std::exception &e) + { + std::cerr << "Failed to initialize PS2 runtime: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "Failed to initialize PS2 runtime: unknown exception" << std::endl; + } + + return false; +} + bool PS2Runtime::loadELF(const std::string &elfPath) { configureIoPathsFromElf(elfPath); @@ -865,11 +823,11 @@ bool PS2Runtime::loadELF(const std::string &elfPath) std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz); } - std::cout << "Loading segment: 0x" << std::hex << ph.vaddr - << " - 0x" << (static_cast(ph.vaddr) + static_cast(ph.memsz)) - << " (filesz: 0x" << ph.filesz - << ", memsz: 0x" << ph.memsz << ")" - << std::dec << std::endl; + RUNTIME_LOG("Loading segment: 0x" << std::hex << ph.vaddr + << " - 0x" << (static_cast(ph.vaddr) + static_cast(ph.memsz)) + << " (filesz: 0x" << ph.filesz + << ", memsz: 0x" << ph.memsz << ")" + << std::dec << std::endl); if (!scratch) { @@ -938,7 +896,7 @@ bool PS2Runtime::loadELF(const std::string &elfPath) ps2_game_overrides::applyMatching(*this, elfPath, m_cpuContext.pc); - std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; + RUNTIME_LOG("ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec); return true; } @@ -1010,12 +968,6 @@ bool PS2Runtime::hasFunction(uint32_t address) const return true; } - if (address == 0x2913E4u) - { - auto parent = m_functionTable.find(0x2913B0u); - return parent != m_functionTable.end(); - } - return false; } @@ -1029,15 +981,6 @@ PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address) return it->second; } - if (address == 0x2913E4u) - { - auto parent = m_functionTable.find(0x2913B0u); - if (parent != m_functionTable.end()) - { - return parent->second; - } - } - std::cerr << "Warning: Function at address 0x" << std::hex << address << std::dec << " not found" << std::endl; static RecompiledFunction defaultFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -1186,10 +1129,10 @@ void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint3 int &count = seen[address]; if (count < 3) { - std::cout << "[VU0] microprogram @0x" << std::hex << address - << " pc=0x" << ctx->pc - << " ra=0x" << static_cast(_mm_extract_epi32(ctx->r[31], 0)) - << std::dec << std::endl; + RUNTIME_LOG("[VU0] microprogram @0x" << std::hex << address + << " pc=0x" << ctx->pc + << " ra=0x" << static_cast(_mm_extract_epi32(ctx->r[31], 0)) + << std::dec << std::endl); } ++count; @@ -1781,7 +1724,9 @@ void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx) ++samePcCount; if ((samePcCount % kSamePcYieldInterval) == 0u) { - std::cout << "CPU is doing some work at PC 0x" << std::hex << pc << ". PC not updating." << std::endl; + PS2_IF_AGRESSIVE_LOGS({ + RUNTIME_LOG("CPU is doing some work at PC 0x" << std::hex << pc << ". PC not updating."); + }); std::this_thread::yield(); } } @@ -1883,17 +1828,18 @@ void PS2Runtime::reacquireGuestExecution(uint32_t depth) } } -void PS2Runtime::cooperativeGuestYield() +bool PS2Runtime::shouldPreemptGuestExecution() { - GuestExecutionReleaseScope release(this); - if (m_guestExecutionWaiters.load(std::memory_order_acquire) != 0u) - { - std::this_thread::sleep_for(std::chrono::microseconds(100)); - } - else + thread_local uint32_t s_backEdgeYieldCounter = 0u; + const uint32_t waiterCount = m_guestExecutionWaiters.load(std::memory_order_acquire); + const uint32_t yieldInterval = (waiterCount != 0u) ? 64u : 100u; + if (++s_backEdgeYieldCounter < yieldInterval) { - std::this_thread::yield(); + return false; } + + s_backEdgeYieldCounter = 0u; + return true; } uint8_t PS2Runtime::Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) @@ -2048,7 +1994,10 @@ void PS2Runtime::run() { m_stopRequested.store(false, std::memory_order_relaxed); ps2_stubs::resetSifState(); + ps2_syscalls::resetSoundDriverRpcState(); + ps2_stubs::resetAudioStubState(); ps2_stubs::resetGsSyncVCallbackState(); + ps2_stubs::resetMpegStubState(); ps2_syscalls::initializeGuestKernelState(m_memory.getRDRAM()); m_cpuContext.r[4] = _mm_setzero_si128(); m_cpuContext.r[5] = _mm_setzero_si128(); @@ -2058,7 +2007,7 @@ void PS2Runtime::run() m_debugSp.store(static_cast(_mm_extract_epi32(m_cpuContext.r[29], 0)), std::memory_order_relaxed); m_debugGp.store(static_cast(_mm_extract_epi32(m_cpuContext.r[28], 0)), std::memory_order_relaxed); - std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl; + RUNTIME_LOG("Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec); // A blank image to use as a framebuffer Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK); @@ -2075,8 +2024,8 @@ void PS2Runtime::run() { 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; + RUNTIME_LOG("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) { @@ -2089,92 +2038,67 @@ void PS2Runtime::run() g_activeThreads.fetch_sub(1, std::memory_order_relaxed); gameThreadFinished.store(true, std::memory_order_release); }); + ps2_syscalls::EnsureVSyncWorkerRunning(m_memory.getRDRAM(), this); + uint64_t tick = 0; while (!isStopRequested() && g_activeThreads.load(std::memory_order_relaxed) > 0) { - tick++; - if ((tick % 120) == 0) - { - uint64_t curDma = m_memory.dmaStartCount(); - uint64_t curGif = m_memory.gifCopyCount(); - uint64_t curGs = m_memory.gsWriteCount(); - uint64_t curVif = m_memory.vifWriteCount(); - const GSRegisters &gs = m_memory.gs(); - const uint32_t dbgPc = m_debugPc.load(std::memory_order_relaxed); - const uint32_t dbgRa = m_debugRa.load(std::memory_order_relaxed); - const uint32_t dbgSp = m_debugSp.load(std::memory_order_relaxed); - const uint32_t dbgGp = m_debugGp.load(std::memory_order_relaxed); - const int activeThreads = g_activeThreads.load(std::memory_order_relaxed); - - constexpr uint32_t kSndTransTypeAddr = 0x01E0E1C0u; - constexpr uint32_t kSndTransBankAddr = 0x01E0E1C8u; - constexpr uint32_t kSndTransLevelAddr = 0x01E0E1B8u; - constexpr uint32_t kSndGetAdrsAddr = 0x01E212D8u; - constexpr uint32_t kSndStatusMirrorAddr = 0x01E213C0u; - constexpr uint32_t kSndSeCheckAddr = 0x01E0EF10u; - constexpr uint32_t kSndMidiCheckAddr = 0x01E0EF20u; - - const uint32_t sndTransType = readGuestU32Wrapped(m_memory.getRDRAM(), kSndTransTypeAddr); - const uint32_t sndTransLevel = readGuestU32Wrapped(m_memory.getRDRAM(), kSndTransLevelAddr); - const uint32_t sndTransBank = readGuestU32Wrapped(m_memory.getRDRAM(), kSndTransBankAddr); - const uint32_t sndGetAdrs = readGuestU32Wrapped(m_memory.getRDRAM(), kSndGetAdrsAddr); - auto readGuestS16 = [&](uint32_t addr) -> int32_t + PS2_IF_AGRESSIVE_LOGS({ + tick++; + if ((tick % 120) == 0) { - const uint8_t *rdram = m_memory.getRDRAM(); - if (!rdram) - { - return 0; - } - const uint16_t raw = static_cast( - static_cast(rdram[(addr + 0u) & PS2_RAM_MASK]) | - (static_cast(rdram[(addr + 1u) & PS2_RAM_MASK]) << 8)); - return static_cast(raw); - }; - const int32_t sndMirrorMidi0 = readGuestS16(kSndStatusMirrorAddr + 0x1Eu); - const int32_t sndMirrorSe0 = readGuestS16(kSndStatusMirrorAddr + 0x26u); - int32_t sndBankMidiCheck = 0; - int32_t sndBankSeCheck = 0; - if (sndTransBank < 4u) - { - sndBankMidiCheck = readGuestS16(kSndMidiCheckAddr + (sndTransBank * 2u)); - } - if (sndTransBank < 5u) - { - sndBankSeCheck = readGuestS16(kSndSeCheckAddr + (sndTransBank * 2u)); + uint64_t curDma = m_memory.dmaStartCount(); + uint64_t curGif = m_memory.gifCopyCount(); + uint64_t curGs = m_memory.gsWriteCount(); + uint64_t curVif = m_memory.vifWriteCount(); + const GSRegisters &gs = m_memory.gs(); + const uint32_t dbgPc = m_debugPc.load(std::memory_order_relaxed); + const uint32_t dbgRa = m_debugRa.load(std::memory_order_relaxed); + const uint32_t dbgSp = m_debugSp.load(std::memory_order_relaxed); + const uint32_t dbgGp = m_debugGp.load(std::memory_order_relaxed); + const int activeThreads = g_activeThreads.load(std::memory_order_relaxed); + + std::cout << "[run:tick] tick=" << tick + << " pc=0x" << std::hex << dbgPc + << " ra=0x" << dbgRa + << " sp=0x" << dbgSp + << " gp=0x" << dbgGp + << " dispfb1=0x" << gs.dispfb1 + << " display1=0x" << gs.display1 + << std::dec + << " activeThreads=" << activeThreads + << " dma=" << curDma + << " gif=" << curGif + << " gsw=" << curGs + << " vif=" << curVif + << std::endl; } - std::cout << "[run:tick] tick=" << tick - << " pc=0x" << std::hex << dbgPc - << " ra=0x" << dbgRa - << " sp=0x" << dbgSp - << " gp=0x" << dbgGp - << " dispfb1=0x" << gs.dispfb1 - << " display1=0x" << gs.display1 - << std::dec - << " activeThreads=" << activeThreads - << " dma=" << curDma - << " gif=" << curGif - << " gsw=" << curGs - << " vif=" << curVif - << " sndType=" << sndTransType - << " sndLvl=" << sndTransLevel - << " sndBank=" << sndTransBank - << " getAdrs=0x" << std::hex << sndGetAdrs << std::dec - << " sndMirrorMidi0=" << sndMirrorMidi0 - << " sndMirrorSe0=" << sndMirrorSe0 - << " sndChkMidi=" << sndBankMidiCheck - << " sndChkSe=" << sndBankSeCheck - << std::endl; - } - UploadFrame(frameTex, this); + }); + uint32_t presentWidth = FB_WIDTH; + uint32_t presentHeight = DEFAULT_DISPLAY_HEIGHT; + UploadFrame(frameTex, this, presentWidth, presentHeight); BeginDrawing(); ClearBackground(BLACK); - DrawTexture(frameTex, 0, 0, WHITE); + const float srcWidth = static_cast(std::max(1u, presentWidth)); + const float srcHeight = static_cast(std::max(1u, presentHeight)); + const float screenWidth = static_cast(GetScreenWidth()); + const float screenHeight = static_cast(GetScreenHeight()); + const float scale = std::min(screenWidth / srcWidth, screenHeight / srcHeight); + const float dstWidth = srcWidth * scale; + const float dstHeight = srcHeight * scale; + const Rectangle srcRect{0.0f, 0.0f, srcWidth, srcHeight}; + const Rectangle dstRect{ + (screenWidth - dstWidth) * 0.5f, + (screenHeight - dstHeight) * 0.5f, + dstWidth, + dstHeight}; + DrawTexturePro(frameTex, srcRect, dstRect, Vector2{0.0f, 0.0f}, 0.0f, WHITE); EndDrawing(); if (WindowShouldClose()) { - std::cout << "[run] window close requested, breaking out of loop" << std::endl; + RUNTIME_LOG("[run] window close requested, breaking out of loop"); requestStop(); break; } @@ -2235,7 +2159,7 @@ void PS2Runtime::run() CloseWindow(); const int remainingThreads = g_activeThreads.load(std::memory_order_relaxed); - std::cout << "[run] exiting loop, activeThreads=" << remainingThreads << std::endl; + RUNTIME_LOG("[run] exiting loop, activeThreads=" << remainingThreads); if (remainingThreads > 0) { std::cerr << "[run] warning: " << remainingThreads diff --git a/ps2xRuntime/src/lib/ps2_stubs.cpp b/ps2xRuntime/src/lib/ps2_stubs.cpp deleted file mode 100644 index d1387e8a..00000000 --- a/ps2xRuntime/src/lib/ps2_stubs.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "ps2_stubs.h" -#include "ps2_runtime.h" -#include "ps2_runtime_macros.h" -#include "ps2_syscalls.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "raylib.h" -#include "stubs/helpers/ps2_stubs_helpers.inl" - -namespace ps2_stubs -{ - namespace - { - constexpr uint8_t kPadModeDualShock = 0x73; - constexpr uint8_t kPadAnalogCenter = 0x80; - - constexpr uint16_t kPadBtnSelect = 1u << 0; - constexpr uint16_t kPadBtnL3 = 1u << 1; - constexpr uint16_t kPadBtnR3 = 1u << 2; - constexpr uint16_t kPadBtnStart = 1u << 3; - constexpr uint16_t kPadBtnUp = 1u << 4; - constexpr uint16_t kPadBtnRight = 1u << 5; - constexpr uint16_t kPadBtnDown = 1u << 6; - constexpr uint16_t kPadBtnLeft = 1u << 7; - constexpr uint16_t kPadBtnL2 = 1u << 8; - constexpr uint16_t kPadBtnR2 = 1u << 9; - constexpr uint16_t kPadBtnL1 = 1u << 10; - constexpr uint16_t kPadBtnR1 = 1u << 11; - constexpr uint16_t kPadBtnTriangle = 1u << 12; - constexpr uint16_t kPadBtnCircle = 1u << 13; - constexpr uint16_t kPadBtnCross = 1u << 14; - constexpr uint16_t kPadBtnSquare = 1u << 15; - - struct PadInputState - { - uint16_t buttons = 0xFFFF; // active-low - uint8_t rx = kPadAnalogCenter; - uint8_t ry = kPadAnalogCenter; - uint8_t lx = kPadAnalogCenter; - uint8_t ly = kPadAnalogCenter; - }; - - std::mutex g_padOverrideMutex; - bool g_padOverrideEnabled = false; - PadInputState g_padOverrideState{}; - bool g_padDebugCached = false; - bool g_padDebugEnabled = false; - - uint8_t axisToByte(float axis) - { - axis = std::clamp(axis, -1.0f, 1.0f); - const float mapped = (axis + 1.0f) * 127.5f; - return static_cast(std::lround(mapped)); - } - - bool padDebugEnabled() - { - if (!g_padDebugCached) - { - const char *env = std::getenv("PS2_PAD_DEBUG"); - g_padDebugEnabled = (env && *env && std::strcmp(env, "0") != 0); - g_padDebugCached = true; - } - return g_padDebugEnabled; - } - - void setButton(PadInputState &state, uint16_t mask, bool pressed) - { - if (pressed) - { - state.buttons = static_cast(state.buttons & ~mask); - } - } - - int findFirstGamepad() - { - for (int i = 0; i < 4; ++i) - { - if (IsGamepadAvailable(i)) - { - return i; - } - } - return -1; - } - - void applyGamepadState(PadInputState &state) - { - if (!IsWindowReady()) - { - return; - } - - const int gamepad = findFirstGamepad(); - if (gamepad < 0) - { - return; - } - - // Raylib mapping (PS2 -> raylib buttons/axes): - // D-Pad -> LEFT_FACE_*, Cross/Circle/Square/Triangle -> RIGHT_FACE_* - // L1/R1 -> TRIGGER_1, L2/R2 -> TRIGGER_2, L3/R3 -> THUMB - // Select/Start -> MIDDLE_LEFT/MIDDLE_RIGHT - state.lx = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_X)); - state.ly = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_Y)); - state.rx = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_X)); - state.ry = axisToByte(GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_Y)); - - setButton(state, kPadBtnUp, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)); - setButton(state, kPadBtnDown, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)); - setButton(state, kPadBtnLeft, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)); - setButton(state, kPadBtnRight, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)); - - setButton(state, kPadBtnCross, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)); - setButton(state, kPadBtnCircle, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)); - setButton(state, kPadBtnSquare, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)); - setButton(state, kPadBtnTriangle, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)); - - setButton(state, kPadBtnL1, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)); - setButton(state, kPadBtnR1, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)); - setButton(state, kPadBtnL2, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_2)); - setButton(state, kPadBtnR2, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_2)); - - setButton(state, kPadBtnL3, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)); - setButton(state, kPadBtnR3, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)); - - setButton(state, kPadBtnSelect, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)); - setButton(state, kPadBtnStart, IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)); - } - - void applyKeyboardState(PadInputState &state, bool allowAnalog) - { - if (!IsWindowReady()) - { - return; - } - - // Keyboard mapping (PS2 -> keys): - // D-Pad: arrows, Square/Cross/Circle/Triangle: Z/X/C/V - // L1/R1: Q/E, L2/R2: 1/3, Start/Select: Enter/RightShift - // L3/R3: LeftCtrl/RightCtrl, Analog left: WASD - setButton(state, kPadBtnUp, IsKeyDown(KEY_UP)); - setButton(state, kPadBtnDown, IsKeyDown(KEY_DOWN)); - setButton(state, kPadBtnLeft, IsKeyDown(KEY_LEFT)); - setButton(state, kPadBtnRight, IsKeyDown(KEY_RIGHT)); - - setButton(state, kPadBtnSquare, IsKeyDown(KEY_Z)); - setButton(state, kPadBtnCross, IsKeyDown(KEY_X)); - setButton(state, kPadBtnCircle, IsKeyDown(KEY_C)); - setButton(state, kPadBtnTriangle, IsKeyDown(KEY_V)); - - setButton(state, kPadBtnL1, IsKeyDown(KEY_Q)); - setButton(state, kPadBtnR1, IsKeyDown(KEY_E)); - setButton(state, kPadBtnL2, IsKeyDown(KEY_ONE)); - setButton(state, kPadBtnR2, IsKeyDown(KEY_THREE)); - - setButton(state, kPadBtnStart, IsKeyDown(KEY_ENTER)); - setButton(state, kPadBtnSelect, IsKeyDown(KEY_RIGHT_SHIFT)); - setButton(state, kPadBtnL3, IsKeyDown(KEY_LEFT_CONTROL)); - setButton(state, kPadBtnR3, IsKeyDown(KEY_RIGHT_CONTROL)); - - if (!allowAnalog) - { - return; - } - - float ax = 0.0f; - float ay = 0.0f; - if (IsKeyDown(KEY_D)) - ax += 1.0f; - if (IsKeyDown(KEY_A)) - ax -= 1.0f; - if (IsKeyDown(KEY_S)) - ay += 1.0f; - if (IsKeyDown(KEY_W)) - ay -= 1.0f; - - if (ax != 0.0f || ay != 0.0f) - { - state.lx = axisToByte(ax); - state.ly = axisToByte(ay); - } - } - - void fillPadStatus(uint8_t *data, const PadInputState &state) - { - std::memset(data, 0, 32); - data[1] = kPadModeDualShock; - data[2] = static_cast(state.buttons & 0xFFu); - data[3] = static_cast((state.buttons >> 8) & 0xFFu); - data[4] = state.rx; - data[5] = state.ry; - data[6] = state.lx; - data[7] = state.ly; - } - } - -#include "stubs/ps2_stubs_libc.inl" -#include "stubs/ps2_stubs_ps2.inl" -#include "stubs/ps2_stubs_misc.inl" - -#include "stubs/ps2_stubs_gs.inl" -#include "stubs/ps2_stubs_residentEvilCV.inl" - - void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) - { - TODO_NAMED("unknown", rdram, ctx, runtime); - } - - 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=" << stubName - << " PC=0x" << std::hex << ctx->pc - << ", RA=0x" << caller_ra - << ", Stub# guess (from $v0)=0x" << stub_num << std::dec << std::endl; - - // More context for debugging - 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; - - setReturnS32(ctx, -1); // Return error - } - - void setPadOverrideState(uint16_t buttons, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry) - { - std::lock_guard lock(g_padOverrideMutex); - g_padOverrideEnabled = true; - g_padOverrideState.buttons = buttons; - g_padOverrideState.lx = lx; - g_padOverrideState.ly = ly; - g_padOverrideState.rx = rx; - g_padOverrideState.ry = ry; - } - - void clearPadOverrideState() - { - std::lock_guard lock(g_padOverrideMutex); - g_padOverrideEnabled = false; - g_padOverrideState = PadInputState{}; - } - -} diff --git a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp index 115c3ffa..e478fa42 100644 --- a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp +++ b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp @@ -1,9 +1,8 @@ // Based on Blackline Interactive implementation -#include "ps2_memory.h" +#include "runtime/ps2_memory.h" #include #include -#include -#include +#include "ps2_log.h" enum VIFCmd : uint8_t { @@ -33,6 +32,21 @@ namespace { std::atomic s_debugVu1KickCount{0}; std::atomic s_debugVif1OpcodeCount{0}; + constexpr uint8_t kGifFmtImage = 2u; + + uint32_t gifImageQwcFromTag(const uint8_t *data, uint32_t sizeBytes) + { + if (!data || sizeBytes < 16u) + return 0u; + + uint64_t tagLo = 0u; + std::memcpy(&tagLo, data, sizeof(tagLo)); + const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); + if (flg != kGifFmtImage) + return 0u; + + return static_cast(tagLo & 0x7FFFu); + } } void PS2Memory::processVIF1Data(uint32_t srcPhys, uint32_t sizeBytes) @@ -66,6 +80,37 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes) while (pos + 4 <= sizeBytes) { + if (m_vif1PendingPath2ImageQwc != 0u) + { + const uint32_t availableQw = (sizeBytes - pos) / 16u; + if (availableQw == 0u) + { + break; + } + + const uint32_t chunkQw = std::min(m_vif1PendingPath2ImageQwc, availableQw); + std::vector imagePacket(16u + static_cast(chunkQw) * 16u, 0u); + const uint64_t imageTag = + static_cast(chunkQw & 0x7FFFu) | + ((m_vif1PendingPath2ImageQwc == chunkQw) ? (1ull << 15) : 0ull) | + (static_cast(kGifFmtImage) << 58); + std::memcpy(imagePacket.data(), &imageTag, sizeof(imageTag)); + std::memcpy(imagePacket.data() + 16u, data + pos, static_cast(chunkQw) * 16u); + submitGifPacket(GifPathId::Path2, + imagePacket.data(), + static_cast(imagePacket.size()), + true, + m_vif1PendingPath2DirectHl); + + pos += chunkQw * 16u; + m_vif1PendingPath2ImageQwc -= chunkQw; + if (m_vif1PendingPath2ImageQwc == 0u) + { + m_vif1PendingPath2DirectHl = false; + } + continue; + } + uint32_t cmd; memcpy(&cmd, data + pos, 4); pos += 4; @@ -78,13 +123,13 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes) const uint32_t opcodeIndex = s_debugVif1OpcodeCount.fetch_add(1, std::memory_order_relaxed); if (opcodeIndex < 160u) { - std::cout << "[vif1:cmd] idx=" << opcodeIndex - << " opcode=0x" << std::hex << static_cast(opcode) - << " imm=0x" << imm - << std::dec - << " num=" << static_cast(num) - << " irq=" << static_cast(irq ? 1u : 0u) - << std::endl; + RUNTIME_LOG("[vif1:cmd] idx=" << opcodeIndex + << " opcode=0x" << std::hex << static_cast(opcode) + << " imm=0x" << imm + << std::dec + << " num=" << static_cast(num) + << " irq=" << static_cast(irq ? 1u : 0u) + << std::endl); } // Track most-recent command for VIFn_CODE emulation. @@ -155,12 +200,12 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes) const uint32_t kickIndex = s_debugVu1KickCount.fetch_add(1, std::memory_order_relaxed); if (kickIndex < 48u) { - std::cout << "[vif1:mscal] idx=" << kickIndex - << " opcode=0x" << std::hex << static_cast(opcode) - << " imm=0x" << imm - << " startPc=0x" << startPC - << " itop=0x" << vif1_regs.itop - << std::dec << std::endl; + RUNTIME_LOG("[vif1:mscal] idx=" << kickIndex + << " opcode=0x" << std::hex << static_cast(opcode) + << " imm=0x" << imm + << " startPc=0x" << startPC + << " itop=0x" << vif1_regs.itop + << std::dec << std::endl); } if (m_vu1MscalCallback) m_vu1MscalCallback(startPC, vif1_regs.itop); @@ -174,10 +219,10 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes) const uint32_t kickIndex = s_debugVu1KickCount.fetch_add(1, std::memory_order_relaxed); if (kickIndex < 48u) { - std::cout << "[vif1:mscnt] idx=" << kickIndex - << " itop=0x" << std::hex << vif1_regs.itop - << " pc=resume" - << std::dec << std::endl; + RUNTIME_LOG("[vif1:mscnt] idx=" << kickIndex + << " itop=0x" << std::hex << vif1_regs.itop + << " pc=resume" + << std::dec << std::endl); } if (m_vu1MscntCallback) m_vu1MscntCallback(vif1_regs.itop); @@ -243,6 +288,17 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes) { const bool directHl = (opcode == VIF_DIRECTHL); submitGifPacket(GifPathId::Path2, data + pos, qwCount * 16, true, directHl); + + const uint32_t imageQw = gifImageQwcFromTag(data + pos, qwCount * 16u); + if (imageQw != 0u) + { + const uint32_t inlineImageQw = (qwCount > 0u) ? (qwCount - 1u) : 0u; + if (imageQw > inlineImageQw) + { + m_vif1PendingPath2ImageQwc = imageQw - inlineImageQw; + m_vif1PendingPath2DirectHl = directHl; + } + } } pos += qwCount * 16; diff --git a/ps2xRuntime/src/lib/ps2_vu1.cpp b/ps2xRuntime/src/lib/ps2_vu1.cpp index ed085209..10b7dd80 100644 --- a/ps2xRuntime/src/lib/ps2_vu1.cpp +++ b/ps2xRuntime/src/lib/ps2_vu1.cpp @@ -1,7 +1,8 @@ -#include "ps2_vu1.h" -#include "ps2_gs_gpu.h" -#include "ps2_gif_arbiter.h" -#include "ps2_memory.h" +#include "runtime/ps2_vu1.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_gif_arbiter.h" +#include "runtime/ps2_memory.h" +#include "ps2_log.h" #include #include #include @@ -1225,12 +1226,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz const uint32_t debugIndex = s_debugVu1XgkickCount.fetch_add(1, std::memory_order_relaxed); if (debugIndex < 64u) { - std::cout << "[vu1:xgkick] idx=" << debugIndex - << " addr=0x" << std::hex << addr - << " totalBytes=0x" << totalBytes - << std::dec - << " wrap=" << static_cast((addr + totalBytes > dataSize) ? 1u : 0u) - << std::endl; + RUNTIME_LOG("[vu1:xgkick] idx=" << debugIndex + << " addr=0x" << std::hex << addr + << " totalBytes=0x" << totalBytes + << std::dec + << " wrap=" << static_cast((addr + totalBytes > dataSize) ? 1u : 0u) + << std::endl); } if (addr + totalBytes <= dataSize) diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl deleted file mode 100644 index f7a07f7c..00000000 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl +++ /dev/null @@ -1,856 +0,0 @@ -namespace -{ - std::mutex g_gs_sync_v_mutex; - uint64_t g_gs_sync_v_base_tick = 0u; - std::mutex g_gs_sync_v_callback_mutex; - uint32_t g_gs_sync_v_callback_func = 0u; - uint32_t g_gs_sync_v_callback_gp = 0u; - uint32_t g_gs_sync_v_callback_sp = 0u; - uint32_t g_gs_sync_v_callback_stack_base = 0u; - uint32_t g_gs_sync_v_callback_stack_top = 0u; - uint32_t g_gs_sync_v_callback_bad_pc_logs = 0u; -} - -static void resetGsSyncVState() -{ - std::lock_guard lock(g_gs_sync_v_mutex); - g_gs_sync_v_base_tick = ps2_syscalls::GetCurrentVSyncTick(); -} - -static int32_t getGsSyncVFieldForTick(uint64_t tick) -{ - std::lock_guard lock(g_gs_sync_v_mutex); - if (tick <= g_gs_sync_v_base_tick) - { - return 0; - } - - return static_cast((tick - g_gs_sync_v_base_tick - 1u) & 1u); -} - -void resetGsSyncVCallbackState() -{ - { - std::lock_guard lock(g_gs_sync_v_callback_mutex); - g_gs_sync_v_callback_func = 0u; - g_gs_sync_v_callback_gp = 0u; - g_gs_sync_v_callback_sp = 0u; - g_gs_sync_v_callback_stack_base = 0u; - g_gs_sync_v_callback_stack_top = 0u; - g_gs_sync_v_callback_bad_pc_logs = 0u; - } - resetGsSyncVState(); -} - -void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick) -{ - if (!rdram || !runtime) - { - return; - } - - uint32_t callback = 0u; - uint32_t gp = 0u; - uint32_t callbackStackTop = 0u; - const uint64_t callbackTick = (tick != 0u) ? tick : ps2_syscalls::GetCurrentVSyncTick(); - { - std::lock_guard lock(g_gs_sync_v_callback_mutex); - callback = g_gs_sync_v_callback_func; - gp = g_gs_sync_v_callback_gp; - callbackStackTop = g_gs_sync_v_callback_stack_top; - if (callback == 0u) - { - return; - } - } - - if (!runtime->hasFunction(callback)) - { - return; - } - - if (callbackStackTop == 0u) - { - constexpr uint32_t kCallbackStackSize = 0x4000u; - const uint32_t stackTop = runtime->reserveAsyncCallbackStack(kCallbackStackSize, 16u); - if (stackTop != 0u) - { - std::lock_guard lock(g_gs_sync_v_callback_mutex); - if (g_gs_sync_v_callback_stack_top == 0u) - { - g_gs_sync_v_callback_stack_base = stackTop - (kCallbackStackSize - 0x10u); - g_gs_sync_v_callback_stack_top = stackTop; - } - callbackStackTop = g_gs_sync_v_callback_stack_top; - } - } - - try - { - R5900Context callbackCtx{}; - SET_GPR_U32(&callbackCtx, 28, gp); - SET_GPR_U32(&callbackCtx, 29, (callbackStackTop != 0u) ? callbackStackTop : (PS2_RAM_SIZE - 0x10u)); - SET_GPR_U32(&callbackCtx, 31, 0u); - SET_GPR_U32(&callbackCtx, 4, static_cast(callbackTick)); - callbackCtx.pc = callback; - - uint32_t steps = 0u; - while (callbackCtx.pc != 0u && !runtime->isStopRequested() && steps < 1024u) - { - if (!runtime->hasFunction(callbackCtx.pc)) - { - if (g_gs_sync_v_callback_bad_pc_logs < 16u) - { - std::cerr << "[sceGsSyncVCallback:bad-pc] pc=0x" << std::hex << callbackCtx.pc - << " ra=0x" << getRegU32(&callbackCtx, 31) - << " sp=0x" << getRegU32(&callbackCtx, 29) - << " gp=0x" << getRegU32(&callbackCtx, 28) - << std::dec << std::endl; - ++g_gs_sync_v_callback_bad_pc_logs; - } - callbackCtx.pc = 0u; - break; - } - - auto step = runtime->lookupFunction(callbackCtx.pc); - if (!step) - { - break; - } - ++steps; - step(rdram, &callbackCtx, runtime); - } - } - catch (const std::exception &e) - { - static uint32_t warnCount = 0u; - if (warnCount < 8u) - { - std::cerr << "[sceGsSyncVCallback] callback exception: " << e.what() << std::endl; - ++warnCount; - } - } -} - -void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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); - const uint32_t totalImageBytes = rowBytes * static_cast(img.height); - const uint32_t headerQwc = 12u; - const uint32_t imageQwc = (totalImageBytes + 15u) / 16u; - const uint32_t totalQwc = headerQwc + imageQwc; - - uint32_t pktAddr = runtime->guestMalloc(totalQwc * 16u, 16u); - if (pktAddr == 0) - { - setReturnS32(ctx, -1); - return; - } - - uint8_t *pkt = getMemPtr(rdram, pktAddr); - const uint8_t *src = getConstMemPtr(rdram, srcAddr); - if (!pkt || !src) - { - setReturnS32(ctx, -1); - return; - } - - uint32_t dbp = (static_cast(img.vram_addr) * 2048u) / 256u; - uint32_t dsax = static_cast(img.x); - uint32_t dsay = static_cast(img.y); - - // Full messy - uint64_t *q = reinterpret_cast(pkt); - q[0] = 0x1000000000000004ULL; - q[1] = 0x0E0E0E0E0E0E0E0EULL; - q[2] = (static_cast(img.psm & 0x3Fu) << 24) | (static_cast(1u) << 16) | - (static_cast(dbp & 0x3FFFu) << 32) | (static_cast(fbw & 0x3Fu) << 48) | - (static_cast(img.psm & 0x3Fu) << 56); - q[3] = 0x50ULL; - q[4] = (static_cast(dsay & 0x7FFu) << 48) | (static_cast(dsax & 0x7FFu) << 32); - q[5] = 0x51ULL; - q[6] = (static_cast(img.height) << 32) | static_cast(img.width); - q[7] = 0x52ULL; - q[8] = 0ULL; - q[9] = 0x53ULL; - q[10] = (static_cast(2) << 58) | (static_cast(imageQwc) & 0x7FFF) | - (1ULL << 15); - q[11] = 0ULL; - - std::memcpy(pkt + 12 * 8, src, totalImageBytes); - - constexpr uint32_t GIF_CHANNEL = 0x1000A000; - constexpr uint32_t CHCR_STR_MODE0 = 0x101u; - auto &mem = runtime->memory(); - mem.writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); - mem.writeIORegister(GIF_CHANNEL + 0x20u, totalQwc & 0xFFFFu); - mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); - - setReturnS32(ctx, 0); -} - -void sceGsExecStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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); - const uint32_t totalImageBytes = rowBytes * static_cast(img.height); - - uint8_t *dst = getMemPtr(rdram, dstAddr); - if (!dst) - { - setReturnS32(ctx, -1); - return; - } - - uint32_t sbp = (static_cast(img.vram_addr) * 2048u) / 256u; - uint64_t bitbltbuf = (static_cast(sbp & 0x3FFFu) << 0) | - (static_cast(fbw & 0x3Fu) << 16) | - (static_cast(img.psm & 0x3Fu) << 24) | - (static_cast(0u) << 32) | - (static_cast(1u) << 48) | - (static_cast(0u) << 56); - uint64_t trxpos = (static_cast(img.x & 0x7FFu) << 0) | - (static_cast(img.y & 0x7FFu) << 16) | - (static_cast(0u) << 32) | - (static_cast(0u) << 48); - uint64_t trxreg = static_cast(img.height) << 32 | static_cast(img.width); - - uint32_t pktAddr = runtime->guestMalloc(80u, 16u); - if (pktAddr == 0) - { - setReturnS32(ctx, -1); - return; - } - - uint8_t *pkt = getMemPtr(rdram, pktAddr); - if (!pkt) - { - setReturnS32(ctx, -1); - return; - } - - uint64_t *q = reinterpret_cast(pkt); - q[0] = 0x1000000000000004ULL; - q[1] = 0x0E0E0E0E0E0E0E0EULL; - q[2] = bitbltbuf; - q[3] = 0x50ULL; - q[4] = trxpos; - q[5] = 0x51ULL; - q[6] = trxreg; - q[7] = 0x52ULL; - q[8] = 1ULL; - q[9] = 0x53ULL; - - constexpr uint32_t GIF_CHANNEL = 0x1000A000; - constexpr uint32_t CHCR_STR_MODE0 = 0x101u; - auto &mem = runtime->memory(); - mem.writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); - mem.writeIORegister(GIF_CHANNEL + 0x20u, 5u); - mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); - mem.processPendingTransfers(); - - runtime->gs().consumeLocalToHostBytes(dst, totalImageBytes); - - setReturnS32(ctx, 0); -} - -void sceGsGetGParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t addr = writeGsGParamToScratch(runtime); - setReturnU32(ctx, addr); -} - -void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t envAddr = getRegU32(ctx, 4); - GsDispEnvMem env{}; - if (!readGsDispEnv(rdram, envAddr, env)) - { - setReturnS32(ctx, -1); - return; - } - applyGsDispEnv(runtime, env); - setReturnS32(ctx, 0); -} - -void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t envAddr = getRegU32(ctx, 4); - GsRegPairMem pairs[8]{}; - if (!readGsRegPairs(rdram, envAddr, pairs, 8u)) - { - setReturnS32(ctx, -1); - return; - } - applyGsRegPairs(runtime, pairs, 8u); - setReturnS32(ctx, 0); -} - -void sceGsResetGraph(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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); - resetGsSyncVState(); - - uint64_t pmode = makePmode(1, 0, 0, 0, 0, 0x80); - uint64_t smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1); - uint64_t dispfb = makeDispFb(0, 10, 0, 0, 0); - uint64_t display = makeDisplay(0, 0, 0, 0, 639, 447); - uint64_t bgcolor = 0ULL; - - if (runtime) - { - uint32_t pktAddr = runtime->guestMalloc(192u, 16u); - if (pktAddr != 0u) - { - uint8_t *pkt = getMemPtr(rdram, pktAddr); - if (pkt) - { - uint64_t *q = reinterpret_cast(pkt); - q[0] = 0x1000000000000005ULL; - q[1] = 0x0E0E0E0E0E0E0E0EULL; - q[2] = pmode; - q[3] = 0x41ULL; - q[4] = smode2; - q[5] = 0x42ULL; - q[6] = dispfb; - q[7] = 0x59ULL; - q[8] = display; - q[9] = 0x5aULL; - q[10] = bgcolor; - q[11] = 0x5fULL; - constexpr uint32_t GIF_CHANNEL = 0x1000A000; - constexpr uint32_t CHCR_STR_MODE0 = 0x101u; - auto &mem = runtime->memory(); - mem.writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); - mem.writeIORegister(GIF_CHANNEL + 0x20u, 12u); - mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); - } - } - } - } - - setReturnS32(ctx, 0); -} - -void sceGsResetPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)ctx; - (void)runtime; - setReturnS32(ctx, 0); -} - -void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t envAddr = getRegU32(ctx, 4); - uint32_t psm = getRegU32(ctx, 5); - uint32_t w = getRegU32(ctx, 6); - uint32_t h = getRegU32(ctx, 7); - const uint32_t ztest = readStackU32(rdram, ctx, 16); - const uint32_t zpsm = readStackU32(rdram, ctx, 20); - const uint32_t clear = readStackU32(rdram, ctx, 24); - (void)clear; - - if (w == 0u) - { - w = 640u; - } - if (h == 0u) - { - h = 448u; - } - - const uint32_t fbw = std::max(1u, (w + 63u) / 64u); - const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u); - const uint64_t smode2 = - (static_cast(g_gparam.interlace & 0x1u) << 0) | - (static_cast(g_gparam.ffmode & 0x1u) << 1); - const uint64_t dispfb = makeDispFb(0u, fbw, psm, 0u, 0u); - const uint64_t display = makeDisplay(636u, 32u, 0u, 0u, w - 1u, h - 1u); - - const int32_t drawWidth = static_cast(w); - const int32_t drawHeight = static_cast(h); - - uint32_t zbufAddr = 0u; - { - R5900Context temp = *ctx; - sceGszbufaddr(rdram, &temp, runtime); - zbufAddr = getRegU32(&temp, 2); - } - - GsDBuffDcMem db{}; - db.disp[0].pmode = pmode; - db.disp[0].smode2 = smode2; - db.disp[0].dispfb = dispfb; - db.disp[0].display = display; - db.disp[0].bgcolor = 0u; - db.disp[1] = db.disp[0]; - - db.giftag0 = {makeGiftagAplusD(14u), 0x0E0E0E0E0E0E0E0EULL}; - seedGsDrawEnv1(db.draw01, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); - seedGsDrawEnv2(db.draw02, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); - db.giftag1 = db.giftag0; - seedGsDrawEnv1(db.draw11, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); - seedGsDrawEnv2(db.draw12, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); - - if (!writeGsDBuffDc(rdram, envAddr, db)) - { - setReturnS32(ctx, -1); - return; - } - setReturnS32(ctx, 0); -} - -void sceGsSetDefDBuff(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime) -{ - const uint32_t envAddr = getRegU32(ctx, 4); - uint32_t psm = getRegU32(ctx, 5); - uint32_t w = getRegU32(ctx, 6); - uint32_t h = getRegU32(ctx, 7); - const uint32_t ztest = readStackU32(rdram, ctx, 16); - const uint32_t zpsm = readStackU32(rdram, ctx, 20); - const uint32_t clear = readStackU32(rdram, ctx, 24); - (void)clear; - - if (w == 0u) - { - w = 640u; - } - if (h == 0u) - { - h = 448u; - } - - const uint32_t fbw = std::max(1u, (w + 63u) / 64u); - const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u); - const uint64_t smode2 = - (static_cast(g_gparam.interlace & 0x1u) << 0) | - (static_cast(g_gparam.ffmode & 0x1u) << 1); - const uint64_t dispfb = makeDispFb(0u, fbw, psm, 0u, 0u); - const uint64_t display = makeDisplay(636u, 32u, 0u, 0u, w - 1u, h - 1u); - - const int32_t drawWidth = static_cast(w); - const int32_t drawHeight = static_cast(h); - - uint32_t zbufAddr = 0u; - { - R5900Context temp = *ctx; - sceGszbufaddr(rdram, &temp, runtime); - zbufAddr = getRegU32(&temp, 2); - } - - GsDBuffMem db{}; - db.disp[0].pmode = pmode; - db.disp[0].smode2 = smode2; - db.disp[0].dispfb = dispfb; - db.disp[0].display = display; - db.disp[0].bgcolor = 0u; - db.disp[1] = db.disp[0]; - - db.giftag0 = { makeGiftagAplusD(14u), 0x0E0E0E0E0E0E0E0EULL }; - seedGsDrawEnv1(db.draw0, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); - db.giftag1 = db.giftag0; - seedGsDrawEnv1(db.draw1, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false); - - if (!writeGsDBuff(rdram, envAddr, db)) - { - setReturnS32(ctx, -1); - return; - } - setReturnS32(ctx, 0); -} - -void sceGsSetDefDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - uint32_t envAddr = getRegU32(ctx, 4); - uint32_t param_2 = getRegU32(ctx, 5); - int32_t w = static_cast(static_cast(getRegU32(ctx, 6) & 0xFFFF)); - int32_t h = static_cast(static_cast(getRegU32(ctx, 7) & 0xFFFF)); - uint32_t param_5 = readStackU32(rdram, ctx, 16); - uint32_t param_6 = readStackU32(rdram, ctx, 20); - - if (w <= 0) - w = 640; - if (h <= 0) - h = 448; - - uint32_t psm = param_2 & 0xFU; - uint32_t fbw = ((static_cast(w) + 63u) >> 6) & 0x3FU; - sceGszbufaddr(rdram, ctx, runtime); - int32_t zbuf = static_cast(static_cast(getRegU32(ctx, 2) & 0xFFFF)); - - GsDrawEnv1Mem env{}; - seedGsDrawEnv1(env, - w, - h, - 0u, - fbw, - psm, - static_cast(zbuf), - param_6 & 0xFu, - param_5 & 0x3u, - (param_2 & 2u) != 0u); - - uint8_t *const ptr = getMemPtr(rdram, envAddr); - if (!ptr) - { - setReturnS32(ctx, 8); - return; - } - std::memcpy(ptr, &env, sizeof(env)); - - setReturnS32(ctx, 8); -} - -void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t envAddr = getRegU32(ctx, 4); - uint32_t param_2 = getRegU32(ctx, 5); - int32_t w = static_cast(static_cast(getRegU32(ctx, 6) & 0xFFFF)); - int32_t h = static_cast(static_cast(getRegU32(ctx, 7) & 0xFFFF)); - uint32_t param_5 = readStackU32(rdram, ctx, 16); - uint32_t param_6 = readStackU32(rdram, ctx, 20); - - if (w <= 0) - w = 640; - if (h <= 0) - h = 448; - - uint32_t psm = param_2 & 0xFU; - uint32_t fbw = ((static_cast(w) + 63u) >> 6) & 0x3FU; - sceGszbufaddr(rdram, ctx, runtime); - int32_t zbuf = static_cast(static_cast(getRegU32(ctx, 2) & 0xFFFF)); - - GsDrawEnv2Mem env{}; - seedGsDrawEnv2(env, - w, - h, - 0u, - fbw, - psm, - static_cast(zbuf), - param_6 & 0xFu, - param_5 & 0x3u, - (param_2 & 2u) != 0u); - - uint8_t *const ptr = getMemPtr(rdram, envAddr); - if (!ptr) - { - setReturnS32(ctx, 8); - return; - } - - std::memcpy(ptr, &env, sizeof(env)); - setReturnS32(ctx, 8); -} - -void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - sceGsSetDefLoadImage(rdram, ctx, runtime); -} - -void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t envAddr = getRegU32(ctx, 4); - const uint32_t which = getRegU32(ctx, 5) & 1u; - - GsDBuffDcMem db{}; - if (!runtime || !readGsDBuffDc(rdram, envAddr, db)) - { - setReturnS32(ctx, -1); - return; - } - - applyGsDispEnv(runtime, db.disp[which]); - if (which == 0u) - { - applyGsRegPairs(runtime, reinterpret_cast(&db.draw01), 8u); - applyGsRegPairs(runtime, reinterpret_cast(&db.draw02), 8u); - } - else - { - applyGsRegPairs(runtime, reinterpret_cast(&db.draw11), 8u); - applyGsRegPairs(runtime, reinterpret_cast(&db.draw12), 8u); - } - - setReturnS32(ctx, static_cast(which ^ 1u)); -} - -void sceGsSwapDBuff(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime) -{ - const uint32_t envAddr = getRegU32(ctx, 4); - const uint32_t which = getRegU32(ctx, 5) & 1u; - - GsDBuffMem db{}; - if (!runtime || !readGsDBuff(rdram, envAddr, db)) - { - setReturnS32(ctx, -1); - return; - } - - applyGsDispEnv(runtime, db.disp[which]); - if (which == 0u) - { - applyGsRegPairs(runtime, reinterpret_cast(&db.draw0), 8u); - } - else - { - applyGsRegPairs(runtime, reinterpret_cast(&db.draw1), 8u); - } - - setReturnS32(ctx, static_cast(which ^ 1u)); -} - -void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int32_t mode = static_cast(getRegU32(ctx, 4)); - auto &mem = runtime->memory(); - - if (mode == 0) - { - mem.processPendingTransfers(); - - uint32_t count = 0; - constexpr uint32_t kTimeout = 0x1000000; - - while ((mem.readIORegister(0x10009000) & 0x100) != 0) - { - if (++count > kTimeout) - { - setReturnS32(ctx, -1); - return; - } - } - - while ((mem.readIORegister(0x1000A000) & 0x100) != 0) - { - if (++count > kTimeout) - { - setReturnS32(ctx, -1); - return; - } - } - - while ((mem.readIORegister(0x10003C00) & 0x1F000003) != 0) - { - if (++count > kTimeout) - { - setReturnS32(ctx, -1); - return; - } - } - - while ((mem.readIORegister(0x10003020) & 0xC00) != 0) - { - if (++count > kTimeout) - { - setReturnS32(ctx, -1); - return; - } - } - - setReturnS32(ctx, 0); - } - else - { - uint32_t result = 0; - - if ((mem.readIORegister(0x10009000) & 0x100) != 0) - result |= 1; - if ((mem.readIORegister(0x1000A000) & 0x100) != 0) - result |= 2; - if ((mem.readIORegister(0x10003C00) & 0x1F000003) != 0) - result |= 4; - if ((mem.readIORegister(0x10003020) & 0xC00) != 0) - result |= 0x10; - - setReturnS32(ctx, result); - } -} - -void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint64_t tick = ps2_syscalls::WaitForNextVSyncTick(rdram, runtime); - if (g_gparam.interlace != 0u) - { - setReturnS32(ctx, getGsSyncVFieldForTick(tick)); - return; - } - - setReturnS32(ctx, 1); -} - -void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t newCallback = getRegU32(ctx, 4); - const uint32_t callerPc = ctx ? ctx->pc : 0u; - const uint32_t callerRa = ctx ? getRegU32(ctx, 31) : 0u; - const uint32_t gp = getRegU32(ctx, 28); - const uint32_t sp = getRegU32(ctx, 29); - - uint32_t oldCallback = 0u; - { - std::lock_guard lock(g_gs_sync_v_callback_mutex); - oldCallback = g_gs_sync_v_callback_func; - g_gs_sync_v_callback_func = newCallback; - if (newCallback != 0u) - { - g_gs_sync_v_callback_gp = gp; - g_gs_sync_v_callback_sp = sp; - } - } - - static uint32_t s_syncVCallbackLogCount = 0u; - if (s_syncVCallbackLogCount < 128u) - { - std::cout << "[sceGsSyncVCallback:set] new=0x" << std::hex << newCallback - << " old=0x" << oldCallback - << " callerPc=0x" << callerPc - << " callerRa=0x" << callerRa - << " gp=0x" << gp - << " sp=0x" << sp - << std::dec << std::endl; - ++s_syncVCallbackLogCount; - } - - if (newCallback != 0u) - { - ps2_syscalls::EnsureVSyncWorkerRunning(rdram, runtime); - } - - setReturnU32(ctx, oldCallback); -} - -void sceGszbufaddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - uint32_t param_1 = getRegU32(ctx, 4); - int32_t w = static_cast(static_cast(getRegU32(ctx, 6) & 0xFFFF)); - int32_t h = static_cast(static_cast(getRegU32(ctx, 7) & 0xFFFF)); - - int32_t width_blocks = (w + 63) >> 6; - if (w + 63 < 0) - width_blocks = (w + 126) >> 6; - - int32_t height_blocks; - if ((param_1 & 2) != 0) - { - int32_t v = (h + 63) >> 6; - if (h + 63 < 0) - v = (h + 126) >> 6; - height_blocks = v; - } - else - { - int32_t v = (h + 31) >> 5; - if (h + 31 < 0) - v = (h + 62) >> 5; - height_blocks = v; - } - - int32_t product = width_blocks * height_blocks; - - uint64_t gparam_val = 0; - if (runtime) - { - uint8_t *scratch = runtime->memory().getScratchpad(); - if (scratch) - { - std::memcpy(&gparam_val, scratch + 0x100, sizeof(gparam_val)); - } - } - if ((gparam_val & 0xFFFF0000FFFFULL) == 1ULL) - product = (product * 0x10000) >> 16; - else - product = (product * 0x20000) >> 16; - - setReturnS32(ctx, product); -} diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_libc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_libc.inl deleted file mode 100644 index 94ff6599..00000000 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_libc.inl +++ /dev/null @@ -1,976 +0,0 @@ -namespace -{ - uint32_t sanitizeMemTransferSize(uint32_t size, const char *op) - { - constexpr uint32_t kMaxTransfer = PS2_RAM_SIZE; - if (size <= kMaxTransfer) - { - return size; - } - - static std::mutex s_warnMutex; - static std::unordered_map s_warnCounts; - uint32_t warnCount = 0u; - { - std::lock_guard lock(s_warnMutex); - warnCount = ++s_warnCounts[op ? op : "memop"]; - } - if (warnCount <= 16u) - { - std::cerr << "[" << (op ? op : "memop") << "] size clamp from 0x" - << std::hex << size << " to 0x" << kMaxTransfer - << std::dec << std::endl; - } - return kMaxTransfer; - } - - uint32_t guestContiguousBytes(uint32_t guestAddr) - { - uint32_t offset = 0u; - bool scratch = false; - if (!ps2ResolveGuestPointer(guestAddr, offset, scratch)) - { - return 0u; - } - if (scratch) - { - return (offset < PS2_SCRATCHPAD_SIZE) ? (PS2_SCRATCHPAD_SIZE - offset) : 0u; - } - return (offset < PS2_RAM_SIZE) ? (PS2_RAM_SIZE - offset) : 0u; - } -} - -void malloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - const uint32_t guestAddr = getRegU32(ctx, 4); // $a0 - if (runtime && guestAddr != 0u) - { - runtime->guestFree(guestAddr); - } -} - -void calloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - 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) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - uint32_t srcAddr = getRegU32(ctx, 5); // $a1 - uint32_t size = getRegU32(ctx, 6); // $a2 - size = sanitizeMemTransferSize(size, "memcpy"); - - uint32_t copied = 0u; - uint32_t curDst = destAddr; - uint32_t curSrc = srcAddr; - while (copied < size) - { - uint8_t *hostDest = getMemPtr(rdram, curDst); - const uint8_t *hostSrc = getConstMemPtr(rdram, curSrc); - if (!hostDest || !hostSrc) - { - break; - } - - uint32_t chunk = size - copied; - chunk = std::min(chunk, guestContiguousBytes(curDst)); - chunk = std::min(chunk, guestContiguousBytes(curSrc)); - if (chunk == 0u) - { - break; - } - - ::memcpy(hostDest, hostSrc, chunk); - copied += chunk; - curDst += chunk; - curSrc += chunk; - } - - if (copied != 0u) - { - ps2TraceGuestRangeWrite(rdram, destAddr, copied, "memcpy", ctx); - } - - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void memset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - int value = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value) - uint32_t size = getRegU32(ctx, 6); // $a2 - size = sanitizeMemTransferSize(size, "memset"); - - uint32_t written = 0u; - uint32_t curDst = destAddr; - while (written < size) - { - uint8_t *hostDest = getMemPtr(rdram, curDst); - if (!hostDest) - { - break; - } - - uint32_t chunk = size - written; - chunk = std::min(chunk, guestContiguousBytes(curDst)); - if (chunk == 0u) - { - break; - } - - ::memset(hostDest, value, chunk); - written += chunk; - curDst += chunk; - } - - if (written != 0u) - { - ps2TraceGuestRangeWrite(rdram, destAddr, written, "memset", ctx); - } - - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void memmove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - uint32_t srcAddr = getRegU32(ctx, 5); // $a1 - uint32_t size = getRegU32(ctx, 6); // $a2 - size = sanitizeMemTransferSize(size, "memmove"); - - uint32_t copied = 0u; - std::vector tmp; - tmp.reserve(size); - for (uint32_t i = 0u; i < size; ++i) - { - const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); - if (!src) - { - break; - } - tmp.push_back(*src); - } - - for (uint32_t i = 0u; i < static_cast(tmp.size()); ++i) - { - uint8_t *dst = getMemPtr(rdram, destAddr + i); - if (!dst) - { - break; - } - *dst = tmp[i]; - ++copied; - } - - if (copied != 0u) - { - ps2TraceGuestRangeWrite(rdram, destAddr, copied, "memmove", ctx); - } - - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void memcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t ptr1Addr = getRegU32(ctx, 4); // $a0 - uint32_t ptr2Addr = getRegU32(ctx, 5); // $a1 - uint32_t size = getRegU32(ctx, 6); // $a2 - size = sanitizeMemTransferSize(size, "memcmp"); - int result = 0; - - for (uint32_t i = 0u; i < size; ++i) - { - const uint8_t *lhs = getConstMemPtr(rdram, ptr1Addr + i); - const uint8_t *rhs = getConstMemPtr(rdram, ptr2Addr + i); - if (!lhs || !rhs) - { - result = (!lhs && !rhs) ? 0 : (lhs ? 1 : -1); - break; - } - if (*lhs != *rhs) - { - result = static_cast(*lhs) - static_cast(*rhs); - break; - } - } - setReturnS32(ctx, result); -} - -void strcpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - uint32_t srcAddr = getRegU32(ctx, 5); // $a1 - - char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); - const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); - - if (hostDest && hostSrc) - { - ::strcpy(hostDest, hostSrc); - ps2TraceGuestRangeWrite(rdram, destAddr, static_cast(::strlen(hostSrc) + 1u), "strcpy", ctx); - } - else - { - std::cerr << "strcpy error: Invalid address provided." - << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" - << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec - << std::endl; - } - - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void strncpy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - uint32_t srcAddr = getRegU32(ctx, 5); // $a1 - uint32_t size = getRegU32(ctx, 6); // $a2 - - char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); - const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); - - if (hostDest && hostSrc) - { - ::strncpy(hostDest, hostSrc, size); - ps2TraceGuestRangeWrite(rdram, destAddr, size, "strncpy", ctx); - } - else - { - std::cerr << "strncpy error: Invalid address provided." - << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" - << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec - << std::endl; - } - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void strlen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t strAddr = getRegU32(ctx, 4); // $a0 - const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); - size_t len = 0; - - if (hostStr) - { - len = ::strlen(hostStr); - } - else - { - std::cerr << "strlen error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; - } - setReturnU32(ctx, (uint32_t)len); -} - -void strcmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t str1Addr = getRegU32(ctx, 4); // $a0 - uint32_t str2Addr = getRegU32(ctx, 5); // $a1 - - const char *hostStr1 = reinterpret_cast(getConstMemPtr(rdram, str1Addr)); - const char *hostStr2 = reinterpret_cast(getConstMemPtr(rdram, str2Addr)); - int result = 0; - - if (hostStr1 && hostStr2) - { - result = ::strcmp(hostStr1, hostStr2); - } - else - { - std::cerr << "strcmp error: Invalid address provided." - << " Str1: 0x" << std::hex << str1Addr << " (host ptr valid: " << (hostStr1 != nullptr) << ")" - << ", Str2: 0x" << str2Addr << " (host ptr valid: " << (hostStr2 != nullptr) << ")" << std::dec - << std::endl; - // Return non-zero on error, consistent with memcmp error handling - result = (hostStr1 == nullptr) - (hostStr2 == nullptr); - if (result == 0 && hostStr1 == nullptr) - result = 1; // Both null -> treat as different? Or 0? Let's say different. - } - setReturnS32(ctx, result); -} - -void strncmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t str1Addr = getRegU32(ctx, 4); // $a0 - uint32_t str2Addr = getRegU32(ctx, 5); // $a1 - uint32_t size = getRegU32(ctx, 6); // $a2 - - const char *hostStr1 = reinterpret_cast(getConstMemPtr(rdram, str1Addr)); - const char *hostStr2 = reinterpret_cast(getConstMemPtr(rdram, str2Addr)); - int result = 0; - - if (hostStr1 && hostStr2) - { - result = ::strncmp(hostStr1, hostStr2, size); - } - else - { - std::cerr << "strncmp error: Invalid address provided." - << " Str1: 0x" << std::hex << str1Addr << " (host ptr valid: " << (hostStr1 != nullptr) << ")" - << ", Str2: 0x" << str2Addr << " (host ptr valid: " << (hostStr2 != nullptr) << ")" << std::dec - << std::endl; - result = (hostStr1 == nullptr) - (hostStr2 == nullptr); - if (result == 0 && hostStr1 == nullptr) - result = 1; // Both null -> different - } - setReturnS32(ctx, result); -} - -void strcat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - uint32_t srcAddr = getRegU32(ctx, 5); // $a1 - - char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); - const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); - - if (hostDest && hostSrc) - { - ::strcat(hostDest, hostSrc); - } - else - { - std::cerr << "strcat error: Invalid address provided." - << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" - << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec - << std::endl; - } - - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void strncat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t destAddr = getRegU32(ctx, 4); // $a0 - uint32_t srcAddr = getRegU32(ctx, 5); // $a1 - uint32_t size = getRegU32(ctx, 6); // $a2 - - char *hostDest = reinterpret_cast(getMemPtr(rdram, destAddr)); - const char *hostSrc = reinterpret_cast(getConstMemPtr(rdram, srcAddr)); - - if (hostDest && hostSrc) - { - ::strncat(hostDest, hostSrc, size); - } - else - { - std::cerr << "strncat error: Invalid address provided." - << " Dest: 0x" << std::hex << destAddr << " (host ptr valid: " << (hostDest != nullptr) << ")" - << ", Src: 0x" << srcAddr << " (host ptr valid: " << (hostSrc != nullptr) << ")" << std::dec - << std::endl; - } - - // returns dest pointer ($v0 = $a0) - ctx->r[2] = ctx->r[4]; -} - -void strchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t strAddr = getRegU32(ctx, 4); // $a0 - int char_code = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value) - - const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); - char *foundPtr = nullptr; - uint32_t resultAddr = 0; - - if (hostStr) - { - foundPtr = ::strchr(const_cast(hostStr), char_code); - if (foundPtr) - { - resultAddr = hostPtrToPs2Addr(rdram, foundPtr); - } - } - else - { - std::cerr << "strchr error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; - } - - // returns PS2 address or 0 (NULL) - setReturnU32(ctx, resultAddr); -} - -void strrchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t strAddr = getRegU32(ctx, 4); // $a0 - int char_code = (int)(getRegU32(ctx, 5) & 0xFF); // $a1 (char value) - - const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); - char *foundPtr = nullptr; - uint32_t resultAddr = 0; - - if (hostStr) - { - foundPtr = ::strrchr(const_cast(hostStr), char_code); // Use const_cast carefully - if (foundPtr) - { - resultAddr = hostPtrToPs2Addr(rdram, foundPtr); - } - } - else - { - std::cerr << "strrchr error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; - } - - // returns PS2 address or 0 (NULL) - setReturnU32(ctx, resultAddr); -} - -void strstr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t haystackAddr = getRegU32(ctx, 4); // $a0 - uint32_t needleAddr = getRegU32(ctx, 5); // $a1 - - const char *hostHaystack = reinterpret_cast(getConstMemPtr(rdram, haystackAddr)); - const char *hostNeedle = reinterpret_cast(getConstMemPtr(rdram, needleAddr)); - char *foundPtr = nullptr; - uint32_t resultAddr = 0; - - if (hostHaystack && hostNeedle) - { - foundPtr = ::strstr(const_cast(hostHaystack), hostNeedle); - if (foundPtr) - { - resultAddr = hostPtrToPs2Addr(rdram, foundPtr); - } - } - else - { - std::cerr << "strstr error: Invalid address provided." - << " Haystack: 0x" << std::hex << haystackAddr << " (host ptr valid: " << (hostHaystack != nullptr) << ")" - << ", Needle: 0x" << needleAddr << " (host ptr valid: " << (hostNeedle != nullptr) << ")" << std::dec - << std::endl; - } - - // returns PS2 address or 0 (NULL) - setReturnU32(ctx, resultAddr); -} - -void printf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t format_addr = getRegU32(ctx, 4); // $a0 - 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(), 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 - { - std::cerr << "printf error: Invalid format string address provided: 0x" << std::hex << format_addr << std::dec << std::endl; - } - - // returns the number of characters written, or negative on error. - setReturnS32(ctx, ret); -} - -void sprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t str_addr = getRegU32(ctx, 4); // $a0 - uint32_t format_addr = getRegU32(ctx, 5); // $a1 - constexpr size_t kSafeSprintfBytes = 256u; // Keep guest stack temporaries from being overwritten. - - const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); - int ret = -1; - - if (format_addr != 0) - { - 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() >= kSafeSprintfBytes) - { - rendered.resize(kSafeSprintfBytes - 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 format address provided." - << " Dest: 0x" << std::hex << str_addr - << ", Format: 0x" << format_addr << std::dec - << std::endl; - } - - // returns the number of characters written (excluding null), or negative on error. - setReturnS32(ctx, ret); -} - -void snprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t str_addr = getRegU32(ctx, 4); // $a0 - size_t size = getRegU32(ctx, 5); // $a1 - uint32_t format_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 = formatPs2StringWithArgs(rdram, ctx, runtime, formatOwned.c_str(), 3); - ret = static_cast(rendered.size()); - - 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 - << ", Format: 0x" << format_addr << std::dec - << ", Size: " << size << std::endl; - } - - // returns the number of characters that *would* have been written - // if size was large enough (excluding null), or negative on error. - setReturnS32(ctx, ret); -} - -void puts(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t strAddr = getRegU32(ctx, 4); // $a0 - const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); - int result = EOF; - - if (hostStr) - { - result = std::puts(hostStr); // std::puts adds a newline - std::fflush(stdout); // Ensure output appears - } - else - { - std::cerr << "puts error: Invalid address provided: 0x" << std::hex << strAddr << std::dec << std::endl; - } - - // returns non-negative on success, EOF on error. - setReturnS32(ctx, result >= 0 ? 0 : -1); // PS2 might expect 0/-1 rather than EOF -} - -void fopen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - uint32_t modeAddr = getRegU32(ctx, 5); // $a1 - - const char *hostPath = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - const char *hostMode = reinterpret_cast(getConstMemPtr(rdram, modeAddr)); - uint32_t file_handle = 0; - - if (hostPath && hostMode) - { - // TODO: Add translation for PS2 paths like mc0:, host:, cdrom:, etc. - // treating as direct host path - std::cout << "ps2_stub fopen: path='" << hostPath << "', mode='" << hostMode << "'" << std::endl; - FILE *fp = ::fopen(hostPath, hostMode); - if (fp) - { - std::lock_guard lock(g_file_mutex); - file_handle = generate_file_handle(); - g_file_map[file_handle] = fp; - std::cout << " -> handle=0x" << std::hex << file_handle << std::dec << std::endl; - } - else - { - std::cerr << "ps2_stub fopen error: Failed to open '" << hostPath << "' with mode '" << hostMode << "'. Error: " << strerror(errno) << std::endl; - } - } - else - { - std::cerr << "fopen error: Invalid address provided for path or mode." - << " Path: 0x" << std::hex << pathAddr << " (host ptr valid: " << (hostPath != nullptr) << ")" - << ", Mode: 0x" << modeAddr << " (host ptr valid: " << (hostMode != nullptr) << ")" << std::dec - << std::endl; - } - // returns a file handle (non-zero) on success, or NULL (0) on error. - setReturnU32(ctx, file_handle); -} - -void fclose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t file_handle = getRegU32(ctx, 4); // $a0 - int ret = EOF; // Default to error - - if (file_handle != 0) - { - std::lock_guard lock(g_file_mutex); - auto it = g_file_map.find(file_handle); - if (it != g_file_map.end()) - { - FILE *fp = it->second; - ret = ::fclose(fp); - g_file_map.erase(it); - } - else - { - std::cerr << "ps2_stub fclose error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; - } - } - else - { - // Closing NULL handle in Standard C defines this as no-op - ret = 0; - } - - // returns 0 on success, EOF on error. - setReturnS32(ctx, ret); -} - -void fread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t ptrAddr = getRegU32(ctx, 4); // $a0 (buffer) - uint32_t size = getRegU32(ctx, 5); // $a1 (element size) - uint32_t count = getRegU32(ctx, 6); // $a2 (number of elements) - uint32_t file_handle = getRegU32(ctx, 7); // $a3 (file handle) - size_t items_read = 0; - - uint8_t *hostPtr = getMemPtr(rdram, ptrAddr); - FILE *fp = get_file_ptr(file_handle); - - if (hostPtr && fp && size > 0 && count > 0) - { - items_read = ::fread(hostPtr, size, count, fp); - } - else - { - std::cerr << "fread error: Invalid arguments." - << " Ptr: 0x" << std::hex << ptrAddr << " (host ptr valid: " << (hostPtr != nullptr) << ")" - << ", Handle: 0x" << file_handle << " (file valid: " << (fp != nullptr) << ")" << std::dec - << ", Size: " << size << ", Count: " << count << std::endl; - } - // returns the number of items successfully read. - setReturnU32(ctx, (uint32_t)items_read); -} - -void fwrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t ptrAddr = getRegU32(ctx, 4); // $a0 (buffer) - uint32_t size = getRegU32(ctx, 5); // $a1 (element size) - uint32_t count = getRegU32(ctx, 6); // $a2 (number of elements) - uint32_t file_handle = getRegU32(ctx, 7); // $a3 (file handle) - size_t items_written = 0; - - const uint8_t *hostPtr = getConstMemPtr(rdram, ptrAddr); - FILE *fp = get_file_ptr(file_handle); - - if (hostPtr && fp && size > 0 && count > 0) - { - items_written = ::fwrite(hostPtr, size, count, fp); - } - else - { - std::cerr << "fwrite error: Invalid arguments." - << " Ptr: 0x" << std::hex << ptrAddr << " (host ptr valid: " << (hostPtr != nullptr) << ")" - << ", Handle: 0x" << file_handle << " (file valid: " << (fp != nullptr) << ")" << std::dec - << ", Size: " << size << ", Count: " << count << std::endl; - } - // returns the number of items successfully written. - setReturnU32(ctx, (uint32_t)items_written); -} - -void fprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t file_handle = getRegU32(ctx, 4); // $a0 - uint32_t format_addr = getRegU32(ctx, 5); // $a1 - 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 = 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 << std::dec - << std::endl; - } - - // returns the number of characters written, or negative on error. - setReturnS32(ctx, ret); -} - -void fseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t file_handle = getRegU32(ctx, 4); // $a0 - long offset = (long)getRegU32(ctx, 5); // $a1 (Note: might need 64-bit for large files?) - int whence = (int)getRegU32(ctx, 6); // $a2 (SEEK_SET, SEEK_CUR, SEEK_END) - int ret = -1; // Default error - - FILE *fp = get_file_ptr(file_handle); - - if (fp) - { - // Ensure whence is valid (0, 1, 2) - if (whence >= 0 && whence <= 2) - { - ret = ::fseek(fp, offset, whence); - } - else - { - std::cerr << "fseek error: Invalid whence value: " << whence << std::endl; - } - } - else - { - std::cerr << "fseek error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; - } - - // returns 0 on success, non-zero on error. - setReturnS32(ctx, ret); -} - -void ftell(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t file_handle = getRegU32(ctx, 4); // $a0 - long ret = -1L; - - FILE *fp = get_file_ptr(file_handle); - - if (fp) - { - ret = ::ftell(fp); - } - else - { - std::cerr << "ftell error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; - } - - // returns the current position, or -1L on error. - if (ret > 0xFFFFFFFFL || ret < 0) - { - setReturnS32(ctx, -1); - } - else - { - setReturnU32(ctx, (uint32_t)ret); - } -} - -void fflush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t file_handle = getRegU32(ctx, 4); // $a0 - int ret = EOF; // Default error - - // If handle is 0 fflush flushes *all* output streams. - if (file_handle == 0) - { - ret = ::fflush(NULL); - } - else - { - FILE *fp = get_file_ptr(file_handle); - if (fp) - { - ret = ::fflush(fp); - } - else - { - std::cerr << "fflush error: Invalid file handle 0x" << std::hex << file_handle << std::dec << std::endl; - } - } - // returns 0 on success, EOF on error. - setReturnS32(ctx, ret); -} - -void sqrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::sqrtf(arg); -} - -void sin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - 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]; - ctx->f[0] = ::tanf(arg); -} - -void atan2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float y = ctx->f[12]; - float x = ctx->f[14]; - ctx->f[0] = ::atan2f(y, x); -} - -void pow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float base = ctx->f[12]; - float exp = ctx->f[14]; - ctx->f[0] = ::powf(base, exp); -} - -void exp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::expf(arg); -} - -void log(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::logf(arg); -} - -void log10(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::log10f(arg); -} - -void ceil(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::ceilf(arg); -} - -void floor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::floorf(arg); -} - -void fabs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float arg = ctx->f[12]; - ctx->f[0] = ::fabsf(arg); -} diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl deleted file mode 100644 index cfaffa07..00000000 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl +++ /dev/null @@ -1,4430 +0,0 @@ -void calloc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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 ret0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, 0u); - ctx->pc = getRegU32(ctx, 31); -} - -void ret1(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, 1u); - ctx->pc = getRegU32(ctx, 31); -} - -void reta0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, getRegU32(ctx, 4)); - ctx->pc = getRegU32(ctx, 31); -} - -void free_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - 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) -{ - setReturnS32(ctx, 0); -} - -void mbtowc_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t wcAddr = getRegU32(ctx, 5); // $a1 - const uint32_t strAddr = getRegU32(ctx, 6); // $a2 - const int32_t n = static_cast(getRegU32(ctx, 7)); // $a3 - if (n <= 0 || strAddr == 0u) - { - setReturnS32(ctx, 0); - return; - } - - const uint8_t *src = getConstMemPtr(rdram, strAddr); - if (!src) - { - setReturnS32(ctx, -1); - return; - } - - const uint8_t ch = *src; - if (wcAddr != 0u) - { - if (uint8_t *dst = getMemPtr(rdram, wcAddr)) - { - const uint32_t out = static_cast(ch); - std::memcpy(dst, &out, sizeof(out)); - } - } - setReturnS32(ctx, (ch == 0u) ? 0 : 1); -} - -void printf_r(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - TODO_NAMED("sceCdRI", rdram, ctx, runtime); -} - -void sceCdRM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceCdRM", rdram, ctx, runtime); -} - -void sceFsDbChk(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceFsDbChk", rdram, ctx, runtime); -} - -void sceFsIntrSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceFsIntrSigSema", rdram, ctx, runtime); -} - -void sceFsSemExit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceFsSemExit", rdram, ctx, runtime); -} - -void sceFsSemInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceFsSemInit", rdram, ctx, runtime); -} - -void sceFsSigSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceFsSigSema", rdram, ctx, runtime); -} - -void sceIDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegFlush", rdram, ctx, runtime); -} - -void sceRpcFreePacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceRpcFreePacket", rdram, ctx, runtime); -} - -void sceRpcGetFPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceRpcGetFPacket", rdram, ctx, runtime); -} - -void sceRpcGetFPacket2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceRpcGetFPacket2", rdram, ctx, runtime); -} - -void sceSDC(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSifCmdIntrHdlr", rdram, ctx, runtime); -} - -void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifLoadModule(rdram, ctx, runtime); -} - -void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t srcAddr = getRegU32(ctx, 7); // $a3 - const uint32_t dstAddr = readStackU32(rdram, ctx, 16); - const uint32_t size = readStackU32(rdram, ctx, 20); - if (size != 0u && srcAddr != 0u && dstAddr != 0u) - { - for (uint32_t i = 0; i < size; ++i) - { - const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); - uint8_t *dst = getMemPtr(rdram, dstAddr + i); - if (!src || !dst) - { - break; - } - *dst = *src; - } - } - - setReturnS32(ctx, 1); -} - -void sceVu0ecossin(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ecossin", rdram, ctx, runtime); -} - -void abs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int32_t value = static_cast(getRegU32(ctx, 4)); - if (value == std::numeric_limits::min()) - { - setReturnS32(ctx, std::numeric_limits::max()); - return; - } - setReturnS32(ctx, value < 0 ? -value : value); -} - -void atan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - float in = ctx ? ctx->f[12] : 0.0f; - if (in == 0.0f) - { - uint32_t raw = getRegU32(ctx, 4); - std::memcpy(&in, &raw, sizeof(in)); - } - const float out = std::atan(in); - if (ctx) - { - ctx->f[0] = out; - } - - uint32_t outRaw = 0u; - std::memcpy(&outRaw, &out, sizeof(outRaw)); - setReturnU32(ctx, outRaw); -} - -void close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioClose(rdram, ctx, runtime); -} - -void DmaAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, getRegU32(ctx, 4)); -} - -void exit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - if (runtime) - { - runtime->requestStop(); - } - setReturnS32(ctx, 0); -} - -void fstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - setReturnS32(ctx, 1); -} - -void iopGetArea(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, kIopHeapBase); -} - -void lseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioLseek(rdram, ctx, runtime); -} - -void memchr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t srcAddr = getRegU32(ctx, 4); - const uint8_t needle = static_cast(getRegU32(ctx, 5) & 0xFFu); - const uint32_t size = getRegU32(ctx, 6); - - for (uint32_t i = 0; i < size; ++i) - { - const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); - if (!src) - { - break; - } - if (*src == needle) - { - setReturnU32(ctx, srcAddr + i); - return; - } - } - - setReturnU32(ctx, 0u); -} - -void open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioOpen(rdram, ctx, runtime); -} - -void Pad_init(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void Pad_set(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void rand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, std::rand() & 0x7FFF); -} - -namespace -{ - std::mutex g_mcStateMutex; - int32_t g_mcNextFd = 1; - int32_t g_mcLastResult = 0; -} - -void read(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioRead(rdram, ctx, runtime); -} - -void sceCdApplyNCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdBreak(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceCdChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdDelayThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceCdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 2); -} - -void sceCdGetDiskType(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - // SCECdPS2DVD - setReturnS32(ctx, 0x14); -} - -void sceCdGetReadPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, g_cdStreamingLbn); -} - -void sceCdGetToc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - g_cdInitialized = true; - g_lastCdError = 0; - setReturnS32(ctx, 1); -} - -void sceCdInitEeCB(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdIntToPos(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - g_cdMode = getRegU32(ctx, 4); - setReturnS32(ctx, 1); -} - -void sceCdNcmdDiskReady(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 2); -} - -void sceCdPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdPosToInt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - 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) -{ - 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) -{ - sceCdRead(rdram, ctx, runtime); -} - -void sceCdSearchFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - g_cdStreamingLbn = getRegU32(ctx, 4); - setReturnS32(ctx, 1); -} - -void sceCdStandby(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, g_cdInitialized ? 6 : 0); -} - -void sceCdStInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdStPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdStRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - setReturnS32(ctx, 1); -} - -void sceCdStResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdStSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_cdStreamingLbn = getRegU32(ctx, 4); - setReturnS32(ctx, 1); -} - -void sceCdStSeekF(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_cdStreamingLbn = getRegU32(ctx, 4); - setReturnS32(ctx, 1); -} - -void sceCdStStart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_cdStreamingLbn = getRegU32(ctx, 4); - setReturnS32(ctx, 1); -} - -void sceCdStStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceCdStStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceCdSyncS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceCdTrayReq(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - ps2_syscalls::fioClose(rdram, ctx, runtime); -} - -void sceDeci2Close(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2Close", rdram, ctx, runtime); -} - -void sceDeci2ExLock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2ExLock", rdram, ctx, runtime); -} - -void sceDeci2ExRecv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2ExRecv", rdram, ctx, runtime); -} - -void sceDeci2ExReqSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2ExReqSend", rdram, ctx, runtime); -} - -void sceDeci2ExSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2ExSend", rdram, ctx, runtime); -} - -void sceDeci2ExUnLock(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2ExUnLock", rdram, ctx, runtime); -} - -void sceDeci2Open(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2Open", rdram, ctx, runtime); -} - -void sceDeci2Poll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2Poll", rdram, ctx, runtime); -} - -void sceDeci2ReqSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDeci2ReqSend", rdram, ctx, runtime); -} - -void sceDmaCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaCallback", rdram, ctx, runtime); -} - -void sceDmaDebug(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaDebug", rdram, ctx, runtime); -} - -void sceDmaGetChan(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - TODO_NAMED("sceDmaGetEnv", rdram, ctx, runtime); -} - -void sceDmaLastSyncTime(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaLastSyncTime", rdram, ctx, runtime); -} - -void sceDmaPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaPause", rdram, ctx, runtime); -} - -void sceDmaPutEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaPutEnv", rdram, ctx, runtime); -} - -void sceDmaPutStallAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaPutStallAddr", rdram, ctx, runtime); -} - -void sceDmaRecv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaRecv", rdram, ctx, runtime); -} - -void sceDmaRecvI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaRecvI", rdram, ctx, runtime); -} - -void sceDmaRecvN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaRecvN", rdram, ctx, runtime); -} - -void sceDmaReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceDmaRestart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaRestart", rdram, ctx, runtime); -} - -void sceDmaSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); -} - -void sceDmaSendI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); -} - -void sceDmaSendM(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, false)); -} - -void sceDmaSendN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, submitDmaSend(rdram, ctx, runtime, true)); -} - -void sceDmaSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, submitDmaSync(rdram, ctx, runtime)); -} - -void sceDmaSyncN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, submitDmaSync(rdram, ctx, runtime)); -} - -void sceDmaWatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceDmaWatch", rdram, ctx, runtime); -} - -void sceFsInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceFsReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -static void writeU32AtGp(uint8_t *rdram, uint32_t gp, int32_t offset, uint32_t value) -{ - const uint32_t addr = gp + static_cast(offset); - if (uint8_t *p = getMemPtr(rdram, addr)) - *reinterpret_cast(p) = value; -} - -void sceeFontInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t gp = getRegU32(ctx, 28); - const uint32_t a0 = getRegU32(ctx, 4); - const uint32_t a1 = getRegU32(ctx, 5); - const uint32_t a2 = getRegU32(ctx, 6); - const uint32_t a3 = getRegU32(ctx, 7); - writeU32AtGp(rdram, gp, -0x7b60, a1); - writeU32AtGp(rdram, gp, -0x7b5c, a2); - writeU32AtGp(rdram, gp, -0x7b64, a0); - writeU32AtGp(rdram, gp, -0x7c98, a3); - writeU32AtGp(rdram, gp, -0x7b4c, 0x7f7f7f7f); - writeU32AtGp(rdram, gp, -0x7b50, 0x3f800000); - writeU32AtGp(rdram, gp, -0x7b54, 0x3f800000); - writeU32AtGp(rdram, gp, -0x7b58, 0); - - if (runtime && a0 != 0u) - { - if ((a0 * 256u) + 64u <= PS2_GS_VRAM_SIZE) - { - uint32_t clutData[16]; - for (uint32_t i = 0; i < 16u; ++i) - { - uint8_t alpha = static_cast((i * 0x80u) / 15u); - clutData[i] = (i == 0) - ? 0x00000000u - : (0x80u | (0x80u << 8) | (0x80u << 16) | (static_cast(alpha) << 24)); - } - constexpr uint32_t kClutQwc = 4u; - constexpr uint32_t kHeaderQwc = 6u; - constexpr uint32_t kTotalQwc = kHeaderQwc + kClutQwc; - uint32_t pktAddr = runtime->guestMalloc(kTotalQwc * 16u, 16u); - if (pktAddr != 0u) - { - uint8_t *pkt = getMemPtr(rdram, pktAddr); - if (pkt) - { - uint64_t *q = reinterpret_cast(pkt); - const uint32_t dbp = a0 & 0x3FFFu; - constexpr uint8_t psm = 0u; - q[0] = (4ULL << 60) | (1ULL << 56) | 1ULL; - q[1] = 0x0E0E0E0E0E0E0E0EULL; - q[2] = (static_cast(dbp) << 32) | (1ULL << 48) | (static_cast(psm) << 56); - q[3] = 0x50ULL; - q[4] = 0ULL; - q[5] = 0x51ULL; - q[6] = 16ULL | (1ULL << 32); - q[7] = 0x52ULL; - q[8] = 0ULL; - q[9] = 0x53ULL; - q[10] = (2ULL << 58) | (kClutQwc & 0x7FFF) | (1ULL << 15); - q[11] = 0ULL; - std::memcpy(pkt + 12u * 8u, clutData, 64u); - constexpr uint32_t GIF_CHANNEL = 0x1000A000; - constexpr uint32_t CHCR_STR_MODE0 = 0x101u; - runtime->memory().writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); - runtime->memory().writeIORegister(GIF_CHANNEL + 0x20u, kTotalQwc & 0xFFFFu); - runtime->memory().writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); - runtime->memory().processPendingTransfers(); - } - } - } - } - - setReturnS32(ctx, static_cast(a0 + 4)); -} - -void sceeFontLoadFont(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static constexpr uint32_t kFontBase = 0x176148u; - static constexpr uint32_t kFontEntrySz = 0x24u; - - const uint32_t fontDataAddr = getRegU32(ctx, 4); - const int fontId = static_cast(getRegU32(ctx, 5)); - const int tbp0 = static_cast(getRegU32(ctx, 7)); - - if (!fontDataAddr || !runtime) - { - setReturnS32(ctx, tbp0); - return; - } - - const uint8_t *fontPtr = getConstMemPtr(rdram, fontDataAddr); - if (!fontPtr) - { - setReturnS32(ctx, tbp0); - return; - } - - int width = static_cast(*reinterpret_cast(fontPtr + 0x00u)); - int height = static_cast(*reinterpret_cast(fontPtr + 0x04u)); - uint32_t raw8 = *reinterpret_cast(fontPtr + 0x08u); - int fontDataSz = static_cast(*reinterpret_cast(fontPtr + 0x0cu)); - - uint32_t pointsize = raw8; - uint32_t fontOff = static_cast(fontId * static_cast(kFontEntrySz)); - if (raw8 & 0x40000000u) - { - pointsize = raw8 - 0x40000000u; - if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff + 0x20u)) - *reinterpret_cast(p) = 1u; - } - else - { - if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff + 0x20u)) - *reinterpret_cast(p) = 0u; - } - - int tw = (width >= 0) ? (width >> 6) : ((width + 0x3f) >> 6); - int qwc = (fontDataSz >= 0) ? (fontDataSz >> 4) : ((fontDataSz + 0xf) >> 4); - - uint32_t glyphSrc = fontDataAddr + static_cast(fontDataSz) + 0x10u; - uint32_t glyphAlloc = runtime->guestMalloc(0x2010u, 0x40u); - if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff)) - *reinterpret_cast(p) = glyphAlloc; - - if (glyphAlloc != 0u) - { - uint8_t *dst = getMemPtr(rdram, glyphAlloc); - const uint8_t *src = getConstMemPtr(rdram, glyphSrc); - if (dst && src) - std::memcpy(dst, src, 0x2010u); - } - - uint32_t isDoubleByte = 0; - if (const uint8_t *p = getConstMemPtr(rdram, kFontBase + fontOff + 0x20u)) - isDoubleByte = *reinterpret_cast(p); - if (isDoubleByte == 0u) - { - uint32_t kernSrc = glyphSrc + 0x2010u; - uint32_t kernAlloc = runtime->guestMalloc(0xc400u, 0x40u); - if (glyphAlloc != 0u) - *reinterpret_cast(getMemPtr(rdram, glyphAlloc + 0x2000u)) = kernAlloc; - if (kernAlloc != 0u) - { - uint8_t *dst = getMemPtr(rdram, kernAlloc); - const uint8_t *src = getConstMemPtr(rdram, kernSrc); - if (dst && src) - std::memcpy(dst, src, 0xc400u); - } - } - - auto writeFontField = [&](uint32_t off, uint32_t val) - { - if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff + off)) - *reinterpret_cast(p) = val; - }; - writeFontField(0x18u, pointsize); - writeFontField(0x08u, static_cast(tbp0)); - writeFontField(0x0cu, static_cast(tw)); - - int logW = 0; - for (int w = width; w != 1 && w != 0; w = static_cast(static_cast(w) >> 1)) - logW++; - writeFontField(0x10u, static_cast(logW)); - - int logH = 0; - for (int h = height; h != 1 && h != 0; h = static_cast(static_cast(h) >> 1)) - logH++; - writeFontField(0x14u, static_cast(logH)); - writeFontField(0x04u, 0u); - writeFontField(0x1cu, getRegU32(ctx, 6)); - - if (qwc > 0) - { - const uint32_t imageBytes = static_cast(qwc) * 16u; - const uint8_t psm = 20u; - const uint32_t headerQwc = 12u; - const uint32_t imageQwc = static_cast(qwc); - const uint32_t totalQwc = headerQwc + imageQwc; - uint32_t pktAddr = runtime->guestMalloc(totalQwc * 16u, 16u); - if (pktAddr != 0u) - { - uint8_t *pkt = getMemPtr(rdram, pktAddr); - const uint8_t *imgSrc = getConstMemPtr(rdram, fontDataAddr + 0x10u); - if (pkt && imgSrc) - { - uint64_t *q = reinterpret_cast(pkt); - const uint32_t dbp = static_cast(tbp0) & 0x3FFFu; - const uint32_t dbw = static_cast(tw > 0 ? tw : 1) & 0x3Fu; - const uint32_t rrw = static_cast(width > 0 ? width : 64); - const uint32_t rrh = static_cast(height > 0 ? height : 1); - - q[0] = (4ULL << 60) | (1ULL << 56) | 1ULL; - q[1] = 0x0E0E0E0E0E0E0E0EULL; - q[2] = (static_cast(psm) << 24) | (1ULL << 16) | - (static_cast(dbp) << 32) | (static_cast(dbw) << 48) | - (static_cast(psm) << 56); - q[3] = 0x50ULL; - q[4] = 0ULL; - q[5] = 0x51ULL; - q[6] = (static_cast(rrh) << 32) | static_cast(rrw); - q[7] = 0x52ULL; - q[8] = 0ULL; - q[9] = 0x53ULL; - q[10] = (2ULL << 58) | (imageQwc & 0x7FFF) | (1ULL << 15); - q[11] = 0ULL; - std::memcpy(pkt + 12 * 8, imgSrc, imageBytes); - - constexpr uint32_t GIF_CHANNEL = 0x1000A000; - constexpr uint32_t CHCR_STR_MODE0 = 0x101u; - runtime->memory().writeIORegister(GIF_CHANNEL + 0x10u, pktAddr); - runtime->memory().writeIORegister(GIF_CHANNEL + 0x20u, totalQwc & 0xFFFFu); - runtime->memory().writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0); - } - } - } - - int retTbp = tbp0 + ((fontDataSz >= 0 ? fontDataSz : fontDataSz + 0x7f) >> 7); - setReturnS32(ctx, retTbp); -} - -static constexpr uint32_t kFontBase = 0x176148u; -static constexpr uint32_t kFontEntrySz = 0x24u; - -void sceeFontGenerateString(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const float sclx = ctx->f[12]; - const float scly = ctx->f[13]; - const uint32_t bufAddr = getRegU32(ctx, 4); - const uint64_t paramX = GPR_U64(ctx, 5); - const int64_t paramY = GPR_S64(ctx, 6); - const int paramW = static_cast(getRegU32(ctx, 7)); - const int paramH = static_cast(getRegU32(ctx, 8)); - const uint32_t colour = getRegU32(ctx, 9); - const int alignCh = static_cast(getRegU32(ctx, 10) & 0xffu); - const int fontId = static_cast(getRegU32(ctx, 11)); - - const uint32_t sp = getRegU32(ctx, 29); - const uint32_t strAddr = FAST_READ32(sp + 0x00u); - const uint32_t param14 = FAST_READ32(sp + 0x18u); - - if (bufAddr == 0u) - { - setReturnS32(ctx, 0); - ctx->pc = getRegU32(ctx, 31); - return; - } - - const uint32_t gp = getRegU32(ctx, 28); - const uint32_t fontModeAdj = FAST_READ32(gp + static_cast(static_cast(-0x7c98))); - const uint32_t shiftAmt = fontModeAdj & 0x1fu; - const int scrHeight = static_cast(FAST_READ32(gp + static_cast(static_cast(-0x7b5c)))); - const int scrWidth = static_cast(FAST_READ32(gp + static_cast(static_cast(-0x7b60)))); - const uint32_t fontClut = FAST_READ32(gp + static_cast(static_cast(-0x7b64))); - - const uint32_t fontOff = static_cast(fontId * static_cast(kFontEntrySz)); - const int lineH = static_cast(FAST_READ32(kFontBase + fontOff + 0x18u)); - - int iVar21 = 0; - int iStack_dc = 0; - uint32_t uStack_d8 = 0; - int iVar15 = 0; - - int16_t sVar8; - { - int yStepRaw = static_cast(static_cast((lineH + 6) * 16) * scly); - sVar8 = static_cast((static_cast(paramY) + 0x700) * 16) + static_cast(yStepRaw >> static_cast(shiftAmt)); - } - - int16_t baseX = static_cast((static_cast(paramX) + 0x6c0) * 16); - - if (param14 != 0u) - { - int64_t clipY1 = static_cast(static_cast(paramY) + paramH); - int64_t clipX1 = static_cast(static_cast(paramX) + paramW); - if (clipY1 > scrHeight - 1) clipY1 = static_cast(scrHeight - 1); - if (clipX1 > scrWidth - 1) clipX1 = static_cast(scrWidth - 1); - int64_t clipY0 = 0; - if (paramY > 0) clipY0 = paramY; - uint64_t clipX0 = 0; - if (static_cast(paramX) > 0) clipX0 = paramX; - - uint64_t scissor = clipX0 | (static_cast(static_cast(clipX1)) << 16) - | (static_cast(static_cast(clipY0)) << 32) | (static_cast(static_cast(clipY1)) << 48); - - FAST_WRITE64(bufAddr + 0x00, 0x1000000000000005ull); - FAST_WRITE64(bufAddr + 0x08, 0x0eull); - FAST_WRITE64(bufAddr + 0x10, scissor); - FAST_WRITE64(bufAddr + 0x18, 0x40ull); - FAST_WRITE64(bufAddr + 0x20, 0x20000ull); - FAST_WRITE64(bufAddr + 0x28, 0x47ull); - FAST_WRITE64(bufAddr + 0x30, 0x44ull); - FAST_WRITE64(bufAddr + 0x38, 0x42ull); - FAST_WRITE64(bufAddr + 0x40, 0x160ull); - FAST_WRITE64(bufAddr + 0x48, 0x14ull); - FAST_WRITE64(bufAddr + 0x50, 0x156ull); - FAST_WRITE64(bufAddr + 0x58, 0ull); - FAST_WRITE64(bufAddr + 0x60, 0x1000000000000001ull); - FAST_WRITE64(bufAddr + 0x68, 0x0eull); - - uint64_t iVar5 = static_cast(FAST_READ32(kFontBase + fontOff + 0x08u)); - uint64_t iVar22 = static_cast(FAST_READ32(kFontBase + fontOff + 0x0cu)); - uint64_t iVar3 = static_cast(FAST_READ32(kFontBase + fontOff + 0x10u)); - uint64_t iVar4 = static_cast(FAST_READ32(kFontBase + fontOff + 0x14u)); - - uint64_t tex0 = iVar5 - | 0x2000000000000000ull - | (iVar22 << 14) - | 0x400000000ull - | (iVar3 << 26) - | 0x1400000ull - | (iVar4 << 30) - | (static_cast(fontClut) << 37); - - FAST_WRITE64(bufAddr + 0x70, tex0); - FAST_WRITE64(bufAddr + 0x78, 6ull); - FAST_WRITE64(bufAddr + 0x80, 0x1000000000000001ull); - FAST_WRITE64(bufAddr + 0x88, 0x0eull); - FAST_WRITE64(bufAddr + 0x90, static_cast(colour)); - FAST_WRITE64(bufAddr + 0x98, 1ull); - - iVar21 = 10; - } - - int iVar22_qw = iVar21 + 1; - uint32_t s2 = bufAddr + static_cast(iVar22_qw * 16); - uint32_t uVar20 = 0; - - size_t sLen = 0; - { - const char *hostStr = reinterpret_cast(getConstMemPtr(rdram, strAddr)); - if (hostStr) sLen = ::strlen(hostStr); - } - - while (uVar20 < sLen) - { - uint8_t bVar1 = FAST_READ8(strAddr + uVar20); - uint32_t uVar9 = static_cast(bVar1); - int8_t chSigned = static_cast(bVar1); - - if (uStack_d8 < 0x21u) - { - goto label_check_printable; - } - - if (uVar9 > 0x20u) - { - uint32_t dat176168 = FAST_READ32(kFontBase + fontOff + 0x20u); - if (dat176168 == 0u) - { - uint32_t fontPtr0 = FAST_READ32(kFontBase + fontOff); - uint32_t tableAddr = FAST_READ32(fontPtr0 + 0x2000u); - int8_t kern = static_cast(FAST_READ8(tableAddr - 0x1c20u + uStack_d8 * 0xe0u + uVar9)); - iVar15 += static_cast(static_cast(static_cast(kern)) * sclx); - } - goto label_check_printable; - } - - goto label_space; - -label_check_printable: - if (uVar9 < 0x21u) - { - goto label_space; - } - - { - int glyphIdx = static_cast(chSigned); - uint32_t iVar19_off = static_cast(glyphIdx * 0x20); - - if (param14 != 0u) - { - uint32_t fontPtr = FAST_READ32(kFontBase + fontOff); - int16_t sVar7 = baseX + static_cast(iVar15); - - iVar22_qw += 2; - iStack_dc += 1; - - uint16_t wU0 = FAST_READ16(fontPtr + iVar19_off + 0); - uint16_t wV0 = FAST_READ16(fontPtr + iVar19_off + 2); - FAST_WRITE16(s2 + 0x00, wU0); - FAST_WRITE16(s2 + 0x02, wV0); - - int16_t dx0 = static_cast(FAST_READ16(fontPtr + iVar19_off + 8)); - int16_t dy0 = static_cast(FAST_READ16(fontPtr + iVar19_off + 10)); - uint16_t wX0 = static_cast(sVar7 + static_cast(static_cast(static_cast(static_cast(dx0)) * sclx))); - int yVal0 = static_cast(static_cast(static_cast(dy0)) * scly) >> static_cast(shiftAmt); - uint16_t wY0 = static_cast(sVar8 + static_cast(yVal0)); - FAST_WRITE16(s2 + 0x08, wX0); - FAST_WRITE16(s2 + 0x0a, wY0); - FAST_WRITE32(s2 + 0x0c, 1u); - - s2 += 0x10u; - - uint16_t wU1 = FAST_READ16(fontPtr + iVar19_off + 4); - uint16_t wV1 = FAST_READ16(fontPtr + iVar19_off + 6); - FAST_WRITE16(s2 + 0x00, wU1); - FAST_WRITE16(s2 + 0x02, wV1); - - int16_t dx1 = static_cast(FAST_READ16(fontPtr + iVar19_off + 12)); - int16_t dy1 = static_cast(FAST_READ16(fontPtr + iVar19_off + 14)); - uint16_t wX1 = static_cast(sVar7 + static_cast(static_cast(static_cast(static_cast(dx1)) * sclx))); - int yVal1 = static_cast(static_cast(static_cast(dy1)) * scly) >> static_cast(shiftAmt); - uint16_t wY1 = static_cast(sVar8 + static_cast(yVal1)); - FAST_WRITE16(s2 + 0x08, wX1); - FAST_WRITE16(s2 + 0x0a, wY1); - FAST_WRITE32(s2 + 0x0c, 1u); - - s2 += 0x10u; - } - - { - uint32_t fontPtr = FAST_READ32(kFontBase + fontOff); - uint32_t advOff = static_cast((glyphIdx * 2 + 1) * 16 + 8); - int16_t advW = static_cast(FAST_READ16(fontPtr + advOff)); - iVar15 += static_cast(static_cast(static_cast(advW)) * sclx); - } - } - goto label_next; - -label_space: - { - int spaceW = static_cast(FAST_READ32(kFontBase + fontOff + 0x1cu)); - iVar15 += static_cast(static_cast(spaceW) * sclx); - } - -label_next: - uStack_d8 = uVar9; - uVar20++; - } - - if (param14 != 0u) - { - if (alignCh != 'L') - { - if (alignCh == 'C' || alignCh == 'R') - { - int shift = paramW * 16 - iVar15; - if (alignCh == 'C') shift >>= 1; - if (iStack_dc > 0) - { - uint32_t adj = bufAddr + static_cast(iVar21 * 16) + 0x20u; - for (int k = 0; k < iStack_dc; k++) - { - int16_t oldX0 = static_cast(FAST_READ16(adj - 8u)); - int16_t oldX1 = static_cast(FAST_READ16(adj + 8u)); - FAST_WRITE16(adj - 8u, static_cast(oldX0 + static_cast(shift))); - FAST_WRITE16(adj + 8u, static_cast(oldX1 + static_cast(shift))); - adj += 0x20u; - } - } - } - else if (alignCh == 'J' && sLen > 1) - { - int iVar19_div = static_cast(sLen) - 1; - if (iVar19_div == 0) iVar19_div = 1; - int spacePer = (paramW * 16 - iVar15) / iVar19_div; - uint32_t adj = bufAddr + static_cast(iVar21 * 16) + 0x20u; - int accum = 0; - for (uint32_t jj = 0; jj < sLen; jj++) - { - int8_t jch = static_cast(FAST_READ8(strAddr + jj)); - if (jch > 0x20) - { - int16_t oldX0 = static_cast(FAST_READ16(adj - 8u)); - int16_t oldX1 = static_cast(FAST_READ16(adj + 8u)); - FAST_WRITE16(adj - 8u, static_cast(oldX0 + static_cast(accum))); - FAST_WRITE16(adj + 8u, static_cast(oldX1 + static_cast(accum))); - adj += 0x20u; - } - accum += spacePer; - } - } - } - - if (param14 != 0u) - { - uint32_t tagAddr = bufAddr + static_cast(iVar21 * 16); - FAST_WRITE64(tagAddr + 0x00, static_cast(static_cast(iStack_dc)) | 0x4400000000000000ull); - FAST_WRITE64(tagAddr + 0x08, 0x5353ull); - - uint32_t endAddr = bufAddr + static_cast(iVar22_qw * 16); - FAST_WRITE64(endAddr + 0x00, 0x1000000000008001ull); - FAST_WRITE64(endAddr + 0x08, 0x0eull); - - int iVar19_end = iVar22_qw + 1; - uint32_t endAddr2 = bufAddr + static_cast(iVar19_end * 16); - FAST_WRITE64(endAddr2 + 0x00, 0x01ff0000027f0000ull); - FAST_WRITE64(endAddr2 + 0x08, 0x40ull); - - iVar22_qw += 2; - } - } - - int ret = 0; - if (param14 != 0u) ret = iVar22_qw; - setReturnS32(ctx, ret); - ctx->pc = getRegU32(ctx, 31); -} - -void sceeFontPrintfAt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t oldSp = getRegU32(ctx, 29); - const uint32_t frame = oldSp - 0x900u; - - const uint32_t bufAddr = getRegU32(ctx, 4); - const uint32_t paramX = getRegU32(ctx, 5); - const uint32_t paramY = getRegU32(ctx, 6); - const uint32_t fmtAddr = getRegU32(ctx, 7); - - const uint8_t *callerVa = getConstMemPtr(rdram, oldSp + 16u); - uint8_t *frameVa = getMemPtr(rdram, frame + 0x8f8u); - if (callerVa && frameVa) - std::memcpy(frameVa, callerVa, 64u); - - SET_GPR_U32(ctx, 4, frame + 0x20u); - SET_GPR_U32(ctx, 5, fmtAddr); - SET_GPR_U32(ctx, 6, frame + 0x8f8u); - vsprintf(rdram, ctx, runtime); - - const uint32_t gp = getRegU32(ctx, 28); - uint32_t defaultSclxBits = FAST_READ32(gp + static_cast(static_cast(-0x7b54))); - uint32_t defaultSclyBits = FAST_READ32(gp + static_cast(static_cast(-0x7b50))); - uint32_t defaultColour = FAST_READ32(gp + static_cast(static_cast(-0x7b4c))); - uint32_t defaultFontId = FAST_READ32(gp + static_cast(static_cast(-0x7b58))); - uint32_t scrWidth = FAST_READ32(gp + static_cast(static_cast(-0x7b60))); - uint32_t scrHeight = FAST_READ32(gp + static_cast(static_cast(-0x7b5c))); - - std::memcpy(&ctx->f[12], &defaultSclxBits, sizeof(float)); - std::memcpy(&ctx->f[13], &defaultSclyBits, sizeof(float)); - - FAST_WRITE32(frame + 0x00u, frame + 0x20u); - FAST_WRITE32(frame + 0x08u, frame + 0x820u); - FAST_WRITE32(frame + 0x10u, frame + 0x824u); - FAST_WRITE32(frame + 0x18u, 1u); - - SET_GPR_U32(ctx, 29, frame); - SET_GPR_U32(ctx, 4, bufAddr); - SET_GPR_U32(ctx, 5, paramX); - SET_GPR_U32(ctx, 6, paramY); - SET_GPR_U32(ctx, 7, scrWidth); - SET_GPR_U32(ctx, 8, scrHeight); - SET_GPR_U32(ctx, 9, defaultColour); - SET_GPR_U32(ctx, 10, 0x4cu); - SET_GPR_U32(ctx, 11, defaultFontId); - - sceeFontGenerateString(rdram, ctx, runtime); - - SET_GPR_U32(ctx, 29, oldSp); - ctx->pc = getRegU32(ctx, 31); -} - -void sceeFontPrintfAt2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t oldSp = getRegU32(ctx, 29); - const uint32_t frame = oldSp - 0x900u; - - const uint32_t bufAddr = getRegU32(ctx, 4); - const uint32_t paramX = getRegU32(ctx, 5); - const uint32_t paramY = getRegU32(ctx, 6); - const uint32_t paramW = getRegU32(ctx, 7); - const uint32_t paramH = getRegU32(ctx, 8); - const uint32_t alignRaw = getRegU32(ctx, 9); - const uint32_t fmtAddr = getRegU32(ctx, 10); - const uint64_t param8 = GPR_U64(ctx, 11); - - int8_t alignChar = static_cast(alignRaw & 0xffu); - - FAST_WRITE64(frame + 0x8f8u, param8); - - SET_GPR_U32(ctx, 4, frame + 0x20u); - SET_GPR_U32(ctx, 5, fmtAddr); - SET_GPR_U32(ctx, 6, frame + 0x8f8u); - vsprintf(rdram, ctx, runtime); - - const uint32_t gp = getRegU32(ctx, 28); - uint32_t defaultSclxBits = FAST_READ32(gp + static_cast(static_cast(-0x7b54))); - uint32_t defaultSclyBits = FAST_READ32(gp + static_cast(static_cast(-0x7b50))); - uint32_t defaultColour = FAST_READ32(gp + static_cast(static_cast(-0x7b4c))); - uint32_t defaultFontId = FAST_READ32(gp + static_cast(static_cast(-0x7b58))); - - std::memcpy(&ctx->f[12], &defaultSclxBits, sizeof(float)); - std::memcpy(&ctx->f[13], &defaultSclyBits, sizeof(float)); - - FAST_WRITE32(frame + 0x00u, frame + 0x20u); - FAST_WRITE32(frame + 0x08u, frame + 0x820u); - FAST_WRITE32(frame + 0x10u, frame + 0x824u); - FAST_WRITE32(frame + 0x18u, 1u); - - SET_GPR_U32(ctx, 29, frame); - SET_GPR_U32(ctx, 4, bufAddr); - SET_GPR_U32(ctx, 5, paramX); - SET_GPR_U32(ctx, 6, paramY); - SET_GPR_U32(ctx, 7, paramW); - SET_GPR_U32(ctx, 8, paramH); - SET_GPR_U32(ctx, 9, defaultColour); - SET_GPR_U32(ctx, 10, static_cast(static_cast(alignChar))); - SET_GPR_U32(ctx, 11, defaultFontId); - - sceeFontGenerateString(rdram, ctx, runtime); - - SET_GPR_U32(ctx, 29, oldSp); - ctx->pc = getRegU32(ctx, 31); -} - -void sceeFontClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static constexpr uint32_t kFontBase = 0x176148u; - static constexpr uint32_t kFontEntrySz = 0x24u; - const int fontId = static_cast(getRegU32(ctx, 4)); - const uint32_t fontOff = static_cast(fontId * static_cast(kFontEntrySz)); - uint32_t glyphPtr = 0; - if (const uint8_t *p = getConstMemPtr(rdram, kFontBase + fontOff)) - glyphPtr = *reinterpret_cast(p); - if (glyphPtr != 0u) - { - if (runtime) - { - uint32_t kernPtr = 0; - if (const uint8_t *kp = getConstMemPtr(rdram, glyphPtr + 0x2000u)) - kernPtr = *reinterpret_cast(kp); - if (kernPtr != 0u) - runtime->guestFree(kernPtr); - runtime->guestFree(glyphPtr); - } - if (uint8_t *p = getMemPtr(rdram, kFontBase + fontOff)) - *reinterpret_cast(p) = 0u; - setReturnS32(ctx, 0); - } - else - { - setReturnS32(ctx, -1); - } -} - -void sceeFontSetColour(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t gp = getRegU32(ctx, 28); - writeU32AtGp(rdram, gp, -0x7b4c, getRegU32(ctx, 4)); -} - -void sceeFontSetMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t gp = getRegU32(ctx, 28); - writeU32AtGp(rdram, gp, -0x7c98, getRegU32(ctx, 4)); - setReturnS32(ctx, 0); -} - -void sceeFontSetFont(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t gp = getRegU32(ctx, 28); - writeU32AtGp(rdram, gp, -0x7b58, getRegU32(ctx, 4)); -} - -void sceeFontSetScale(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t gp = getRegU32(ctx, 28); - uint32_t sclx_bits, scly_bits; - std::memcpy(&sclx_bits, &ctx->f[12], sizeof(float)); - std::memcpy(&scly_bits, &ctx->f[13], sizeof(float)); - writeU32AtGp(rdram, gp, -0x7b54, sclx_bits); - writeU32AtGp(rdram, gp, -0x7b50, scly_bits); -} - -void sceIoctl(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int32_t cmd = static_cast(getRegU32(ctx, 5)); - const uint32_t argAddr = getRegU32(ctx, 6); - - // HTCI wait paths poll sceIoctl(fd, 1, &state) and expect state to move - // away from 1 once host-side I/O is no longer busy. - if (cmd == 1 && argAddr != 0u) - { - uint8_t *argPtr = getMemPtr(rdram, argAddr); - if (!argPtr) - { - setReturnS32(ctx, -1); - return; - } - - const uint32_t ready = 0u; - std::memcpy(argPtr, &ready, sizeof(ready)); - } - - setReturnS32(ctx, 0); -} - -void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static constexpr uint32_t REG_IPU_CTRL = 0x10002010u; - static constexpr uint32_t REG_IPU_CMD = 0x10002000u; - static constexpr uint32_t REG_IPU_IN_FIFO = 0x10007010u; - static constexpr uint32_t IQVAL_BASE = 0x1721e0u; - static constexpr uint32_t VQVAL_BASE = 0x172230u; - static constexpr uint32_t SETD4_CHCR_ENTRY = 0x126428u; - - if (!runtime) - return; - - PS2Memory &mem = runtime->memory(); - - auto setD4 = runtime->lookupFunction(SETD4_CHCR_ENTRY); - if (setD4) - { - ctx->r[4] = _mm_set_epi64x(0, 1); - { - PS2Runtime::GuestExecutionScope guestExecution(runtime); - setD4(rdram, ctx, runtime); - } - } - - mem.write32(REG_IPU_CTRL, 0x40000000u); - mem.write32(REG_IPU_CMD, 0u); - - __m128i v; - v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x00u); - mem.write128(REG_IPU_IN_FIFO, v); - v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x10u); - mem.write128(REG_IPU_IN_FIFO, v); - v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x20u); - mem.write128(REG_IPU_IN_FIFO, v); - v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x30u); - mem.write128(REG_IPU_IN_FIFO, v); - v = runtime->Load128(rdram, ctx, IQVAL_BASE + 0x40u); - mem.write128(REG_IPU_IN_FIFO, v); - mem.write128(REG_IPU_IN_FIFO, v); - mem.write128(REG_IPU_IN_FIFO, v); - mem.write128(REG_IPU_IN_FIFO, v); - - mem.write32(REG_IPU_CMD, 0x50000000u); - mem.write32(REG_IPU_CMD, 0x58000000u); - - v = runtime->Load128(rdram, ctx, VQVAL_BASE + 0x00u); - mem.write128(REG_IPU_IN_FIFO, v); - v = runtime->Load128(rdram, ctx, VQVAL_BASE + 0x10u); - mem.write128(REG_IPU_IN_FIFO, v); - - mem.write32(REG_IPU_CMD, 0x60000000u); - mem.write32(REG_IPU_CMD, 0x90000000u); - - mem.write32(REG_IPU_CTRL, 0x40000000u); - mem.write32(REG_IPU_CMD, 0u); -} - -void sceIpuRestartDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceIpuStopDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceIpuSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioLseek(rdram, ctx, runtime); -} - -void sceMcChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceMcChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcFormat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcGetDir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcGetEntSpace(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1024); -} - -void sceMcGetInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t typePtr = getRegU32(ctx, 6); - const uint32_t freePtr = getRegU32(ctx, 7); - const uint32_t formatPtr = readStackU32(rdram, ctx, 16); - - const int32_t cardType = 2; // PS2 memory card. - const int32_t freeBlocks = 0x2000; - const int32_t format = 2; // formatted. - - if (typePtr != 0u) - { - if (uint8_t *out = getMemPtr(rdram, typePtr)) - { - std::memcpy(out, &cardType, sizeof(cardType)); - } - } - if (freePtr != 0u) - { - if (uint8_t *out = getMemPtr(rdram, freePtr)) - { - std::memcpy(out, &freeBlocks, sizeof(freeBlocks)); - } - } - if (formatPtr != 0u) - { - if (uint8_t *out = getMemPtr(rdram, formatPtr)) - { - std::memcpy(out, &format, sizeof(format)); - } - } - - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceMcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int32_t fd = 0; - { - std::lock_guard lock(g_mcStateMutex); - fd = g_mcNextFd++; - if (g_mcNextFd <= 0) - { - g_mcNextFd = 1; - } - g_mcLastResult = fd; - } - setReturnS32(ctx, 0); -} - -void sceMcRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int32_t size = static_cast(getRegU32(ctx, 7)); - if (size > 0) - { - const uint32_t dstAddr = readStackU32(rdram, ctx, 16); - if (uint8_t *dst = getMemPtr(rdram, dstAddr)) - { - std::memset(dst, 0, static_cast(size)); - } - } - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = std::max(0, size); - } - setReturnS32(ctx, 0); -} - -void sceMcRename(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int32_t offset = static_cast(getRegU32(ctx, 5)); - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = std::max(0, offset); - } - setReturnS32(ctx, 0); -} - -void sceMcSetFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cmdPtr = getRegU32(ctx, 5); - const uint32_t resultPtr = getRegU32(ctx, 6); - int32_t result = 0; - { - std::lock_guard lock(g_mcStateMutex); - result = g_mcLastResult; - } - - if (cmdPtr != 0u) - { - if (uint8_t *out = getMemPtr(rdram, cmdPtr)) - { - const int32_t cmd = 0; - std::memcpy(out, &cmd, sizeof(cmd)); - } - } - if (resultPtr != 0u) - { - if (uint8_t *out = getMemPtr(rdram, resultPtr)) - { - std::memcpy(out, &result, sizeof(result)); - } - } - - // 1 = command finished in this runtime's immediate model. - setReturnS32(ctx, 1); -} - -void sceMcUnformat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = 0; - } - setReturnS32(ctx, 0); -} - -void sceMcWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int32_t size = static_cast(getRegU32(ctx, 7)); - { - std::lock_guard lock(g_mcStateMutex); - g_mcLastResult = std::max(0, size); - } - setReturnS32(ctx, 0); -} - -void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegAddBs", rdram, ctx, runtime); -} - -void sceMpegAddCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegAddCallback", rdram, ctx, runtime); -} - -void sceMpegAddStrCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnU32(ctx, 0u); -} - -void sceMpegClearRefBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)ctx; - (void)runtime; - static const uint32_t kRefGlobalAddrs[] = { - 0x171800u, 0x17180Cu, 0x171818u, 0x171804u, 0x171810u, 0x17181Cu - }; - for (uint32_t addr : kRefGlobalAddrs) - { - uint8_t *p = getMemPtr(rdram, addr); - if (!p) - continue; - uint32_t ptr = *reinterpret_cast(p); - if (ptr != 0u) - { - uint8_t *q = getMemPtr(rdram, ptr + 0x28u); - if (q) - *reinterpret_cast(q) = 0u; - } - } - setReturnU32(ctx, 1u); -} - -static void mpegGuestWrite32(uint8_t *rdram, uint32_t addr, uint32_t value) -{ - if (uint8_t *p = getMemPtr(rdram, addr)) - *reinterpret_cast(p) = value; -} -static void mpegGuestWrite64(uint8_t *rdram, uint32_t addr, uint64_t value) -{ - if (uint8_t *p = getMemPtr(rdram, addr)) - { - *reinterpret_cast(p) = static_cast(value); - *reinterpret_cast(p + 4) = static_cast(value >> 32); - } -} - -void sceMpegCreate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t param_1 = getRegU32(ctx, 4); // a0 - const uint32_t param_2 = getRegU32(ctx, 5); // a1 - const uint32_t param_3 = getRegU32(ctx, 6); // a2 - - const uint32_t uVar3 = (param_2 + 3u) & 0xFFFFFFFCu; - const int32_t iVar2_signed = static_cast(param_3) - static_cast(uVar3 - param_2); - - if (iVar2_signed <= 0x117) - { - setReturnU32(ctx, 0u); - return; - } - - const uint32_t puVar4 = uVar3 + 0x108u; - const uint32_t innerSize = static_cast(iVar2_signed) - 0x118u; - - mpegGuestWrite32(rdram, param_1 + 0x40, uVar3); - - const uint32_t a1_init = uVar3 + 0x118u; - mpegGuestWrite32(rdram, puVar4 + 0x0, a1_init); - mpegGuestWrite32(rdram, puVar4 + 0x4, innerSize); - mpegGuestWrite32(rdram, puVar4 + 0x8, a1_init); - mpegGuestWrite32(rdram, puVar4 + 0xC, a1_init); - - const uint32_t allocResult = runtime ? runtime->guestMalloc(0x600, 8u) : (uVar3 + 0x200u); - mpegGuestWrite32(rdram, uVar3 + 0x44, allocResult); - - // param_1[0..2] = 0; param_1[4..0xe] = 0xffffffff/0 as per decompilation - mpegGuestWrite32(rdram, param_1 + 0x00, 0); - mpegGuestWrite32(rdram, param_1 + 0x04, 0); - mpegGuestWrite32(rdram, param_1 + 0x08, 0); - mpegGuestWrite64(rdram, param_1 + 0x10, 0xFFFFFFFFFFFFFFFFULL); - mpegGuestWrite64(rdram, param_1 + 0x18, 0xFFFFFFFFFFFFFFFFULL); - mpegGuestWrite64(rdram, param_1 + 0x20, 0); - mpegGuestWrite64(rdram, param_1 + 0x28, 0xFFFFFFFFFFFFFFFFULL); - mpegGuestWrite64(rdram, param_1 + 0x30, 0xFFFFFFFFFFFFFFFFULL); - mpegGuestWrite64(rdram, param_1 + 0x38, 0); - - static const unsigned s_zeroOffsets[] = { - 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0, 0xE4, 0xE8, 0xF8, - 0x0C, 0x14, 0x2C, 0x34, 0x3C, - 0x48, 0xFC, 0x100, 0x104, 0x70, 0x90, 0xAC - }; - for (unsigned off : s_zeroOffsets) - mpegGuestWrite32(rdram, uVar3 + off, 0u); - mpegGuestWrite64(rdram, uVar3 + 0x78, 0); - mpegGuestWrite64(rdram, uVar3 + 0x88, 0); - - mpegGuestWrite64(rdram, uVar3 + 0xF0, 0xFFFFFFFFFFFFFFFFULL); - mpegGuestWrite32(rdram, uVar3 + 0x1C, 0x1209F8u); - mpegGuestWrite32(rdram, uVar3 + 0x24, 0x120A08u); - mpegGuestWrite32(rdram, uVar3 + 0xB0, 1u); - mpegGuestWrite32(rdram, uVar3 + 0x9C, 0xFFFFFFFFu); - mpegGuestWrite32(rdram, uVar3 + 0x80, 0xFFFFFFFFu); - mpegGuestWrite32(rdram, uVar3 + 0x94, 0xFFFFFFFFu); - mpegGuestWrite32(rdram, uVar3 + 0x98, 0xFFFFFFFFu); - - mpegGuestWrite32(rdram, 0x1717BCu, param_1); - - static const uint32_t s_refValues[] = { - 0x171A50u, 0x171C58u, 0x171CC0u, 0x171D28u, 0x171D90u, - 0x171AB8u, 0x171B20u, 0x171B88u, 0x171BF0u - }; - for (unsigned i = 0; i < 9u; ++i) - mpegGuestWrite32(rdram, 0x171800u + i * 4u, s_refValues[i]); - - uint32_t setDynamicRet = a1_init; - if (uint8_t *p = getMemPtr(rdram, puVar4 + 8)) - setDynamicRet = *reinterpret_cast(p); - mpegGuestWrite32(rdram, puVar4 + 12, setDynamicRet); - - setReturnU32(ctx, setDynamicRet); -} - -void sceMpegDelete(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDelete", rdram, ctx, runtime); -} - -void sceMpegDemuxPss(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDemuxPss", rdram, ctx, runtime); -} - -void sceMpegDemuxPssRing(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDemuxPssRing", rdram, ctx, runtime); -} - -void sceMpegDispCenterOffX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDispCenterOffX", rdram, ctx, runtime); -} - -void sceMpegDispCenterOffY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDispCenterOffY", rdram, ctx, runtime); -} - -void sceMpegDispHeight(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDispHeight", rdram, ctx, runtime); -} - -void sceMpegDispWidth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegDispWidth", rdram, ctx, runtime); -} - -void sceMpegGetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegGetDecodeMode", rdram, ctx, runtime); -} - -void sceMpegGetPicture(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - const uint32_t param_1 = getRegU32(ctx, 4); - if (uint8_t *base = getMemPtr(rdram, param_1)) - { - const uint32_t iVar1 = *reinterpret_cast(base + 0x40); - if (uint8_t *inner = getMemPtr(rdram, iVar1)) - { - *reinterpret_cast(inner + 0xb0) = 1; - *reinterpret_cast(inner + 0xd8) = (getRegU32(ctx, 5) & 0x0FFFFFFFu) | 0x20000000u; - *reinterpret_cast(inner + 0xe4) = getRegU32(ctx, 6); - *reinterpret_cast(inner + 0xdc) = 0; - *reinterpret_cast(inner + 0xe0) = 0; - } - } - setReturnU32(ctx, 0u); -} - -void sceMpegGetPictureRAW8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegGetPictureRAW8", rdram, ctx, runtime); -} - -void sceMpegGetPictureRAW8xy(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegGetPictureRAW8xy", rdram, ctx, runtime); -} - -void sceMpegInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegInit", rdram, ctx, runtime); -} - -void sceMpegIsEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - const uint32_t param_1 = getRegU32(ctx, 4); - uint8_t *base = getMemPtr(rdram, param_1 + 0x40u); - if (base) - { - uint32_t ptrAddr = *reinterpret_cast(base); - if (ptrAddr != 0u) - { - uint8_t *p = getMemPtr(rdram, ptrAddr); - if (p) - *reinterpret_cast(p) = 1u; - } - } - setReturnS32(ctx, 1); -} - -void sceMpegIsRefBuffEmpty(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegIsRefBuffEmpty", rdram, ctx, runtime); -} - -void sceMpegReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - const uint32_t param_1 = getRegU32(ctx, 4); - uint8_t *base = getMemPtr(rdram, param_1); - if (!base) - return; - uint32_t inner = *reinterpret_cast(base + 0x40); - if (inner == 0u) - return; - mpegGuestWrite32(rdram, inner + 0x00, 0u); - mpegGuestWrite32(rdram, inner + 0x04, 0u); - mpegGuestWrite32(rdram, inner + 0x08, 0u); - mpegGuestWrite32(rdram, param_1 + 0x08, 0u); - mpegGuestWrite32(rdram, inner + 0x80, 0xFFFFFFFFu); - mpegGuestWrite32(rdram, inner + 0xAC, 0u); - mpegGuestWrite32(rdram, 0x171904u, 0u); -} - -void sceMpegResetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegResetDefaultPtsGap", rdram, ctx, runtime); -} - -void sceMpegSetDecodeMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegSetDecodeMode", rdram, ctx, runtime); -} - -void sceMpegSetDefaultPtsGap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegSetDefaultPtsGap", rdram, ctx, runtime); -} - -void sceMpegSetImageBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceMpegSetImageBuff", rdram, ctx, runtime); -} - -void sceOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioOpen(rdram, ctx, runtime); -} - -void scePadEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadEnterPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadExitPressMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadGetButtonMask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - // Report all buttons supported. - setReturnS32(ctx, static_cast(0xFFFFu)); -} - -void scePadGetDmaStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnU32(ctx, getRegU32(ctx, 6)); -} - -void scePadGetFrameCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - static std::atomic frameCount{0}; - setReturnU32(ctx, frameCount++); -} - -void scePadGetModVersion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - // Arbitrary non-zero module version. - setReturnS32(ctx, 0x0200); -} - -void scePadGetPortMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 2); -} - -void scePadGetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - // 0 = completed/no pending request. - setReturnS32(ctx, 0); -} - -void scePadGetSlotMax(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - (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) -{ - const int32_t act = static_cast(getRegU32(ctx, 6)); - if (act < 0) - { - setReturnS32(ctx, 1); // one actuator descriptor - return; - } - setReturnS32(ctx, 0); -} - -void scePadInfoComb(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - // No combined modes reported. - setReturnS32(ctx, 0); -} - -void scePadInfoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - (void)rdram; - (void)runtime; - // Pressure mode is disabled in this minimal implementation. - setReturnS32(ctx, 0); -} - -void scePadInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadInit2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadPortClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadPortOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int port = static_cast(getRegU32(ctx, 4)); - const int slot = static_cast(getRegU32(ctx, 5)); - const uint32_t dataAddr = getRegU32(ctx, 6); - uint8_t *data = getMemPtr(rdram, dataAddr); - if (!data) - { - setReturnS32(ctx, 0); - return; - } - - PadInputState state; - bool useOverride = false; - { - std::lock_guard lock(g_padOverrideMutex); - if (g_padOverrideEnabled) - { - state = g_padOverrideState; - useOverride = true; - } - } - - if (!useOverride) - { - if (runtime && runtime->padBackend().readState(port, slot, data, 32)) - { - setReturnS32(ctx, 1); - return; - } - - applyGamepadState(state); - applyKeyboardState(state, true); - } - - fillPadStatus(data, state); - - if (padDebugEnabled()) - { - static uint32_t logCounter = 0; - if ((logCounter++ % 60u) == 0u) - { - std::cout << "[pad] buttons=0x" << std::hex << state.buttons << std::dec - << " lx=" << static_cast(state.lx) - << " ly=" << static_cast(state.ly) - << " rx=" << static_cast(state.rx) - << " ry=" << static_cast(state.ry) - << (useOverride ? " (override)" : "") << std::endl; - } - } - - setReturnS32(ctx, 1); -} - -void scePadReqIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - const uint32_t state = getRegU32(ctx, 4); - const uint32_t strAddr = getRegU32(ctx, 5); - char *buf = reinterpret_cast(getMemPtr(rdram, strAddr)); - if (!buf) - { - setReturnS32(ctx, -1); - return; - } - - const char *text = (state == 0) ? "COMPLETE" : "BUSY"; - std::strncpy(buf, text, 31); - buf[31] = '\0'; - setReturnS32(ctx, 0); -} - -void scePadSetActAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadSetActDirect(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadSetButtonInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadSetMainMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadSetReqState(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadSetVrefParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 1); -} - -void scePadSetWarningLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 0); -} - -void scePadStateIntToStr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - const uint32_t state = getRegU32(ctx, 4); - const uint32_t strAddr = getRegU32(ctx, 5); - char *buf = reinterpret_cast(getMemPtr(rdram, strAddr)); - if (!buf) - { - setReturnS32(ctx, -1); - return; - } - - const char *text = "UNKNOWN"; - if (state == 6) - { - text = "STABLE"; - } - else if (state == 1) - { - text = "FINDPAD"; - } - else if (state == 0) - { - text = "DISCONNECTED"; - } - - std::strncpy(buf, text, 31); - buf[31] = '\0'; - setReturnS32(ctx, 0); -} - -void scePrintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t format_addr = getRegU32(ctx, 4); - const std::string formatOwned = readPs2CStringBounded(rdram, runtime, format_addr, 1024); - if (format_addr == 0) - return; - 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 scePrintf: " << logLine; - std::cout << std::flush; - } - else if (count == kMaxPrintfLogs + 1) - { - std::cerr << "PS2 printf logging suppressed after " << kMaxPrintfLogs << " lines" << std::endl; - } -} - -void sceRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioRead(rdram, ctx, runtime); -} - -void sceResetttyinit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceResetttyinit", rdram, ctx, runtime); -} - -void sceSdCallBack(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSdCallBack", rdram, ctx, runtime); -} - -void sceSdRemote(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSdRemote", rdram, ctx, runtime); -} - -void sceSdRemoteInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSdRemoteInit", rdram, ctx, runtime); -} - -void sceSdTransToIOP(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSdTransToIOP", rdram, ctx, runtime); -} - -void sceSetBrokenLink(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSetBrokenLink", rdram, ctx, runtime); -} - -void sceSetPtm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSetPtm", rdram, ctx, runtime); -} - -namespace -{ - struct Ps2SifDmaTransfer - { - uint32_t src = 0; - uint32_t dest = 0; - int32_t size = 0; - int32_t attr = 0; - }; - static_assert(sizeof(Ps2SifDmaTransfer) == 16u, "Unexpected SIF DMA descriptor size"); - - std::mutex g_sifDmaTransferMutex; - uint32_t g_nextSifDmaTransferId = 1u; - std::mutex g_sifCmdStateMutex; - std::unordered_map g_sifRegs; - std::unordered_map g_sifSregs; - std::unordered_map g_sifCmdHandlers; - uint32_t g_sifCmdBuffer = 0u; - uint32_t g_sifSysCmdBuffer = 0u; - bool g_sifCmdInitialized = false; - uint32_t g_sifGetRegLogCount = 0u; - uint32_t g_sifSetRegLogCount = 0u; - - constexpr uint32_t kSifRegBootStatus = 0x4u; - constexpr uint32_t kSifRegMainAddr = 0x80000000u; - constexpr uint32_t kSifRegSubAddr = 0x80000001u; - constexpr uint32_t kSifRegMsCom = 0x80000002u; - constexpr uint32_t kSifBootReadyMask = 0x00020000u; - - void seedDefaultSifRegsLocked() - { - g_sifRegs.clear(); - g_sifSregs.clear(); - g_sifCmdHandlers.clear(); - g_sifCmdBuffer = 0u; - g_sifSysCmdBuffer = 0u; - g_sifCmdInitialized = false; - g_sifGetRegLogCount = 0u; - g_sifSetRegLogCount = 0u; - - g_sifRegs[kSifRegBootStatus] = kSifBootReadyMask; - g_sifRegs[kSifRegMainAddr] = 0u; - g_sifRegs[kSifRegSubAddr] = 0u; - g_sifRegs[kSifRegMsCom] = 0u; - } - - bool shouldTraceSifReg(uint32_t reg) - { - switch (reg) - { - case 0x2u: - case 0x4u: - case 0x80000000u: - case 0x80000001u: - case 0x80000002u: - return true; - default: - return false; - } - } - - struct SifStateInitializer - { - SifStateInitializer() - { - std::lock_guard lock(g_sifCmdStateMutex); - seedDefaultSifRegsLocked(); - } - } g_sifStateInitializer; - - uint32_t allocateSifDmaTransferId() - { - std::lock_guard lock(g_sifDmaTransferMutex); - uint32_t id = g_nextSifDmaTransferId++; - if (id == 0u) - { - id = g_nextSifDmaTransferId++; - } - return id; - } - - bool isCopyableGuestAddress(uint32_t addr) - { - if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) - { - return true; - } - - if (addr < 0x20000000u) - { - return true; - } - - if (addr >= 0x20000000u && addr < 0x40000000u) - { - return true; - } - - if (addr >= 0x80000000u && addr < 0xC0000000u) - { - return true; - } - - return false; - } - - bool canCopyGuestByteRange(const uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes) - { - if (!rdram) - { - return false; - } - - if (sizeBytes == 0u) - { - return true; - } - - for (uint32_t i = 0u; i < sizeBytes; ++i) - { - const uint32_t srcByteAddr = srcAddr + i; - const uint32_t dstByteAddr = dstAddr + i; - - if (!isCopyableGuestAddress(srcByteAddr) || !isCopyableGuestAddress(dstByteAddr)) - { - return false; - } - - const uint8_t *src = getConstMemPtr(rdram, srcByteAddr); - const uint8_t *dst = getConstMemPtr(rdram, dstByteAddr); - if (!src || !dst) - { - return false; - } - } - - return true; - } - - bool copyGuestByteRange(uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes) - { - if (!canCopyGuestByteRange(rdram, dstAddr, srcAddr, sizeBytes)) - { - return false; - } - - if (sizeBytes == 0u) - { - return true; - } - - const uint64_t srcBegin = srcAddr; - const uint64_t srcEnd = srcBegin + static_cast(sizeBytes); - const uint64_t dstBegin = dstAddr; - const bool copyBackward = (dstBegin > srcBegin) && (dstBegin < srcEnd); - - if (copyBackward) - { - for (uint32_t i = sizeBytes; i > 0u; --i) - { - const uint32_t index = i - 1u; - const uint8_t *src = getConstMemPtr(rdram, srcAddr + index); - uint8_t *dst = getMemPtr(rdram, dstAddr + index); - if (!src || !dst) - { - return false; - } - *dst = *src; - } - return true; - } - - for (uint32_t i = 0; i < sizeBytes; ++i) - { - const uint8_t *src = getConstMemPtr(rdram, srcAddr + i); - uint8_t *dst = getMemPtr(rdram, dstAddr + i); - if (!src || !dst) - { - return false; - } - *dst = *src; - } - return true; - } -} - -void resetSifState() -{ - std::lock_guard lock(g_sifCmdStateMutex); - seedDefaultSifRegsLocked(); -} - -void sceSifAddCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cid = getRegU32(ctx, 4); - const uint32_t handler = getRegU32(ctx, 5); - std::lock_guard lock(g_sifCmdStateMutex); - g_sifCmdHandlers[cid] = handler; - setReturnS32(ctx, 0); -} - -void sceSifAllocIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - ps2_syscalls::SifBindRpc(rdram, ctx, runtime); -} - -void sceSifCheckStatRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifCheckStatRpc(rdram, ctx, runtime); -} - -void sceSifDmaStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - (void)getRegU32(ctx, 4); // trid - - // Transfers are applied immediately by sceSifSetDma in this runtime. - setReturnS32(ctx, -1); -} - -void sceSifExecRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifExitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - std::lock_guard lock(g_sifCmdStateMutex); - seedDefaultSifRegsLocked(); - setReturnS32(ctx, 0); -} - -void sceSifExitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifFreeIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifGetDataTable(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - std::lock_guard lock(g_sifCmdStateMutex); - setReturnU32(ctx, g_sifCmdBuffer); -} - -void sceSifGetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, getRegU32(ctx, 4)); -} - -void sceSifGetNextRequest(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifGetOtherData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - - const uint32_t rdAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - const uint32_t dstAddr = getRegU32(ctx, 6); - const int32_t sizeSigned = static_cast(getRegU32(ctx, 7)); - - if (sizeSigned <= 0) - { - setReturnS32(ctx, 0); - return; - } - - const uint32_t size = static_cast(sizeSigned); - if (size > PS2_RAM_SIZE) - { - static uint32_t warnCount = 0; - if (warnCount < 32u) - { - std::cerr << "sceSifGetOtherData rejected oversized transfer size=0x" - << std::hex << size << std::dec << std::endl; - ++warnCount; - } - setReturnS32(ctx, -1); - return; - } - - auto readGuestU32Local = [&](uint32_t addr, uint32_t &out) -> bool - { - const uint8_t *ptr = getConstMemPtr(rdram, addr); - if (!ptr) - { - out = 0u; - return false; - } - std::memcpy(&out, ptr, sizeof(out)); - return true; - }; - - auto readGuestS16Local = [&](uint32_t addr, int16_t &out) -> bool - { - const uint8_t *b0 = getConstMemPtr(rdram, addr + 0u); - const uint8_t *b1 = getConstMemPtr(rdram, addr + 1u); - if (!b0 || !b1) - { - out = 0; - return false; - } - const uint16_t raw = static_cast(static_cast(*b0) | - (static_cast(*b1) << 8)); - out = static_cast(raw); - return true; - }; - - constexpr uint32_t kSndTransTypeAddr = 0x01E0E1C0u; - constexpr uint32_t kSndTransBankAddr = 0x01E0E1C8u; - constexpr uint32_t kSndTransLevelAddr = 0x01E0E1B8u; - constexpr uint32_t kSndGetAdrsAddr = 0x01E212D8u; - constexpr uint32_t kSndStatusMirrorAddr = 0x01E213C0u; - constexpr uint32_t kSndSeCheckAddr = 0x01E0EF10u; - constexpr uint32_t kSndMidiCheckAddr = 0x01E0EF20u; - - static uint32_t s_sifGetOtherDataStatusLogs = 0u; - const bool isSndStatusTransfer = (size == 0x42u); - const uint32_t statusLogIndex = s_sifGetOtherDataStatusLogs++; - const bool shouldLogStatus = - isSndStatusTransfer && - (statusLogIndex < 96u || (statusLogIndex % 256u) == 0u); - - if (shouldLogStatus) - { - uint32_t transType = 0u; - uint32_t transLevel = 0u; - uint32_t transBank = 0u; - uint32_t getAdrs = 0u; - (void)readGuestU32Local(kSndTransTypeAddr, transType); - (void)readGuestU32Local(kSndTransLevelAddr, transLevel); - (void)readGuestU32Local(kSndTransBankAddr, transBank); - (void)readGuestU32Local(kSndGetAdrsAddr, getAdrs); - std::cout << "[sceSifGetOtherData] src=0x" << std::hex << srcAddr - << " dst=0x" << dstAddr - << " size=0x" << size - << " get_adrs=0x" << getAdrs - << std::dec - << " transType=" << transType - << " transLevel=" << transLevel - << " transBank=" << transBank - << std::endl; - } - - // Keep RECVX SND_STATUS checksums synchronized with EE-side transfer checks. - if (srcAddr == 0x00012000u && size == 0x42u) - { - constexpr uint32_t kPrimarySeAddr = 0x01E0EF10u; - constexpr uint32_t kPrimaryMidiAddr = 0x01E0EF20u; - constexpr uint32_t kFallbackSeAddr = 0x01E1EF10u; - constexpr uint32_t kFallbackMidiAddr = 0x01E1EF20u; - - auto hasAnyNonZero = [](const uint8_t *ptr, size_t bytes) -> bool - { - if (!ptr) - { - return false; - } - for (size_t i = 0; i < bytes; ++i) - { - if (ptr[i] != 0u) - { - return true; - } - } - return false; - }; - - const uint8_t *selectedSe = getConstMemPtr(rdram, kPrimarySeAddr); - const uint8_t *selectedMidi = getConstMemPtr(rdram, kPrimaryMidiAddr); - - const bool primaryLooksLive = - hasAnyNonZero(selectedSe, 5u * sizeof(int16_t)) || - hasAnyNonZero(selectedMidi, 4u * sizeof(int16_t)); - - if ((!selectedSe || !selectedMidi) || !primaryLooksLive) - { - const uint8_t *fallbackSe = getConstMemPtr(rdram, kFallbackSeAddr); - const uint8_t *fallbackMidi = getConstMemPtr(rdram, kFallbackMidiAddr); - const bool fallbackLooksLive = - hasAnyNonZero(fallbackSe, 5u * sizeof(int16_t)) || - hasAnyNonZero(fallbackMidi, 4u * sizeof(int16_t)); - - if (fallbackLooksLive) - { - selectedSe = fallbackSe; - selectedMidi = fallbackMidi; - } - } - - if (selectedSe && selectedMidi) - { - if (uint8_t *status = getMemPtr(rdram, srcAddr)) - { - std::memcpy(status + 0x26u, selectedSe, 5u * sizeof(int16_t)); // se_sum[5] - std::memcpy(status + 0x1Eu, selectedMidi, 4u * sizeof(int16_t)); // midi_sum[4] - } - } - - if (shouldLogStatus) - { - int16_t se0 = 0; - int16_t midi0 = 0; - int16_t seChk0 = 0; - int16_t midiChk0 = 0; - (void)readGuestS16Local(srcAddr + 0x26u, se0); - (void)readGuestS16Local(srcAddr + 0x1Eu, midi0); - (void)readGuestS16Local(kSndSeCheckAddr + 0u, seChk0); - (void)readGuestS16Local(kSndMidiCheckAddr + 0u, midiChk0); - std::cout << "[sceSifGetOtherData:sndstatus] srcSe0=" << se0 - << " srcMidi0=" << midi0 - << " chkSe0=" << seChk0 - << " chkMidi0=" << midiChk0 - << std::endl; - } - } - - if (!copyGuestByteRange(rdram, dstAddr, srcAddr, size)) - { - static uint32_t warnCount = 0; - if (warnCount < 32u) - { - std::cerr << "sceSifGetOtherData copy failed src=0x" << std::hex << srcAddr - << " dst=0x" << dstAddr - << " size=0x" << size - << std::dec << std::endl; - ++warnCount; - } - setReturnS32(ctx, -1); - return; - } - - // SifRpcReceiveData_t keeps src/dest/size at offsets 0x10/0x14/0x18. - if (uint8_t *rd = getMemPtr(rdram, rdAddr)) - { - std::memcpy(rd + 0x10u, &srcAddr, sizeof(srcAddr)); - std::memcpy(rd + 0x14u, &dstAddr, sizeof(dstAddr)); - std::memcpy(rd + 0x18u, &size, sizeof(size)); - } - - if (shouldLogStatus) - { - uint32_t transBank = 0u; - (void)readGuestU32Local(kSndTransBankAddr, transBank); - int16_t dstSe = 0; - int16_t dstMidi = 0; - int16_t mirrorSe = 0; - int16_t mirrorMidi = 0; - int16_t bankSeChk = 0; - int16_t bankMidiChk = 0; - (void)readGuestS16Local(dstAddr + 0x26u, dstSe); - (void)readGuestS16Local(dstAddr + 0x1Eu, dstMidi); - (void)readGuestS16Local(kSndStatusMirrorAddr + 0x26u, mirrorSe); - (void)readGuestS16Local(kSndStatusMirrorAddr + 0x1Eu, mirrorMidi); - if (transBank < 5u) - { - (void)readGuestS16Local(kSndSeCheckAddr + (transBank * 2u), bankSeChk); - } - if (transBank < 4u) - { - (void)readGuestS16Local(kSndMidiCheckAddr + (transBank * 2u), bankMidiChk); - } - std::cout << "[sceSifGetOtherData:post] bank=" << transBank - << " dstSe=" << dstSe - << " dstMidi=" << dstMidi - << " mirrorSe=" << mirrorSe - << " mirrorMidi=" << mirrorMidi - << " bankSeChk=" << bankSeChk - << " bankMidiChk=" << bankMidiChk - << std::endl; - } - - setReturnS32(ctx, 0); -} - -void sceSifGetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t reg = getRegU32(ctx, 4); - uint32_t value = 0u; - bool shouldLog = false; - { - std::lock_guard lock(g_sifCmdStateMutex); - auto it = g_sifRegs.find(reg); - if (it != g_sifRegs.end()) - { - value = it->second; - } - shouldLog = shouldTraceSifReg(reg) && g_sifGetRegLogCount < 128u; - if (shouldLog) - { - ++g_sifGetRegLogCount; - } - } - if (shouldLog) - { - auto flags = std::cerr.flags(); - std::cerr << "[sceSifGetReg] reg=0x" << std::hex << reg - << " value=0x" << value - << " pc=0x" << (ctx ? ctx->pc : 0u) - << " ra=0x" << (ctx ? getRegU32(ctx, 31) : 0u) - << std::dec << std::endl; - std::cerr.flags(flags); - } - setReturnU32(ctx, value); -} - -void sceSifGetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t reg = getRegU32(ctx, 4); - uint32_t value = 0u; - { - std::lock_guard lock(g_sifCmdStateMutex); - auto it = g_sifSregs.find(reg); - if (it != g_sifSregs.end()) - { - value = it->second; - } - } - setReturnU32(ctx, value); -} - -void sceSifInitCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - std::lock_guard lock(g_sifCmdStateMutex); - g_sifCmdInitialized = true; - setReturnS32(ctx, 0); -} - -void sceSifInitIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_iopHeapNext = kIopHeapBase; - setReturnS32(ctx, 0); -} - -void sceSifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifInitRpc(rdram, ctx, runtime); -} - -void sceSifIsAliveIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::sceSifLoadElf(rdram, ctx, runtime); -} - -void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::sceSifLoadElfPart(rdram, ctx, runtime); -} - -void sceSifLoadFileReset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifLoadIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::sceSifLoadModuleBuffer(rdram, ctx, runtime); -} - -void sceSifRebootIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceSifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifRegisterRpc(rdram, ctx, runtime); -} - -void sceSifRemoveCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cid = getRegU32(ctx, 4); - std::lock_guard lock(g_sifCmdStateMutex); - g_sifCmdHandlers.erase(cid); - setReturnS32(ctx, 0); -} - -void sceSifRemoveRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifRemoveRpc(rdram, ctx, runtime); -} - -void sceSifRemoveRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifRemoveRpcQueue(rdram, ctx, runtime); -} - -void sceSifResetIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceSifRpcLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifSetCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t newBuffer = getRegU32(ctx, 4); - uint32_t prev = 0u; - { - std::lock_guard lock(g_sifCmdStateMutex); - prev = g_sifCmdBuffer; - g_sifCmdBuffer = newBuffer; - } - setReturnU32(ctx, prev); -} - -void sceSifSetDChain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, 0); -} - -void sceSifSetDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - - const uint32_t dmatAddr = getRegU32(ctx, 4); - const uint32_t count = getRegU32(ctx, 5); - if (!dmatAddr || count == 0u || count > 32u) - { - setReturnS32(ctx, 0); - return; - } - - std::array pending{}; - uint32_t pendingCount = 0u; - bool ok = true; - for (uint32_t i = 0; i < count; ++i) - { - const uint32_t entryAddr = dmatAddr + (i * static_cast(sizeof(Ps2SifDmaTransfer))); - const uint8_t *entry = getConstMemPtr(rdram, entryAddr); - if (!entry) - { - ok = false; - break; - } - - Ps2SifDmaTransfer xfer{}; - std::memcpy(&xfer, entry, sizeof(xfer)); - if (xfer.size <= 0) - { - continue; - } - - const uint32_t sizeBytes = static_cast(xfer.size); - if (sizeBytes > PS2_RAM_SIZE) - { - ok = false; - break; - } - if (!canCopyGuestByteRange(rdram, xfer.dest, xfer.src, sizeBytes)) - { - ok = false; - break; - } - - pending[pendingCount++] = xfer; - } - - if (ok) - { - for (uint32_t i = 0; i < pendingCount; ++i) - { - const Ps2SifDmaTransfer &xfer = pending[i]; - if (!copyGuestByteRange(rdram, xfer.dest, xfer.src, static_cast(xfer.size))) - { - ok = false; - break; - } - } - } - - if (!ok) - { - static uint32_t warnCount = 0; - if (warnCount < 32u) - { - std::cerr << "sceSifSetDma failed dmat=0x" << std::hex << dmatAddr - << " count=0x" << count - << std::dec << std::endl; - ++warnCount; - } - setReturnS32(ctx, 0); - return; - } - - ps2_syscalls::dispatchDmacHandlersForCause(rdram, runtime, 5u); - - setReturnS32(ctx, static_cast(allocateSifDmaTransferId())); -} - -void sceSifSetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnU32(ctx, getRegU32(ctx, 5)); -} - -void sceSifSetReg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t reg = getRegU32(ctx, 4); - const uint32_t value = getRegU32(ctx, 5); - uint32_t prev = 0u; - bool shouldLog = false; - { - std::lock_guard lock(g_sifCmdStateMutex); - auto it = g_sifRegs.find(reg); - if (it != g_sifRegs.end()) - { - prev = it->second; - } - g_sifRegs[reg] = value; - shouldLog = shouldTraceSifReg(reg) && g_sifSetRegLogCount < 128u; - if (shouldLog) - { - ++g_sifSetRegLogCount; - } - } - if (shouldLog) - { - auto flags = std::cerr.flags(); - std::cerr << "[sceSifSetReg] reg=0x" << std::hex << reg - << " prev=0x" << prev - << " value=0x" << value - << " pc=0x" << (ctx ? ctx->pc : 0u) - << " ra=0x" << (ctx ? getRegU32(ctx, 31) : 0u) - << std::dec << std::endl; - std::cerr.flags(flags); - } - setReturnU32(ctx, prev); -} - -void sceSifSetRpcQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::SifSetRpcQueue(rdram, ctx, runtime); -} - -void sceSifSetSreg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t reg = getRegU32(ctx, 4); - const uint32_t value = getRegU32(ctx, 5); - uint32_t prev = 0u; - { - std::lock_guard lock(g_sifCmdStateMutex); - auto it = g_sifSregs.find(reg); - if (it != g_sifSregs.end()) - { - prev = it->second; - } - g_sifSregs[reg] = value; - } - setReturnU32(ctx, prev); -} - -void sceSifSetSysCmdBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t newBuffer = getRegU32(ctx, 4); - uint32_t prev = 0u; - { - std::lock_guard lock(g_sifCmdStateMutex); - prev = g_sifSysCmdBuffer; - g_sifSysCmdBuffer = newBuffer; - } - setReturnU32(ctx, prev); -} - -void sceSifStopDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSifSyncIop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void sceSifWriteBackDCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSSyn_BreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_BreakAtick", rdram, ctx, runtime); -} - -void sceSSyn_ClearBreakAtick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_ClearBreakAtick", rdram, ctx, runtime); -} - -void sceSSyn_SendExcMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SendExcMsg", rdram, ctx, runtime); -} - -void sceSSyn_SendNrpnMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SendNrpnMsg", rdram, ctx, runtime); -} - -void sceSSyn_SendRpnMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SendRpnMsg", rdram, ctx, runtime); -} - -void sceSSyn_SendShortMsg(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SendShortMsg", rdram, ctx, runtime); -} - -void sceSSyn_SetChPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetChPriority", rdram, ctx, runtime); -} - -void sceSSyn_SetMasterVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetMasterVolume", rdram, ctx, runtime); -} - -void sceSSyn_SetOutPortVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetOutPortVolume", rdram, ctx, runtime); -} - -void sceSSyn_SetOutputAssign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetOutputAssign", rdram, ctx, runtime); -} - -void sceSSyn_SetOutputMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceSSyn_SetPortMaxPoly(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetPortMaxPoly", rdram, ctx, runtime); -} - -void sceSSyn_SetPortVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetPortVolume", rdram, ctx, runtime); -} - -void sceSSyn_SetTvaEnvMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSSyn_SetTvaEnvMode", rdram, ctx, runtime); -} - -void sceSynthesizerAmpProcI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAmpProcI", rdram, ctx, runtime); -} - -void sceSynthesizerAmpProcNI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAmpProcNI", rdram, ctx, runtime); -} - -void sceSynthesizerAssignAllNoteOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAssignAllNoteOff", rdram, ctx, runtime); -} - -void sceSynthesizerAssignAllSoundOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAssignAllSoundOff", rdram, ctx, runtime); -} - -void sceSynthesizerAssignHoldChange(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAssignHoldChange", rdram, ctx, runtime); -} - -void sceSynthesizerAssignNoteOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAssignNoteOff", rdram, ctx, runtime); -} - -void sceSynthesizerAssignNoteOn(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerAssignNoteOn", rdram, ctx, runtime); -} - -void sceSynthesizerCalcEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerCalcEnv", rdram, ctx, runtime); -} - -void sceSynthesizerCalcPortamentPitch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerCalcPortamentPitch", rdram, ctx, runtime); -} - -void sceSynthesizerCalcTvfCoefAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerCalcTvfCoefAll", rdram, ctx, runtime); -} - -void sceSynthesizerCalcTvfCoefF0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerCalcTvfCoefF0", rdram, ctx, runtime); -} - -void sceSynthesizerCent2PhaseInc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerCent2PhaseInc", rdram, ctx, runtime); -} - -void sceSynthesizerChangeEffectSend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeEffectSend", rdram, ctx, runtime); -} - -void sceSynthesizerChangeHsPanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeHsPanpot", rdram, ctx, runtime); -} - -void sceSynthesizerChangeNrpnCutOff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeNrpnCutOff", rdram, ctx, runtime); -} - -void sceSynthesizerChangeNrpnLfoDepth(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeNrpnLfoDepth", rdram, ctx, runtime); -} - -void sceSynthesizerChangeNrpnLfoRate(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeNrpnLfoRate", rdram, ctx, runtime); -} - -void sceSynthesizerChangeOutAttrib(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeOutAttrib", rdram, ctx, runtime); -} - -void sceSynthesizerChangeOutVol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangeOutVol", rdram, ctx, runtime); -} - -void sceSynthesizerChangePanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePanpot", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartBendSens(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartBendSens", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartExpression(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartExpression", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartHsExpression(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartHsExpression", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartHsPitchBend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartHsPitchBend", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartModuration(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartModuration", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartPitchBend(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartPitchBend", rdram, ctx, runtime); -} - -void sceSynthesizerChangePartVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePartVolume", rdram, ctx, runtime); -} - -void sceSynthesizerChangePortamento(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePortamento", rdram, ctx, runtime); -} - -void sceSynthesizerChangePortamentoTime(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerChangePortamentoTime", rdram, ctx, runtime); -} - -void sceSynthesizerClearKeyMap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerClearKeyMap", rdram, ctx, runtime); -} - -void sceSynthesizerClearSpr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerClearSpr", rdram, ctx, runtime); -} - -void sceSynthesizerCopyOutput(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerCopyOutput", rdram, ctx, runtime); -} - -void sceSynthesizerDmaFromSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerDmaFromSPR", rdram, ctx, runtime); -} - -void sceSynthesizerDmaSpr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerDmaSpr", rdram, ctx, runtime); -} - -void sceSynthesizerDmaToSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerDmaToSPR", rdram, ctx, runtime); -} - -void sceSynthesizerGetPartial(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerGetPartial", rdram, ctx, runtime); -} - -void sceSynthesizerGetPartOutLevel(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerGetPartOutLevel", rdram, ctx, runtime); -} - -void sceSynthesizerGetSampleParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerGetSampleParam", rdram, ctx, runtime); -} - -void sceSynthesizerHsMessage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerHsMessage", rdram, ctx, runtime); -} - -void sceSynthesizerLfoNone(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerLfoNone", rdram, ctx, runtime); -} - -void sceSynthesizerLfoProc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerLfoProc", rdram, ctx, runtime); -} - -void sceSynthesizerLfoSawDown(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerLfoSawDown", rdram, ctx, runtime); -} - -void sceSynthesizerLfoSawUp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerLfoSawUp", rdram, ctx, runtime); -} - -void sceSynthesizerLfoSquare(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerLfoSquare", rdram, ctx, runtime); -} - -void sceSynthesizerReadNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerReadNoise", rdram, ctx, runtime); -} - -void sceSynthesizerReadNoiseAdd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerReadNoiseAdd", rdram, ctx, runtime); -} - -void sceSynthesizerReadSample16(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerReadSample16", rdram, ctx, runtime); -} - -void sceSynthesizerReadSample16Add(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerReadSample16Add", rdram, ctx, runtime); -} - -void sceSynthesizerReadSample8(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerReadSample8", rdram, ctx, runtime); -} - -void sceSynthesizerReadSample8Add(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerReadSample8Add", rdram, ctx, runtime); -} - -void sceSynthesizerResetPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerResetPart", rdram, ctx, runtime); -} - -void sceSynthesizerRestorDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerRestorDma", rdram, ctx, runtime); -} - -void sceSynthesizerSelectPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSelectPatch", rdram, ctx, runtime); -} - -void sceSynthesizerSendShortMessage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSendShortMessage", rdram, ctx, runtime); -} - -void sceSynthesizerSetMasterVolume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetMasterVolume", rdram, ctx, runtime); -} - -void sceSynthesizerSetRVoice(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetRVoice", rdram, ctx, runtime); -} - -void sceSynthesizerSetupDma(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupDma", rdram, ctx, runtime); -} - -void sceSynthesizerSetupLfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupLfo", rdram, ctx, runtime); -} - -void sceSynthesizerSetupMidiModuration(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupMidiModuration", rdram, ctx, runtime); -} - -void sceSynthesizerSetupMidiPanpot(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupMidiPanpot", rdram, ctx, runtime); -} - -void sceSynthesizerSetupNewNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupNewNoise", rdram, ctx, runtime); -} - -void sceSynthesizerSetupReleaseEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupReleaseEnv", rdram, ctx, runtime); -} - -void sceSynthesizerSetuptEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetuptEnv", rdram, ctx, runtime); -} - -void sceSynthesizerSetupTruncateTvaEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupTruncateTvaEnv", rdram, ctx, runtime); -} - -void sceSynthesizerSetupTruncateTvfPitchEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerSetupTruncateTvfPitchEnv", rdram, ctx, runtime); -} - -void sceSynthesizerTonegenerator(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerTonegenerator", rdram, ctx, runtime); -} - -void sceSynthesizerTransposeMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerTransposeMatrix", rdram, ctx, runtime); -} - -void sceSynthesizerTvfProcI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerTvfProcI", rdram, ctx, runtime); -} - -void sceSynthesizerTvfProcNI(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerTvfProcNI", rdram, ctx, runtime); -} - -void sceSynthesizerWaitDmaFromSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerWaitDmaFromSPR", rdram, ctx, runtime); -} - -void sceSynthesizerWaitDmaToSPR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthesizerWaitDmaToSPR", rdram, ctx, runtime); -} - -void sceSynthsizerGetDrumPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthsizerGetDrumPatch", rdram, ctx, runtime); -} - -void sceSynthsizerGetMeloPatch(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthsizerGetMeloPatch", rdram, ctx, runtime); -} - -void sceSynthsizerLfoNoise(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthsizerLfoNoise", rdram, ctx, runtime); -} - -void sceSynthSizerLfoTriangle(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceSynthSizerLfoTriangle", rdram, ctx, runtime); -} - -void sceTtyHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceTtyHandler", rdram, ctx, runtime); -} - -void sceTtyInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceTtyInit", rdram, ctx, runtime); -} - -void sceTtyRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceTtyRead", rdram, ctx, runtime); -} - -void sceTtyWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceTtyWrite", rdram, ctx, runtime); -} - -namespace -{ - bool readVuVec4f(uint8_t *rdram, uint32_t addr, float (&out)[4]) - { - const uint8_t *ptr = getConstMemPtr(rdram, addr); - if (!ptr) - { - return false; - } - std::memcpy(out, ptr, sizeof(out)); - return true; - } - - bool writeVuVec4f(uint8_t *rdram, uint32_t addr, const float (&in)[4]) - { - uint8_t *ptr = getMemPtr(rdram, addr); - if (!ptr) - { - return false; - } - std::memcpy(ptr, in, sizeof(in)); - return true; - } - - bool readVuVec4i(uint8_t *rdram, uint32_t addr, int32_t (&out)[4]) - { - const uint8_t *ptr = getConstMemPtr(rdram, addr); - if (!ptr) - { - return false; - } - std::memcpy(out, ptr, sizeof(out)); - return true; - } - - bool writeVuVec4i(uint8_t *rdram, uint32_t addr, const int32_t (&in)[4]) - { - uint8_t *ptr = getMemPtr(rdram, addr); - if (!ptr) - { - return false; - } - std::memcpy(ptr, in, sizeof(in)); - return true; - } -} - -void sceVpu0Reset(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void sceVu0AddVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t lhsAddr = getRegU32(ctx, 5); - const uint32_t rhsAddr = getRegU32(ctx, 6); - float lhs[4]{}, rhs[4]{}, out[4]{}; - if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = lhs[i] + rhs[i]; - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0ApplyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ApplyMatrix", rdram, ctx, runtime); -} - -void sceVu0CameraMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0CameraMatrix", rdram, ctx, runtime); -} - -void sceVu0ClampVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ClampVector", rdram, ctx, runtime); -} - -void sceVu0ClipAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ClipAll", rdram, ctx, runtime); -} - -void sceVu0ClipScreen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ClipScreen", rdram, ctx, runtime); -} - -void sceVu0ClipScreen3(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ClipScreen3", rdram, ctx, runtime); -} - -void sceVu0CopyMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0CopyMatrix", rdram, ctx, runtime); -} - -void sceVu0CopyVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0CopyVector", rdram, ctx, runtime); -} - -void sceVu0CopyVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0CopyVectorXYZ", rdram, ctx, runtime); -} - -void sceVu0DivVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0DivVector", rdram, ctx, runtime); -} - -void sceVu0DivVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0DivVectorXYZ", rdram, ctx, runtime); -} - -void sceVu0DropShadowMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0DropShadowMatrix", rdram, ctx, runtime); -} - -void sceVu0FTOI0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - float src[4]{}; - int32_t out[4]{}; - if (readVuVec4f(rdram, srcAddr, src)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = static_cast(src[i]); - } - (void)writeVuVec4i(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0FTOI4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - float src[4]{}; - int32_t out[4]{}; - if (readVuVec4f(rdram, srcAddr, src)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = static_cast(src[i] * 16.0f); - } - (void)writeVuVec4i(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0InnerProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t lhsAddr = getRegU32(ctx, 4); - const uint32_t rhsAddr = getRegU32(ctx, 5); - float lhs[4]{}, rhs[4]{}; - float dot = 0.0f; - if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) - { - dot = (lhs[0] * rhs[0]) + (lhs[1] * rhs[1]) + (lhs[2] * rhs[2]) + (lhs[3] * rhs[3]); - } - - if (ctx) - { - ctx->f[0] = dot; - } - uint32_t raw = 0u; - std::memcpy(&raw, &dot, sizeof(raw)); - setReturnU32(ctx, raw); -} - -void sceVu0InterVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0InterVector", rdram, ctx, runtime); -} - -void sceVu0InterVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0InterVectorXYZ", rdram, ctx, runtime); -} - -void sceVu0InversMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0InversMatrix", rdram, ctx, runtime); -} - -void sceVu0ITOF0Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - int32_t src[4]{}; - float out[4]{}; - if (readVuVec4i(rdram, srcAddr, src)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = static_cast(src[i]); - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0ITOF12Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - int32_t src[4]{}; - float out[4]{}; - if (readVuVec4i(rdram, srcAddr, src)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = static_cast(src[i]) / 4096.0f; - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0ITOF4Vector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - int32_t src[4]{}; - float out[4]{}; - if (readVuVec4i(rdram, srcAddr, src)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = static_cast(src[i]) / 16.0f; - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0LightColorMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0LightColorMatrix", rdram, ctx, runtime); -} - -void sceVu0MulMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0MulMatrix", rdram, ctx, runtime); -} - -void sceVu0MulVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0MulVector", rdram, ctx, runtime); -} - -void sceVu0Normalize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - float src[4]{}, out[4]{}; - if (readVuVec4f(rdram, srcAddr, src)) - { - const float len = std::sqrt((src[0] * src[0]) + (src[1] * src[1]) + (src[2] * src[2]) + (src[3] * src[3])); - if (len > 1.0e-6f) - { - const float invLen = 1.0f / len; - for (int i = 0; i < 4; ++i) - { - out[i] = src[i] * invLen; - } - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0NormalLightMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0NormalLightMatrix", rdram, ctx, runtime); -} - -void sceVu0OuterProduct(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t lhsAddr = getRegU32(ctx, 5); - const uint32_t rhsAddr = getRegU32(ctx, 6); - float lhs[4]{}, rhs[4]{}, out[4]{}; - if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) - { - out[0] = (lhs[1] * rhs[2]) - (lhs[2] * rhs[1]); - out[1] = (lhs[2] * rhs[0]) - (lhs[0] * rhs[2]); - out[2] = (lhs[0] * rhs[1]) - (lhs[1] * rhs[0]); - out[3] = 0.0f; - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0RotMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0RotMatrix", rdram, ctx, runtime); -} - -void sceVu0RotMatrixX(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0RotMatrixX", rdram, ctx, runtime); -} - -void sceVu0RotMatrixY(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0RotMatrixY", rdram, ctx, runtime); -} - -void sceVu0RotMatrixZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0RotMatrixZ", rdram, ctx, runtime); -} - -void sceVu0RotTransPers(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0RotTransPers", rdram, ctx, runtime); -} - -void sceVu0RotTransPersN(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0RotTransPersN", rdram, ctx, runtime); -} - -void sceVu0ScaleVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t srcAddr = getRegU32(ctx, 5); - float src[4]{}, out[4]{}; - float scale = ctx ? ctx->f[12] : 0.0f; - if (scale == 0.0f) - { - uint32_t raw = getRegU32(ctx, 6); - std::memcpy(&scale, &raw, sizeof(scale)); - if (scale == 0.0f) - { - scale = static_cast(getRegU32(ctx, 6)); - } - } - - if (readVuVec4f(rdram, srcAddr, src)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = src[i] * scale; - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0ScaleVectorXYZ(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0ScaleVectorXYZ", rdram, ctx, runtime); -} - -void sceVu0SubVector(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t dstAddr = getRegU32(ctx, 4); - const uint32_t lhsAddr = getRegU32(ctx, 5); - const uint32_t rhsAddr = getRegU32(ctx, 6); - float lhs[4]{}, rhs[4]{}, out[4]{}; - if (readVuVec4f(rdram, lhsAddr, lhs) && readVuVec4f(rdram, rhsAddr, rhs)) - { - for (int i = 0; i < 4; ++i) - { - out[i] = lhs[i] - rhs[i]; - } - (void)writeVuVec4f(rdram, dstAddr, out); - } - setReturnS32(ctx, 0); -} - -void sceVu0TransMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0TransMatrix", rdram, ctx, runtime); -} - -void sceVu0TransposeMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - TODO_NAMED("sceVu0TransposeMatrix", rdram, ctx, runtime); -} - -void sceVu0UnitMatrix(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - TODO_NAMED("sceVu0ViewScreenMatrix", rdram, ctx, runtime); -} - -void sceWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ps2_syscalls::fioWrite(rdram, ctx, runtime); -} - -void srand(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - std::srand(getRegU32(ctx, 4)); - setReturnS32(ctx, 0); -} - -void stat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t statAddr = getRegU32(ctx, 5); - uint8_t *statBuf = getMemPtr(rdram, statAddr); - if (!statBuf) - { - setReturnS32(ctx, -1); - return; - } - - // Minimal fake stat payload: zeroed structure indicates a valid, readable file. - std::memset(statBuf, 0, 128); - setReturnS32(ctx, 0); -} - -void strcasecmp(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t lhsAddr = getRegU32(ctx, 4); - const uint32_t rhsAddr = getRegU32(ctx, 5); - const std::string lhs = readPs2CStringBounded(rdram, runtime, lhsAddr, 1024); - const std::string rhs = readPs2CStringBounded(rdram, runtime, rhsAddr, 1024); - - const size_t n = std::min(lhs.size(), rhs.size()); - for (size_t i = 0; i < n; ++i) - { - const int a = std::tolower(static_cast(lhs[i])); - const int b = std::tolower(static_cast(rhs[i])); - if (a != b) - { - setReturnS32(ctx, a - b); - return; - } - } - - setReturnS32(ctx, static_cast(lhs.size()) - static_cast(rhs.size())); -} - -void vfprintf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *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) -{ - 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 - constexpr size_t kSafeVsprintfBytes = 256u; // Keep guest stack temporaries from being overwritten. - 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 (rendered.size() >= kSafeVsprintfBytes) - { - rendered.resize(kSafeVsprintfBytes - 1); - } - 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) -{ - ps2_syscalls::fioWrite(rdram, ctx, runtime); -} diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl deleted file mode 100644 index b7ebf030..00000000 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_ps2.inl +++ /dev/null @@ -1,150 +0,0 @@ -void sceCdRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t a0 = getRegU32(ctx, 4); // usually lbn - const uint32_t a1 = getRegU32(ctx, 5); // usually sector count - const uint32_t a2 = getRegU32(ctx, 6); // usually destination buffer - - struct CdReadArgs - { - uint32_t lbn = 0; - uint32_t sectors = 0; - uint32_t buf = 0; - const char *tag = ""; - }; - - auto clampReadBytes = [](uint32_t sectors, uint32_t offset) -> size_t - { - const uint64_t requested = static_cast(sectors) * static_cast(kCdSectorSize); - if (requested == 0) - { - return 0; - } - - const uint64_t maxBytes = static_cast(PS2_RAM_SIZE - offset); - const uint64_t clamped = std::min(requested, maxBytes); - return static_cast(clamped); - }; - - auto tryRead = [&](const CdReadArgs &args) -> bool - { - const uint32_t offset = args.buf & PS2_RAM_MASK; - const size_t bytes = clampReadBytes(args.sectors, offset); - if (bytes == 0) - { - return true; - } - - return readCdSectors(args.lbn, args.sectors, rdram + offset, bytes); - }; - - CdReadArgs selected{a0, a1, a2, "a0/a1/a2"}; - bool ok = tryRead(selected); - - if (!ok) - { - // Some game-side wrappers use a nonstandard register layout. - // If primary decode does not resolve to a known LBN, try safe alternatives. - constexpr uint32_t kMaxReasonableSectors = PS2_RAM_SIZE / kCdSectorSize; - if (!isResolvableCdLbn(selected.lbn)) - { - const std::array alternatives = { - CdReadArgs{a2, a1, a0, "a2/a1/a0"}, - CdReadArgs{a0, a2, a1, "a0/a2/a1"}, - CdReadArgs{a1, a0, a2, "a1/a0/a2"}, - CdReadArgs{a1, a2, a0, "a1/a2/a0"}, - CdReadArgs{a2, a0, a1, "a2/a0/a1"}}; - - for (const CdReadArgs &candidate : alternatives) - { - if (candidate.sectors > kMaxReasonableSectors) - { - continue; - } - if (!isResolvableCdLbn(candidate.lbn)) - { - continue; - } - - if (tryRead(candidate)) - { - static uint32_t recoverLogCount = 0; - if (recoverLogCount < 16) - { - std::cout << "[sceCdRead] recovered with alternate args " << candidate.tag - << " (pc=0x" << std::hex << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << " a0=0x" << a0 - << " a1=0x" << a1 - << " a2=0x" << a2 << std::dec << ")" << std::endl; - ++recoverLogCount; - } - selected = candidate; - ok = true; - break; - } - } - } - - if (!ok) - { - const uint32_t offset = a2 & PS2_RAM_MASK; - const size_t bytes = clampReadBytes(a1, offset); - if (bytes > 0) - { - std::memset(rdram + offset, 0, bytes); - } - - static uint32_t unresolvedLogCount = 0; - if (unresolvedLogCount < 32) - { - std::cerr << "[sceCdRead] unresolved request pc=0x" << std::hex << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << " a0=0x" << a0 - << " a1=0x" << a1 - << " a2=0x" << a2 << std::dec << std::endl; - ++unresolvedLogCount; - } - } - } - - if (ok) - { - g_cdStreamingLbn = selected.lbn + selected.sectors; - setReturnS32(ctx, 1); // command accepted/success - return; - } - - setReturnS32(ctx, 0); -} - -void sceCdSync(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); // 0 = completed/not busy -} - -void sceCdGetError(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, g_lastCdError); -} - -void builtin_set_imask(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub builtin_set_imask" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void InitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub InitThread" << std::endl; - ++logCount; - } - setReturnS32(ctx, 1); // success -} \ No newline at end of file diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl deleted file mode 100644 index 290ef80a..00000000 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl +++ /dev/null @@ -1,670 +0,0 @@ -void syRtcInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub syRtcInit" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -namespace -{ - constexpr uint32_t kCvSyMallocAddr = 0x002D9A70u; - - constexpr uint32_t kCvMallocMaxSizeAddr = 0x01140B60u; - constexpr uint32_t kCvMallocFreeSizeAddr = 0x01140B68u; - constexpr uint32_t kCvMallocHeadPtrAddr = 0x01140B70u; - constexpr uint32_t kCvMallocPoolAddr = 0x01140B80u; - constexpr uint32_t kCvMallocPoolSize = 0x00CCD000u; - - constexpr uint32_t kCvMallocUseSizeOff = 0x00u; - constexpr uint32_t kCvMallocTotalSizeOff = 0x04u; - constexpr uint32_t kCvMallocNextOff = 0x0Cu; - constexpr uint32_t kCvMallocHeaderSize = 0x40u; - constexpr uint32_t kCvMallocInitialFreeSize = kCvMallocPoolSize - kCvMallocHeaderSize; - - uint32_t cvReadU32(const uint8_t *rdram, uint32_t addr) - { - if (!rdram) - { - return 0u; - } - - const uint32_t offset = addr & PS2_RAM_MASK; - if (offset + sizeof(uint32_t) > PS2_RAM_SIZE) - { - return 0u; - } - - uint32_t value = 0u; - std::memcpy(&value, rdram + offset, sizeof(value)); - return value; - } - - void cvWriteU32(uint8_t *rdram, uint32_t addr, uint32_t value) - { - if (!rdram) - { - return; - } - - const uint32_t offset = addr & PS2_RAM_MASK; - if (offset + sizeof(uint32_t) > PS2_RAM_SIZE) - { - return; - } - - std::memcpy(rdram + offset, &value, sizeof(value)); - } -} - -void syFree(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub syFree" << std::endl; - ++logCount; - } - - const uint32_t guestAddr = getRegU32(ctx, 4); // $a0 - bool released = false; - - if (rdram && guestAddr != 0u) - { - uint32_t search = cvReadU32(rdram, kCvMallocHeadPtrAddr); - if (search < PS2_RAM_SIZE) - { - for (uint32_t guard = 0; guard < 0x100000u; ++guard) - { - const uint32_t next = cvReadU32(rdram, search + kCvMallocNextOff); - if (next == 0u) - { - break; - } - - if (guestAddr == (next + kCvMallocHeaderSize)) - { - const uint32_t searchTotal = cvReadU32(rdram, search + kCvMallocTotalSizeOff); - const uint32_t nextTotal = cvReadU32(rdram, next + kCvMallocTotalSizeOff); - const uint32_t nextUsed = cvReadU32(rdram, next + kCvMallocUseSizeOff); - const uint32_t nextNext = cvReadU32(rdram, next + kCvMallocNextOff); - const uint32_t freeSize = cvReadU32(rdram, kCvMallocFreeSizeAddr); - - cvWriteU32(rdram, search + kCvMallocTotalSizeOff, searchTotal + nextTotal + kCvMallocHeaderSize); - cvWriteU32(rdram, search + kCvMallocNextOff, nextNext); - cvWriteU32(rdram, kCvMallocFreeSizeAddr, freeSize + nextUsed + kCvMallocHeaderSize); - - released = true; - break; - } - - search = next; - if (search >= PS2_RAM_SIZE) - { - break; - } - } - } - } - - if (!released && 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 && runtime->hasFunction(kCvSyMallocAddr) && ctx->pc != kCvSyMallocAddr) - { - const uint32_t returnPc = getRegU32(ctx, 31); - PS2Runtime::RecompiledFunction syMallocFn = runtime->lookupFunction(kCvSyMallocAddr); - ctx->pc = kCvSyMallocAddr; - { - PS2Runtime::GuestExecutionScope guestExecution(runtime); - syMallocFn(rdram, ctx, runtime); - } - - if (ctx->pc == kCvSyMallocAddr || ctx->pc == 0u) - { - ctx->pc = returnPc; - } - - resultAddr = getRegU32(ctx, 2); - } - else 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; - if (logCount < 8) - { - std::cout << "ps2_stub InitSdcParameter" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void Ps2_pad_actuater(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub Ps2_pad_actuater" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void syMallocInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t heapBase = getRegU32(ctx, 4); // $a0 (ignored by original CV allocator) - const uint32_t heapSize = getRegU32(ctx, 5); // $a1 (ignored by original CV allocator) - - cvWriteU32(rdram, kCvMallocMaxSizeAddr, 0u); - cvWriteU32(rdram, kCvMallocFreeSizeAddr, kCvMallocInitialFreeSize); - cvWriteU32(rdram, kCvMallocHeadPtrAddr, kCvMallocPoolAddr); - - cvWriteU32(rdram, kCvMallocPoolAddr + kCvMallocUseSizeOff, 0u); - cvWriteU32(rdram, kCvMallocPoolAddr + kCvMallocTotalSizeOff, kCvMallocInitialFreeSize); - cvWriteU32(rdram, kCvMallocPoolAddr + kCvMallocNextOff, 0u); - - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub syMallocInit" - << " reqBase=0x" << std::hex << heapBase - << " reqSize=0x" << heapSize - << " pool=0x" << kCvMallocPoolAddr - << " free=0x" << kCvMallocInitialFreeSize - << std::dec << std::endl; - ++logCount; - } - - setReturnS32(ctx, 0); -} - -void syHwInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub syHwInit" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void syHwInit2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub syHwInit2" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void InitGdSystemEx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub InitGdSystemEx" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void pdInitPeripheral(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub pdInitPeripheral" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void pdGetPeripheral(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub pdGetPeripheral" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void Ps2SwapDBuff(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub Ps2SwapDBuff" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void InitReadKeyEx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub InitReadKeyEx" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void SetRepeatKeyTimer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub SetRepeatKeyTimer" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void StopFxProgram(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub StopFxProgram" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sndr_trans_func (noop)" << std::endl; - ++logCount; - } - - // small hack for code veronica - constexpr uint32_t kSndBusyAddrCv = 0x01E1E190; - constexpr uint32_t kSndBusyAddrLegacy = 0x01E0E170; - if (rdram) - { - uint32_t offset = kSndBusyAddrCv & PS2_RAM_MASK; - if (offset + sizeof(uint32_t) <= PS2_RAM_SIZE) - { - *reinterpret_cast(rdram + offset) = 0; - } - - offset = kSndBusyAddrLegacy & PS2_RAM_MASK; - if (offset + sizeof(uint32_t) <= PS2_RAM_SIZE) - { - *reinterpret_cast(rdram + offset) = 0; - } - } - - setReturnS32(ctx, 0); -} - -void sdDrvInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - constexpr uint32_t kSdrInitAddr = 0x2E9A20u; - - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sdDrvInit -> SdrInit_0x2e9a20" << std::endl; - ++logCount; - } - - if (!runtime || !ctx || !rdram || !runtime->hasFunction(kSdrInitAddr)) - { - setReturnS32(ctx, 0); - return; - } - - const uint32_t returnPc = getRegU32(ctx, 31); - PS2Runtime::RecompiledFunction sdrInit = runtime->lookupFunction(kSdrInitAddr); - ctx->pc = kSdrInitAddr; - { - PS2Runtime::GuestExecutionScope guestExecution(runtime); - sdrInit(rdram, ctx, runtime); - } - - if (ctx->pc == kSdrInitAddr || ctx->pc == 0u) - { - ctx->pc = returnPc; - } -} - -void ADXF_LoadPartitionNw(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub ADXF_LoadPartitionNw (noop)" << std::endl; - ++logCount; - } - // Return success to keep the ADX partition setup moving. - setReturnS32(ctx, 0); -} - -void sdSndStopAll(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sdSndStopAll" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void sdSysFinish(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub sdSysFinish" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void ADXT_Init(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub ADXT_Init" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void ADXT_SetNumRetry(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub ADXT_SetNumRetry" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -void cvFsSetDefDev(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - if (logCount < 8) - { - std::cout << "ps2_stub cvFsSetDefDev" << std::endl; - ++logCount; - } - setReturnS32(ctx, 0); -} - -namespace -{ - int32_t g_cvMcFileCursor = 0; - constexpr int32_t kCvMcFreeCapacityBytes = 0x01000000; - constexpr int32_t kCvMcSaveCapacityBytes = 0x00080000; - constexpr int32_t kCvMcConfigCapacityBytes = 0x00008000; - constexpr int32_t kCvMcIconCapacityBytes = 0x00004000; -} - -void mcCallMessageTypeSe(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcCheckReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCheckReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCheckWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCheckWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCreateConfigInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCreateFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCreateIconInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcCreateSaveFileInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcDispFileName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcDispFileNumber(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcDisplayFileSelectWindow(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcDisplaySelectFileInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcDisplaySelectFileInfoMesCount(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcDispWindowCurSol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcDispWindowFoundtion(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mceGetInfoApdx(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mceIntrReadFixAlign(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mceStorePwd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcGetConfigCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, kCvMcConfigCapacityBytes); -} - -void mcGetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, g_cvMcFileCursor); -} - -void mcGetFreeCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, kCvMcFreeCapacityBytes); -} - -void mcGetIconCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, kCvMcIconCapacityBytes); -} - -void mcGetIconFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, kCvMcIconCapacityBytes); -} - -void mcGetPortSelectDirInfo(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcGetSaveFileCapacitySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, kCvMcSaveCapacityBytes); -} - -void mcGetStringEnd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t strAddr = getRegU32(ctx, 4); - const std::string value = readPs2CStringBounded(rdram, runtime, strAddr, 1024); - setReturnU32(ctx, strAddr + static_cast(value.size())); -} - -void mcMoveFileSelectWindowCursor(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int32_t delta = static_cast(getRegU32(ctx, 5)); - g_cvMcFileCursor += delta; - g_cvMcFileCursor = std::clamp(g_cvMcFileCursor, -1, 15); - setReturnS32(ctx, 0); -} - -void mcNewCreateConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcNewCreateIcon(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcNewCreateSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcReadIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcReadStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcReadStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcSelectFileInfoInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_cvMcFileCursor = 0; - setReturnS32(ctx, 1); -} - -void mcSelectSaveFileCheck(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcSetFileSelectWindowCursol(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_cvMcFileCursor = static_cast(getRegU32(ctx, 5)); - g_cvMcFileCursor = std::clamp(g_cvMcFileCursor, -1, 15); - setReturnS32(ctx, 0); -} - -void mcSetFileSelectWindowCursolInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - g_cvMcFileCursor = 0; - setReturnS32(ctx, 0); -} - -void mcSetStringSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcSetTyepWriteMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 0); -} - -void mcWriteIconData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcWriteStartConfigFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} - -void mcWriteStartSaveFile(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, 1); -} diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl deleted file mode 100644 index f9e689a4..00000000 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_fileio.inl +++ /dev/null @@ -1,506 +0,0 @@ -static int allocatePs2Fd(FILE *file) -{ - if (!file) - return -1; - - std::lock_guard lock(g_fd_mutex); - int fd = g_nextFd++; - g_fileDescriptors[fd] = file; - return fd; -} - -static FILE *getHostFile(int ps2Fd) -{ - std::lock_guard lock(g_fd_mutex); - auto it = g_fileDescriptors.find(ps2Fd); - if (it != g_fileDescriptors.end()) - { - return it->second; - } - return nullptr; -} - -static void releasePs2Fd(int ps2Fd) -{ - std::lock_guard lock(g_fd_mutex); - g_fileDescriptors.erase(ps2Fd); -} - -struct VagAccumEntry -{ - std::vector data; - uint32_t firstBufAddr = 0; -}; -static std::unordered_map g_vagAccum; -static std::mutex g_vagAccumMutex; -static constexpr size_t kVagAccumMaxBytes = 16 * 1024 * 1024; - -static const char *translateFioMode(int ps2Flags) -{ - bool read = (ps2Flags & PS2_FIO_O_RDONLY) || (ps2Flags & PS2_FIO_O_RDWR); - bool write = (ps2Flags & PS2_FIO_O_WRONLY) || (ps2Flags & PS2_FIO_O_RDWR); - bool append = (ps2Flags & PS2_FIO_O_APPEND); - bool create = (ps2Flags & PS2_FIO_O_CREAT); - bool truncate = (ps2Flags & PS2_FIO_O_TRUNC); - - if (read && write) - { - if (create && truncate) - return "w+b"; - if (create) - return "a+b"; - return "r+b"; - } - else if (write) - { - if (append) - return "ab"; - if (create && truncate) - return "wb"; - if (create) - return "wx"; - return "r+b"; - } - else if (read) - { - return "rb"; - } - return "rb"; -} - -void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - int flags = (int)getRegU32(ctx, 5); // $a1 (PS2 FIO flags) - - const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - if (!ps2Path) - { - std::cerr << "fioOpen error: Invalid path address" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::string hostPath = translatePs2Path(ps2Path); - if (hostPath.empty()) - { - std::cerr << "fioOpen error: Failed to translate path '" << ps2Path << "'" << std::endl; - setReturnS32(ctx, -1); - return; - } - - const char *mode = translateFioMode(flags); - std::cout << "fioOpen: '" << hostPath << "' flags=0x" << std::hex << flags << std::dec << " mode='" << mode << "'" << std::endl; - - FILE *fp = ::fopen(hostPath.c_str(), mode); - if (!fp) - { - std::cerr << "fioOpen error: fopen failed for '" << hostPath << "': " << strerror(errno) << std::endl; - setReturnS32(ctx, -1); // e.g., -ENOENT, -EACCES - return; - } - - int ps2Fd = allocatePs2Fd(fp); - if (ps2Fd < 0) - { - std::cerr << "fioOpen error: Failed to allocate PS2 file descriptor" << std::endl; - ::fclose(fp); - setReturnS32(ctx, -1); // e.g., -EMFILE - return; - } - - // returns the PS2 file descriptor - setReturnS32(ctx, ps2Fd); -} - -void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int ps2Fd = (int)getRegU32(ctx, 4); - - FILE *fp = getHostFile(ps2Fd); - if (!fp) - { - std::cerr << "fioClose warning: Invalid PS2 file descriptor " << ps2Fd << std::endl; - setReturnS32(ctx, -1); - return; - } - - int ret = ::fclose(fp); - releasePs2Fd(ps2Fd); - - { - std::lock_guard lock(g_vagAccumMutex); - auto it = g_vagAccum.find(ps2Fd); - if (it != g_vagAccum.end()) - { - VagAccumEntry &e = it->second; - if (e.data.size() >= 48) - { - const uint32_t magic = (static_cast(e.data[0]) << 24) | - (static_cast(e.data[1]) << 16) | - (static_cast(e.data[2]) << 8) | - static_cast(e.data[3]); - const uint32_t magicLE = (static_cast(e.data[3]) << 24) | - (static_cast(e.data[2]) << 16) | - (static_cast(e.data[1]) << 8) | - static_cast(e.data[0]); - if (magic == 0x56414770u || magicLE == 0x56414770u) - { - if (runtime) - runtime->audioBackend().onVagTransferFromBuffer( - e.data.data(), static_cast(e.data.size()), - e.firstBufAddr ? e.firstBufAddr : 0u); - } - } - g_vagAccum.erase(it); - } - } - - setReturnS32(ctx, ret == 0 ? 0 : -1); -} - -void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int ps2Fd = (int)getRegU32(ctx, 4); // $a0 - uint32_t bufAddr = getRegU32(ctx, 5); // $a1 - size_t size = getRegU32(ctx, 6); // $a2 - - uint8_t *hostBuf = getMemPtr(rdram, bufAddr); - FILE *fp = getHostFile(ps2Fd); - - if (!hostBuf) - { - std::cerr << "fioRead error: Invalid buffer address for fd " << ps2Fd << std::endl; - setReturnS32(ctx, -1); // -EFAULT - return; - } - if (!fp) - { - std::cerr << "fioRead error: Invalid file descriptor " << ps2Fd << std::endl; - setReturnS32(ctx, -1); // -EBADF - return; - } - if (size == 0) - { - setReturnS32(ctx, 0); // Read 0 bytes - return; - } - - size_t bytesRead = 0; - { - std::lock_guard lock(g_sys_fd_mutex); - bytesRead = fread(hostBuf, 1, size, fp); - } - - if (bytesRead < size && ferror(fp)) - { - std::cerr << "fioRead error: fread failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; - clearerr(fp); - setReturnS32(ctx, -1); - return; - } - - { - std::lock_guard lock(g_vagAccumMutex); - auto it = g_vagAccum.find(ps2Fd); - if (it != g_vagAccum.end()) - { - VagAccumEntry &e = it->second; - if (e.data.size() + bytesRead <= kVagAccumMaxBytes) - e.data.insert(e.data.end(), hostBuf, hostBuf + bytesRead); - } - else if (bytesRead >= 4) - { - const uint32_t magic = (static_cast(hostBuf[0]) << 24) | - (static_cast(hostBuf[1]) << 16) | - (static_cast(hostBuf[2]) << 8) | - static_cast(hostBuf[3]); - const uint32_t magicLE = (static_cast(hostBuf[3]) << 24) | - (static_cast(hostBuf[2]) << 16) | - (static_cast(hostBuf[1]) << 8) | - static_cast(hostBuf[0]); - if (magic == 0x56414770u || magicLE == 0x56414770u) - { - VagAccumEntry &e = g_vagAccum[ps2Fd]; - e.firstBufAddr = bufAddr; - if (bytesRead <= kVagAccumMaxBytes) - e.data.assign(hostBuf, hostBuf + bytesRead); - } - } - } - - setReturnS32(ctx, (int32_t)bytesRead); -} - -void fioWrite(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int ps2Fd = (int)getRegU32(ctx, 4); // $a0 - uint32_t bufAddr = getRegU32(ctx, 5); // $a1 - size_t size = getRegU32(ctx, 6); // $a2 - - const uint8_t *hostBuf = getConstMemPtr(rdram, bufAddr); - if (!hostBuf) - { - setReturnS32(ctx, -1); - return; - } - - FILE *fp = getHostFile(ps2Fd); - if (!fp) - { - setReturnS32(ctx, -1); // -EFAULT - return; - } - - if (size == 0) - { - setReturnS32(ctx, 0); // Wrote 0 bytes - return; - } - - size_t bytesWritten = 0; - { - std::lock_guard lock(g_sys_fd_mutex); - bytesWritten = ::fwrite(hostBuf, 1, size, fp); - if (bytesWritten < size && ferror(fp)) - { - clearerr(fp); - setReturnS32(ctx, -1); // -EIO, -ENOSPC etc. - return; - } - } - - // returns number of bytes written - setReturnS32(ctx, (int32_t)bytesWritten); -} - -void fioLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int ps2Fd = (int)getRegU32(ctx, 4); // $a0 - int32_t offset = getRegU32(ctx, 5); // $a1 (PS2 seems to use 32-bit offset here commonly) - int whence = (int)getRegU32(ctx, 6); // $a2 (PS2 FIO_SEEK constants) - - FILE *fp = getHostFile(ps2Fd); - if (!fp) - { - std::cerr << "fioLseek error: Invalid file descriptor " << ps2Fd << std::endl; - setReturnS32(ctx, -1); // -EBADF - return; - } - - int hostWhence; - switch (whence) - { - case PS2_FIO_SEEK_SET: - hostWhence = SEEK_SET; - break; - case PS2_FIO_SEEK_CUR: - hostWhence = SEEK_CUR; - break; - case PS2_FIO_SEEK_END: - hostWhence = SEEK_END; - break; - default: - std::cerr << "fioLseek error: Invalid whence value " << whence << " for fd " << ps2Fd << std::endl; - setReturnS32(ctx, -1); // -EINVAL - return; - } - - if (::fseek(fp, static_cast(offset), hostWhence) != 0) - { - std::cerr << "fioLseek error: fseek failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl; - setReturnS32(ctx, -1); // Return error code - return; - } - - long newPos = ::ftell(fp); - if (newPos < 0) - { - std::cerr << "fioLseek error: ftell failed after fseek for fd " << ps2Fd << ": " << strerror(errno) << std::endl; - setReturnS32(ctx, -1); - } - else - { - if (newPos > 0xFFFFFFFFL) - { - std::cerr << "fioLseek warning: New position exceeds 32-bit for fd " << ps2Fd << std::endl; - setReturnS32(ctx, -1); - } - else - { - setReturnS32(ctx, (int32_t)newPos); - } - } -} - -void fioMkdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - // int mode = (int)getRegU32(ctx, 5); // $a1 - ignored on host - - const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - if (!ps2Path) - { - std::cerr << "fioMkdir error: Invalid path address" << std::endl; - setReturnS32(ctx, -1); // -EFAULT - return; - } - std::string hostPath = translatePs2Path(ps2Path); - if (hostPath.empty()) - { - std::cerr << "fioMkdir error: Failed to translate path '" << ps2Path << "'" << std::endl; - setReturnS32(ctx, -1); - return; - } - std::error_code ec; - bool success = std::filesystem::create_directory(hostPath, ec); - - if (!success && ec) - { - std::cerr << "fioMkdir error: create_directory failed for '" << hostPath - << "': " << ec.message() << std::endl; - setReturnS32(ctx, -1); - } - else - { - std::cout << "fioMkdir: Created directory '" << hostPath << "'" << std::endl; - setReturnS32(ctx, 0); // Success - } -} - -void fioChdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - if (!ps2Path) - { - std::cerr << "fioChdir error: Invalid path address" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::string hostPath = translatePs2Path(ps2Path); - if (hostPath.empty()) - { - std::cerr << "fioChdir error: Failed to translate path '" << ps2Path << "'" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::error_code ec; - std::filesystem::current_path(hostPath, ec); - - if (ec) - { - std::cerr << "fioChdir error: current_path failed for '" << hostPath - << "': " << ec.message() << std::endl; - setReturnS32(ctx, -1); - } - else - { - std::cout << "fioChdir: Changed directory to '" << hostPath << "'" << std::endl; - setReturnS32(ctx, 0); // Success - } -} - -void fioRmdir(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - if (!ps2Path) - { - std::cerr << "fioRmdir error: Invalid path address" << std::endl; - setReturnS32(ctx, -1); - return; - } - std::string hostPath = translatePs2Path(ps2Path); - if (hostPath.empty()) - { - std::cerr << "fioRmdir error: Failed to translate path '" << ps2Path << "'" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::error_code ec; - bool success = std::filesystem::remove(hostPath, ec); - - if (!success || ec) - { - std::cerr << "fioRmdir error: remove failed for '" << hostPath - << "': " << ec.message() << std::endl; - setReturnS32(ctx, -1); - } - else - { - std::cout << "fioRmdir: Removed directory '" << hostPath << "'" << std::endl; - setReturnS32(ctx, 0); // Success - } -} - -void fioGetstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - // we wont implement this for now. - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - uint32_t statBufAddr = getRegU32(ctx, 5); // $a1 - - const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - uint8_t *ps2StatBuf = getMemPtr(rdram, statBufAddr); - - if (!ps2Path) - { - std::cerr << "fioGetstat error: Invalid path addr" << std::endl; - setReturnS32(ctx, -1); - return; - } - if (!ps2StatBuf) - { - std::cerr << "fioGetstat error: Invalid buffer addr" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::string hostPath = translatePs2Path(ps2Path); - if (hostPath.empty()) - { - std::cerr << "fioGetstat error: Bad path translate" << std::endl; - setReturnS32(ctx, -1); - return; - } - - setReturnS32(ctx, -1); -} - -void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - const char *ps2Path = reinterpret_cast(getConstMemPtr(rdram, pathAddr)); - if (!ps2Path) - { - std::cerr << "fioRemove error: Invalid path" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::string hostPath = translatePs2Path(ps2Path); - if (hostPath.empty()) - { - std::cerr << "fioRemove error: Path translate fail" << std::endl; - setReturnS32(ctx, -1); - return; - } - - std::error_code ec; - bool success = std::filesystem::remove(hostPath, ec); - - if (!success || ec) - { - std::cerr << "fioRemove error: remove failed for '" << hostPath - << "': " << ec.message() << std::endl; - setReturnS32(ctx, -1); - } - else - { - std::cout << "fioRemove: Removed file '" << hostPath << "'" << std::endl; - setReturnS32(ctx, 0); // Success - } -} \ No newline at end of file diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl deleted file mode 100644 index 56bb2447..00000000 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl +++ /dev/null @@ -1,902 +0,0 @@ -static bool looksLikeGuestPointerOrNull(uint32_t value) -{ - if (value == 0u) - { - return true; - } - const uint32_t normalized = value & 0x1FFFFFFFu; - return normalized < PS2_RAM_SIZE; -} - -static bool readGuestU32Safe(const uint8_t *rdram, uint32_t addr, uint32_t &out) -{ - const uint8_t *b0 = getConstMemPtr(rdram, addr + 0u); - const uint8_t *b1 = getConstMemPtr(rdram, addr + 1u); - const uint8_t *b2 = getConstMemPtr(rdram, addr + 2u); - const uint8_t *b3 = getConstMemPtr(rdram, addr + 3u); - if (!b0 || !b1 || !b2 || !b3) - { - out = 0u; - return false; - } - - out = static_cast(*b0) | - (static_cast(*b1) << 8) | - (static_cast(*b2) << 16) | - (static_cast(*b3) << 24); - return true; -} - -struct DecodedSemaParams -{ - int init = 0; - int max = 1; - uint32_t attr = 0; - uint32_t option = 0; -}; - -static DecodedSemaParams decodeCreateSemaParams(const uint32_t *param, uint32_t availableWords) -{ - DecodedSemaParams out{}; - if (!param || availableWords == 0u) - { - return out; - } - - // EE layout (kernel.h): - // [0]=count [1]=max_count [2]=init_count [3]=wait_threads [4]=attr [5]=option - const bool hasEeLayout = availableWords >= 3u; - const int eeMax = hasEeLayout ? static_cast(param[1]) : 1; - const int eeInit = hasEeLayout ? static_cast(param[2]) : 0; - const uint32_t eeAttr = (availableWords >= 5u) ? param[4] : 0u; - const uint32_t eeOption = (availableWords >= 6u) ? param[5] : 0u; - - // Legacy layout (IOP-style): - // [0]=attr [1]=option [2]=init [3]=max - const bool hasLegacyLayout = availableWords >= 4u; - const int legacyMax = hasLegacyLayout ? static_cast(param[3]) : 1; - const int legacyInit = hasLegacyLayout ? static_cast(param[2]) : 0; - const uint32_t legacyAttr = hasLegacyLayout ? param[0] : 0u; - const uint32_t legacyOption = hasLegacyLayout ? param[1] : 0u; - - auto countLooksPlausible = [](int value) -> bool - { - return value > 0 && value <= 0x10000; - }; - - bool useLegacyLayout = hasLegacyLayout && !hasEeLayout; - if (hasLegacyLayout && hasEeLayout && countLooksPlausible(legacyMax) && !countLooksPlausible(eeMax)) - { - useLegacyLayout = true; - } - else if (hasLegacyLayout && hasEeLayout && countLooksPlausible(legacyMax) && countLooksPlausible(eeMax)) - { - // If both max values look valid, prefer the layout whose option field - // looks like a pointer/NULL payload. - const bool eeOptionLooksValid = looksLikeGuestPointerOrNull(eeOption); - const bool legacyOptionLooksValid = looksLikeGuestPointerOrNull(legacyOption); - if (!eeOptionLooksValid && legacyOptionLooksValid) - { - useLegacyLayout = true; - } - } - - if (useLegacyLayout && hasLegacyLayout) - { - out.max = legacyMax; - out.init = legacyInit; - out.attr = legacyAttr; - out.option = legacyOption; - } - else - { - if (!hasEeLayout) - { - return out; - } - out.max = eeMax; - out.init = eeInit; - out.attr = eeAttr; - out.option = eeOption; - } - - return out; -} - -void CreateSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - if (paramAddr == 0u) - { - setReturnS32(ctx, KE_ERROR); - return; - } - - uint32_t rawParams[6] = {}; - uint32_t availableWords = 0u; - for (uint32_t i = 0; i < 6u; ++i) - { - if (!readGuestU32Safe(rdram, paramAddr + (i * 4u), rawParams[i])) - { - break; - } - availableWords = i + 1u; - } - - if (availableWords < 3u) - { - setReturnS32(ctx, KE_ERROR); - return; - } - - const DecodedSemaParams decoded = decodeCreateSemaParams(rawParams, availableWords); - int init = decoded.init; - int max = decoded.max; - uint32_t attr = decoded.attr; - uint32_t option = decoded.option; - - if (max <= 0) - { - max = 1; - } - if (init < 0) - { - init = 0; - } - if (init > max) - { - init = max; - } - - int id = 0; - auto info = std::make_shared(); - info->count = init; - info->maxCount = max; - info->initCount = init; - info->attr = attr; - info->option = option; - - { - std::lock_guard lock(g_sema_map_mutex); - for (int attempts = 0; attempts < 0x7FFF; ++attempts) - { - if (g_nextSemaId <= 0) - { - g_nextSemaId = 1; - } - - const int candidate = g_nextSemaId++; - if (candidate <= 0) - { - continue; - } - - if (g_semas.find(candidate) == g_semas.end()) - { - id = candidate; - break; - } - } - - if (id <= 0) - { - setReturnS32(ctx, KE_ERROR); - return; - } - - g_semas.emplace(id, info); - } - std::cout << "[CreateSema] id=" << id << " init=" << init << " max=" << max << std::endl; - setReturnS32(ctx, id); -} - -void DeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int sid = static_cast(getRegU32(ctx, 4)); - std::shared_ptr sema; - - { - std::lock_guard lock(g_sema_map_mutex); - auto it = g_semas.find(sid); - if (it == g_semas.end()) - { - setReturnS32(ctx, KE_UNKNOWN_SEMID); - return; - } - sema = it->second; - g_semas.erase(it); - } - - { - std::lock_guard lock(sema->m); - sema->deleted = true; - } - sema->cv.notify_all(); - - setReturnS32(ctx, KE_OK); -} - -void iDeleteSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - DeleteSema(rdram, ctx, runtime); -} - -void SignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int sid = static_cast(getRegU32(ctx, 4)); - auto sema = lookupSemaInfo(sid); - if (!sema) - { - setReturnS32(ctx, KE_UNKNOWN_SEMID); - return; - } - - int ret = KE_OK; - int beforeCount = 0; - int afterCount = 0; - { - std::lock_guard lock(sema->m); - beforeCount = sema->count; - if (sema->count >= sema->maxCount) - { - ret = KE_SEMA_OVF; - } - else - { - sema->count++; - sema->cv.notify_one(); - } - afterCount = sema->count; - } - - static std::atomic s_signalSemaLogs{0}; - const uint32_t sigLog = s_signalSemaLogs.fetch_add(1, std::memory_order_relaxed); - if (sigLog < 256u) - { - std::cout << "[SignalSema] tid=" << g_currentThreadId - << " sid=" << sid - << " count=" << beforeCount << "->" << afterCount - << " ret=" << ret - << std::endl; - } - - setReturnS32(ctx, ret); -} - -void iSignalSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - SignalSema(rdram, ctx, runtime); -} - -void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int sid = static_cast(getRegU32(ctx, 4)); - auto sema = lookupSemaInfo(sid); - if (!sema) - { - 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) - { - static std::atomic s_waitSemaBlockLogs{0}; - const uint32_t blockLog = s_waitSemaBlockLogs.fetch_add(1, std::memory_order_relaxed); - if (blockLog < 256u) - { - std::cout << "[WaitSema:block] tid=" << g_currentThreadId - << " sid=" << sid - << " pc=0x" << std::hex << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << std::dec - << std::endl; - } - - if (info) - { - std::lock_guard tLock(info->m); - info->status = (info->suspendCount > 0) ? THS_WAITSUSPEND : THS_WAIT; - info->waitType = TSW_SEMA; - info->waitId = sid; - info->forceRelease = false; - } - - sema->waiters++; - { - PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); - sema->cv.wait(lock, [&]() - { - bool forced = info ? info->forceRelease.load() : false; - bool terminated = info ? info->terminated.load() : false; - return sema->count > 0 || sema->deleted || forced || terminated; // - }); - } - sema->waiters--; - if (sema->deleted) - { - ret = KE_WAIT_DELETE; - } - - if (info) - { - std::lock_guard tLock(info->m); - info->status = (info->suspendCount > 0) ? THS_SUSPEND : THS_RUN; - info->waitType = TSW_NONE; - info->waitId = 0; - if (info->forceRelease) - { - info->forceRelease = false; - ret = KE_RELEASE_WAIT; - } - } - - if (info && info->terminated.load()) - { - throw ThreadExitException(); - } - } - - if (ret == 0 && sema->count > 0) - { - sema->count--; - } - - static std::atomic s_waitSemaWakeLogs{0}; - const uint32_t wakeLog = s_waitSemaWakeLogs.fetch_add(1, std::memory_order_relaxed); - if (wakeLog < 256u) - { - std::cout << "[WaitSema:wake] tid=" << g_currentThreadId - << " sid=" << sid - << " ret=" << ret - << " count=" << sema->count - << std::endl; - } - lock.unlock(); - waitWhileSuspended(info, runtime); - setReturnS32(ctx, ret); -} - -void PollSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int sid = static_cast(getRegU32(ctx, 4)); - auto sema = lookupSemaInfo(sid); - if (!sema) - { - setReturnS32(ctx, KE_UNKNOWN_SEMID); - return; - } - - 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) -{ - PollSema(rdram, ctx, runtime); -} - -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, KE_UNKNOWN_SEMID); - return; - } - - ee_sema_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); - if (!status) - { - setReturnS32(ctx, KE_ERROR); - 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, KE_OK); -} - -void iReferSemaStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - 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) -{ - 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) -{ - 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; - } - - uint32_t newBits = 0u; - { - std::lock_guard lock(info->m); - info->bits |= bits; - newBits = info->bits; - } - - static std::atomic s_setEventFlagLogs{0}; - const uint32_t setLog = s_setEventFlagLogs.fetch_add(1, std::memory_order_relaxed); - if (setLog < 256u) - { - std::cout << "[SetEventFlag] tid=" << g_currentThreadId - << " eid=" << eid - << " bits=0x" << std::hex << bits - << " newBits=0x" << newBits - << std::dec << std::endl; - } - info->cv.notify_all(); - setReturnS32(ctx, 0); -} - -void iSetEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - SetEventFlag(rdram, ctx, runtime); -} - -void ClearEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - ClearEventFlag(rdram, ctx, runtime); -} - -void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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()) - { - static std::atomic s_waitEventBlockLogs{0}; - const uint32_t evBlockLog = s_waitEventBlockLogs.fetch_add(1, std::memory_order_relaxed); - if (evBlockLog < 256u) - { - std::cout << "[WaitEventFlag:block] tid=" << g_currentThreadId - << " eid=" << eid - << " waitBits=0x" << std::hex << waitBits - << " mode=0x" << mode - << " bits=0x" << info->bits - << " pc=0x" << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << std::dec - << std::endl; - } - - if (tInfo) - { - std::lock_guard tLock(tInfo->m); - tInfo->status = THS_WAIT; - tInfo->waitType = TSW_EVENT; - tInfo->waitId = eid; - tInfo->forceRelease = false; - } - - info->waiters++; - { - PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); - 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) - { - if (resBitsPtr) - { - *resBitsPtr = info->bits; - } - - if (mode & WEF_CLEAR_ALL) - { - info->bits = 0; - } - else if (mode & WEF_CLEAR) - { - info->bits &= ~waitBits; - } - } - - static std::atomic s_waitEventWakeLogs{0}; - const uint32_t evWakeLog = s_waitEventWakeLogs.fetch_add(1, std::memory_order_relaxed); - if (evWakeLog < 256u) - { - std::cout << "[WaitEventFlag:wake] tid=" << g_currentThreadId - << " eid=" << eid - << " ret=" << ret - << " bits=0x" << std::hex << info->bits - << std::dec - << std::endl; - } - - lock.unlock(); - waitWhileSuspended(tInfo, runtime); - setReturnS32(ctx, ret); -} - -void PollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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_ALL) - { - info->bits = 0; - } - else if (mode & WEF_CLEAR) - { - info->bits &= ~waitBits; - } - - setReturnS32(ctx, KE_OK); -} - -void iPollEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - PollEventFlag(rdram, ctx, runtime); -} - -void ReferEventFlagStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - ReferEventFlagStatus(rdram, ctx, runtime); -} - -void SetAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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] ticks=" << ticks - << " handler=0x" << std::hex << handler - << " arg=0x" << arg << std::dec << std::endl; - ++logCount; - } - - if (!runtime || !handler || !runtime->hasFunction(handler)) - { - setReturnS32(ctx, KE_ERROR); - return; - } - - 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) -{ - SetAlarm(rdram, ctx, runtime); -} - -void CancelAlarm(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - CancelAlarm(rdram, ctx, runtime); -} diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl deleted file mode 100644 index cc3b7ea2..00000000 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl +++ /dev/null @@ -1,593 +0,0 @@ -namespace -{ - constexpr uint32_t kIntcVblankStart = 2u; - constexpr uint32_t kIntcVblankEnd = 3u; - constexpr auto kVblankPeriod = std::chrono::microseconds(16667); - constexpr int kMaxCatchupTicks = 4; - - struct VSyncFlagRegistration - { - uint32_t flagAddr = 0; - uint32_t tickAddr = 0; - }; - - static std::mutex g_irq_handler_mutex; - static std::mutex g_irq_worker_mutex; - static std::condition_variable g_irq_worker_cv; - static std::mutex g_vsync_flag_mutex; - static std::condition_variable g_vsync_cv; - static std::atomic g_irq_worker_stop{false}; - static std::atomic g_irq_worker_running{false}; - static uint32_t g_enabled_intc_mask = 0xFFFFFFFFu; - static uint32_t g_enabled_dmac_mask = 0xFFFFFFFFu; - static uint64_t g_vsync_tick_counter = 0u; - static VSyncFlagRegistration g_vsync_registration{}; -} - -static void writeGuestU32NoThrow(uint8_t *rdram, uint32_t addr, uint32_t value) -{ - if (addr == 0u) - { - return; - } - - uint8_t *dst = getMemPtr(rdram, addr); - if (!dst) - { - return; - } - std::memcpy(dst, &value, sizeof(value)); -} - -static void writeGuestU64NoThrow(uint8_t *rdram, uint32_t addr, uint64_t value) -{ - if (addr == 0u) - { - return; - } - - uint8_t *dst = getMemPtr(rdram, addr); - if (!dst) - { - return; - } - std::memcpy(dst, &value, sizeof(value)); -} - -static uint32_t getAsyncHandlerStackTop(PS2Runtime *runtime) -{ - constexpr uint32_t kAsyncHandlerStackSize = 0x4000u; - thread_local PS2Runtime *s_cachedRuntime = nullptr; - thread_local uint32_t s_cachedStackTop = 0u; - - if (runtime == nullptr) - { - return PS2_RAM_SIZE - 0x10u; - } - - if (s_cachedRuntime != runtime || s_cachedStackTop == 0u) - { - s_cachedRuntime = runtime; - s_cachedStackTop = runtime->reserveAsyncCallbackStack(kAsyncHandlerStackSize, 16u); - } - - return (s_cachedStackTop != 0u) ? s_cachedStackTop : (PS2_RAM_SIZE - 0x10u); -} - -static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause) -{ - if (!rdram || !runtime) - { - return; - } - - std::vector handlers; - { - std::lock_guard lock(g_irq_handler_mutex); - if (cause < 32u && (g_enabled_intc_mask & (1u << cause)) == 0u) - { - return; - } - - handlers.reserve(g_intcHandlers.size()); - for (const auto &[id, info] : g_intcHandlers) - { - (void)id; - if (!info.enabled) - { - continue; - } - if (info.cause != cause) - { - continue; - } - if (info.handler == 0u) - { - continue; - } - handlers.push_back(info); - } - std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) - { return a.order < b.order; }); - } - - for (const IrqHandlerInfo &info : handlers) - { - if (!runtime->hasFunction(info.handler)) - { - continue; - } - - try - { - R5900Context irqCtx{}; - SET_GPR_U32(&irqCtx, 28, info.gp); - SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime)); - SET_GPR_U32(&irqCtx, 31, 0u); - SET_GPR_U32(&irqCtx, 4, cause); - SET_GPR_U32(&irqCtx, 5, info.arg); - SET_GPR_U32(&irqCtx, 6, 0u); - SET_GPR_U32(&irqCtx, 7, 0u); - irqCtx.pc = info.handler; - - while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested()) - { - PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc); - if (!step) - { - break; - } - // Interrupt handlers must be able to preempt a guest thread that is - // spinning on interrupt-produced state, such as a vblank counter. - step(rdram, &irqCtx, runtime); - } - } - catch (const ThreadExitException &) - { - } - catch (const std::exception &e) - { - static uint32_t warnCount = 0; - if (warnCount < 8u) - { - std::cerr << "[INTC] handler 0x" << std::hex << info.handler - << " threw exception: " << e.what() << std::dec << std::endl; - ++warnCount; - } - } - } -} - -void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause) -{ - if (!rdram || !runtime) - { - return; - } - - std::vector handlers; - { - std::lock_guard lock(g_irq_handler_mutex); - if (cause < 32u && (g_enabled_dmac_mask & (1u << cause)) == 0u) - { - return; - } - - handlers.reserve(g_dmacHandlers.size()); - for (const auto &[id, info] : g_dmacHandlers) - { - (void)id; - if (!info.enabled) - { - continue; - } - if (info.cause != cause) - { - continue; - } - if (info.handler == 0u) - { - continue; - } - handlers.push_back(info); - } - std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) - { return a.order < b.order; }); - } - - for (const IrqHandlerInfo &info : handlers) - { - if (!runtime->hasFunction(info.handler)) - { - continue; - } - - try - { - R5900Context irqCtx{}; - SET_GPR_U32(&irqCtx, 28, info.gp); - SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime)); - SET_GPR_U32(&irqCtx, 31, 0u); - SET_GPR_U32(&irqCtx, 4, cause); - SET_GPR_U32(&irqCtx, 5, info.arg); - SET_GPR_U32(&irqCtx, 6, 0u); - SET_GPR_U32(&irqCtx, 7, 0u); - irqCtx.pc = info.handler; - - while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested()) - { - PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc); - if (!step) - { - break; - } - step(rdram, &irqCtx, runtime); - } - } - catch (const ThreadExitException &) - { - } - catch (const std::exception &e) - { - static uint32_t warnCount = 0; - if (warnCount < 8u) - { - std::cerr << "[DMAC] handler 0x" << std::hex << info.handler - << " threw exception: " << e.what() << std::dec << std::endl; - ++warnCount; - } - } - } -} - -static uint64_t signalVSyncFlag(uint8_t *rdram) -{ - VSyncFlagRegistration reg{}; - uint64_t tickValue = 0u; - { - std::lock_guard lock(g_vsync_flag_mutex); - reg = g_vsync_registration; - tickValue = ++g_vsync_tick_counter; - } - - g_vsync_cv.notify_all(); - - if (reg.flagAddr != 0u) - { - writeGuestU32NoThrow(rdram, reg.flagAddr, 1u); - } - if (reg.tickAddr != 0u) - { - writeGuestU64NoThrow(rdram, reg.tickAddr, tickValue); - } - return tickValue; -} - -static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime) -{ - g_currentThreadId = -1; - - using clock = std::chrono::steady_clock; - auto nextTick = clock::now() + kVblankPeriod; - - while (runtime != nullptr && !runtime->isStopRequested()) - { - { - std::unique_lock lock(g_irq_worker_mutex); - if (g_irq_worker_cv.wait_until(lock, nextTick, []() - { return g_irq_worker_stop.load(std::memory_order_acquire); })) - { - break; - } - } - - const auto now = clock::now(); - int ticksToProcess = 0; - while (now >= nextTick && ticksToProcess < kMaxCatchupTicks) - { - ++ticksToProcess; - nextTick += kVblankPeriod; - } - if (ticksToProcess == 0) - { - continue; - } - - for (int i = 0; i < ticksToProcess; ++i) - { - const uint64_t tickValue = signalVSyncFlag(rdram); - ps2_stubs::dispatchGsSyncVCallback(rdram, runtime, tickValue); - dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart); - std::this_thread::sleep_for(std::chrono::microseconds(500)); - dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd); - } - } - - g_irq_worker_running.store(false, std::memory_order_release); - g_irq_worker_cv.notify_all(); -} - -static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime) -{ - if (!rdram || !runtime) - { - return; - } - - std::lock_guard lock(g_irq_worker_mutex); - if (g_irq_worker_running.load(std::memory_order_acquire)) - { - return; - } - - g_irq_worker_stop.store(false, std::memory_order_release); - g_irq_worker_running.store(true, std::memory_order_release); - try - { - std::thread(interruptWorkerMain, rdram, runtime).detach(); - } - catch (...) - { - g_irq_worker_running.store(false, std::memory_order_release); - } -} - -void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime) -{ - ensureInterruptWorkerRunning(rdram, runtime); -} - -uint64_t GetCurrentVSyncTick() -{ - std::lock_guard lock(g_vsync_flag_mutex); - return g_vsync_tick_counter; -} - -void stopInterruptWorker() -{ - g_irq_worker_stop.store(true, std::memory_order_release); - g_irq_worker_cv.notify_all(); - std::unique_lock lock(g_irq_worker_mutex); - g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []() - { return !g_irq_worker_running.load(std::memory_order_acquire); }); - g_vsync_cv.notify_all(); -} - -uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime) -{ - ensureInterruptWorkerRunning(rdram, runtime); - std::unique_lock lock(g_vsync_flag_mutex); - uint64_t current = g_vsync_tick_counter; - { - PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); - g_vsync_cv.wait(lock, [current, runtime]() - { return g_vsync_tick_counter > current || (runtime != nullptr && runtime->isStopRequested()); }); - } - return g_vsync_tick_counter; -} - -void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime) -{ - (void)WaitForNextVSyncTick(rdram, runtime); -} - -void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t flagAddr = getRegU32(ctx, 4); - const uint32_t tickAddr = getRegU32(ctx, 5); - - { - std::lock_guard lock(g_vsync_flag_mutex); - g_vsync_registration.flagAddr = flagAddr; - g_vsync_registration.tickAddr = tickAddr; - } - - writeGuestU32NoThrow(rdram, flagAddr, 0u); - writeGuestU64NoThrow(rdram, tickAddr, 0u); - ensureInterruptWorkerRunning(rdram, runtime); - setReturnS32(ctx, KE_OK); -} - -void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cause = getRegU32(ctx, 4); - if (cause < 32u) - { - std::lock_guard lock(g_irq_handler_mutex); - g_enabled_intc_mask |= (1u << cause); - } - setReturnS32(ctx, KE_OK); -} - -void iEnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - EnableIntc(rdram, ctx, runtime); -} - -void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cause = getRegU32(ctx, 4); - if (cause < 32u) - { - std::lock_guard lock(g_irq_handler_mutex); - g_enabled_intc_mask &= ~(1u << cause); - } - setReturnS32(ctx, KE_OK); -} - -void iDisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - DisableIntc(rdram, ctx, runtime); -} - -void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - IrqHandlerInfo info{}; - info.cause = getRegU32(ctx, 4); - info.handler = getRegU32(ctx, 5); - uint32_t next = getRegU32(ctx, 6); - info.arg = getRegU32(ctx, 7); - info.gp = getRegU32(ctx, 28); - info.sp = getRegU32(ctx, 29); - info.enabled = true; - - int handlerId = 0; - { - std::lock_guard lock(g_irq_handler_mutex); - info.order = (next == 0) ? --g_intc_head_order : ++g_intc_tail_order; - handlerId = g_nextIntcHandlerId++; - info.id = handlerId; - g_intcHandlers[handlerId] = info; - } - - ensureInterruptWorkerRunning(rdram, runtime); - setReturnS32(ctx, handlerId); -} - -void AddIntcHandler2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - AddIntcHandler(rdram, ctx, runtime); -} - -void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cause = getRegU32(ctx, 4); - const int handlerId = static_cast(getRegU32(ctx, 5)); - if (handlerId > 0) - { - std::lock_guard lock(g_irq_handler_mutex); - auto it = g_intcHandlers.find(handlerId); - if (it != g_intcHandlers.end() && it->second.cause == cause) - { - g_intcHandlers.erase(it); - } - } - setReturnS32(ctx, KE_OK); -} - -void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - IrqHandlerInfo info{}; - info.cause = getRegU32(ctx, 4); - info.handler = getRegU32(ctx, 5); - uint32_t next = getRegU32(ctx, 6); - info.arg = getRegU32(ctx, 7); - info.gp = getRegU32(ctx, 28); - info.sp = getRegU32(ctx, 29); - info.enabled = true; - - int handlerId = 0; - { - std::lock_guard lock(g_irq_handler_mutex); - info.order = (next == 0) ? --g_dmac_head_order : ++g_dmac_tail_order; - handlerId = g_nextDmacHandlerId++; - info.id = handlerId; - g_dmacHandlers[handlerId] = info; - } - setReturnS32(ctx, handlerId); -} - -void AddDmacHandler2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - AddDmacHandler(rdram, ctx, runtime); -} - -void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cause = getRegU32(ctx, 4); - const int handlerId = static_cast(getRegU32(ctx, 5)); - if (handlerId > 0) - { - std::lock_guard lock(g_irq_handler_mutex); - auto it = g_dmacHandlers.find(handlerId); - if (it != g_dmacHandlers.end() && it->second.cause == cause) - { - g_dmacHandlers.erase(it); - } - } - setReturnS32(ctx, KE_OK); -} - -void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int handlerId = static_cast(getRegU32(ctx, 5)); - { - std::lock_guard lock(g_irq_handler_mutex); - if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) - { - it->second.enabled = true; - } - } - setReturnS32(ctx, KE_OK); -} - -void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int handlerId = static_cast(getRegU32(ctx, 5)); - { - std::lock_guard lock(g_irq_handler_mutex); - if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end()) - { - it->second.enabled = false; - } - } - setReturnS32(ctx, KE_OK); -} - -void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int handlerId = static_cast(getRegU32(ctx, 5)); - { - std::lock_guard lock(g_irq_handler_mutex); - if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) - { - it->second.enabled = true; - } - } - setReturnS32(ctx, KE_OK); -} - -void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const int handlerId = static_cast(getRegU32(ctx, 5)); - { - std::lock_guard lock(g_irq_handler_mutex); - if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end()) - { - it->second.enabled = false; - } - } - setReturnS32(ctx, KE_OK); -} - -void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cause = getRegU32(ctx, 4); - if (cause < 32u) - { - std::lock_guard lock(g_irq_handler_mutex); - g_enabled_dmac_mask |= (1u << cause); - } - setReturnS32(ctx, KE_OK); -} - -void iEnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - EnableDmac(rdram, ctx, runtime); -} - -void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t cause = getRegU32(ctx, 4); - if (cause < 32u) - { - std::lock_guard lock(g_irq_handler_mutex); - g_enabled_dmac_mask &= ~(1u << cause); - } - setReturnS32(ctx, KE_OK); -} - -void iDisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - DisableDmac(rdram, ctx, runtime); -} diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl deleted file mode 100644 index 6524667e..00000000 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_rpc.inl +++ /dev/null @@ -1,1320 +0,0 @@ -void SifStopModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - const std::string modulePath = readGuestCStringBounded(rdram, pathAddr, kMaxSifModulePathBytes); - if (modulePath.empty()) - { - setReturnS32(ctx, -1); - return; - } - - 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::scoped_lock lock(g_rpc_mutex, g_dtx_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; - 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); -} - -void SifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t clientPtr = getRegU32(ctx, 4); - uint32_t rpcId = getRegU32(ctx, 5); - uint32_t mode = getRegU32(ctx, 6); - - t_SifRpcClientData *client = reinterpret_cast(getMemPtr(rdram, clientPtr)); - - if (!client) - { - setReturnS32(ctx, -1); - return; - } - - 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::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); -} - -void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - std::lock_guard rpcCallLock(g_sif_call_rpc_mutex); - - uint32_t clientPtr = getRegU32(ctx, 4); - 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; - - // Decode both extended-reg convention (EE default) and standard O32 stack convention, - // picking REG whenever plausible, to avoid zero-collision on the stack. - uint32_t sp = getRegU32(ctx, 29); - - uint32_t sendSizeReg = getRegU32(ctx, 8); - uint32_t recvBufReg = getRegU32(ctx, 9); - uint32_t recvSizeReg = getRegU32(ctx, 10); - uint32_t endFuncReg = getRegU32(ctx, 11); - uint32_t endParamReg = 0; - (void)readStackU32(rdram, sp, 0x0, endParamReg); - - uint32_t sendSizeStk = 0; - uint32_t recvBufStk = 0; - uint32_t recvSizeStk = 0; - uint32_t endFuncStk = 0; - uint32_t endParamStk = 0; - (void)readStackU32(rdram, sp, 0x10, sendSizeStk); - (void)readStackU32(rdram, sp, 0x14, recvBufStk); - (void)readStackU32(rdram, sp, 0x18, recvSizeStk); - (void)readStackU32(rdram, sp, 0x1C, endFuncStk); - (void)readStackU32(rdram, sp, 0x20, endParamStk); - - auto looksLikeGuestPtr = [&](uint32_t v) -> bool - { - if (v == 0) - return true; - const uint32_t norm = v & 0x1FFFFFFFu; - return norm >= 0x10000u && norm < PS2_RAM_SIZE; - }; - - auto looksLikeSize = [&](uint32_t v) -> bool - { - return v <= 0x2000000u; - }; - - auto looksLikeFunc = [&](uint32_t v) -> bool - { - return v == 0 || looksLikeGuestPtr(v); - }; - - auto plausiblePack = [&](uint32_t sendSz, uint32_t rbuf, uint32_t rsz, uint32_t endFn) -> bool - { - return looksLikeSize(sendSz) && looksLikeGuestPtr(rbuf) && looksLikeSize(rsz) && looksLikeFunc(endFn); - }; - - const bool regPackPlausible = plausiblePack(sendSizeReg, recvBufReg, recvSizeReg, endFuncReg); - const bool stackPackPlausible = plausiblePack(sendSizeStk, recvBufStk, recvSizeStk, endFuncStk); - - uint32_t boundSidHint = 0u; - { - std::lock_guard lock(g_rpc_mutex); - auto it = g_rpc_clients.find(clientPtr); - if (it != g_rpc_clients.end()) - { - boundSidHint = it->second.sid; - } - } - - auto looksLikeDtxCreatePack = [&](uint32_t sendSz, uint32_t rbuf, uint32_t rsz) -> bool - { - return rbuf != 0u && rsz >= 4u && rsz <= 0x40u && - sendSz >= 12u && sendSz <= 0x1000u; - }; - - const bool isDtxCreate34Call = (boundSidHint == kDtxRpcSid) && (rpcNum == 0x422u); - const bool forceStackForDtxCreate34 = - isDtxCreate34Call && - stackPackPlausible && - looksLikeDtxCreatePack(sendSizeStk, recvBufStk, recvSizeStk) && - !looksLikeDtxCreatePack(sendSizeReg, recvBufReg, recvSizeReg); - - bool useRegConvention = true; - if (forceStackForDtxCreate34) - { - useRegConvention = false; - } - else if (!regPackPlausible && stackPackPlausible) - { - const bool regHasValidCallback = (endFuncReg != 0u) && looksLikeFunc(endFuncReg); - const bool stackHasValidCallback = (endFuncStk != 0u) && looksLikeFunc(endFuncStk); - if (!(regHasValidCallback && !stackHasValidCallback)) - { - useRegConvention = false; - } - } - - sendSize = useRegConvention ? sendSizeReg : sendSizeStk; - recvBuf = useRegConvention ? recvBufReg : recvBufStk; - recvSize = useRegConvention ? recvSizeReg : recvSizeStk; - endFunc = useRegConvention ? endFuncReg : endFuncStk; - endParam = useRegConvention ? endParamReg : endParamStk; - - const bool isDtxLikeRpc = (boundSidHint == kDtxRpcSid) || ((rpcNum & 0xFF00u) == 0x0400u); - static uint32_t dtxAbiLogCount = 0u; - if (isDtxLikeRpc && dtxAbiLogCount < 96u) - { - std::cout << "[SifCallRpc:ABI] client=0x" << std::hex << clientPtr - << " rpc=0x" << rpcNum - << " sidHint=0x" << boundSidHint - << " useReg=" << (useRegConvention ? 1 : 0) - << " reg=(" << sendSizeReg << "," << recvBufReg << "," << recvSizeReg << "," << endFuncReg << "," << endParamReg << ")" - << " stk=(" << sendSizeStk << "," << recvBufStk << "," << recvSizeStk << "," << endFuncStk << "," << endParamStk << ")" - << " plausible=(" << (regPackPlausible ? 1 : 0) << "," << (stackPackPlausible ? 1 : 0) << ")" - << " force34=" << (forceStackForDtxCreate34 ? 1 : 0) - << std::dec << std::endl; - ++dtxAbiLogCount; - } - - 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; - }; - - if (!handled && sid != 0 && runtime) - { - if (!runtime->iop().handleRPC(sid, rpcNum, sendBuf, sendSize, recvBuf, recvSize) && - sid == IOP_SID_LIBSD) - { - const uint8_t *sendPtr = sendBuf ? getConstMemPtr(rdram, sendBuf) : nullptr; - uint8_t *recvPtr = recvBuf ? getMemPtr(rdram, recvBuf) : nullptr; - ps2_iop_audio::handleLibSdRpc(runtime, sid, rpcNum, sendPtr, sendSize, recvPtr, recvSize); - handled = true; - resultPtr = recvBuf; - } - } - - 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)); - } - static uint32_t dtxCreateLogCount = 0; - if (dtxCreateLogCount < 64u) - { - std::cout << "[SifCallRpc:DTX_CREATE] dtxId=0x" << std::hex << dtxId - << " remote=0x" << remoteHandle - << " recvBuf=0x" << recvBuf - << " recvSize=0x" << recvSize - << std::dec << std::endl; - ++dtxCreateLogCount; - } - 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; - } - } - - auto signalRpcCompletionSema = [&](uint32_t semaId) -> bool - { - if (semaId == 0u || semaId > 0xFFFFu) - { - return false; - } - - auto sema = lookupSemaInfo(static_cast(semaId)); - if (!sema) - { - return false; - } - - bool signaled = false; - { - std::lock_guard lock(sema->m); - if (!sema->deleted && sema->count < sema->maxCount) - { - sema->count++; - signaled = true; - } - } - - if (signaled) - { - sema->cv.notify_one(); - } - return signaled; - }; - - if (sid == 1u && (rpcNum == 0x12u || rpcNum == 0x13u)) - { - // RECVX snddrv expects: - // cmd 0x12 -> SND_STATUS* (get_adrs) - // cmd 0x13 -> int[16]* (iop_data_adr_top) - constexpr uint32_t kSdrStatusAddr = 0x00012000u; - constexpr uint32_t kSdrAddrTableAddr = 0x00012100u; - constexpr uint32_t kSdrHdBaseAddr = 0x00014000u; - constexpr uint32_t kSdrSqBaseAddr = 0x00018000u; - constexpr uint32_t kSdrDataBaseAddr = 0x00030000u; - - rpcZeroRdram(rdram, kSdrStatusAddr, 0x42u); - rpcZeroRdram(rdram, kSdrAddrTableAddr, 16u * sizeof(uint32_t)); - (void)writeRpcU32(kSdrAddrTableAddr + (0u * sizeof(uint32_t)), kSdrHdBaseAddr); - (void)writeRpcU32(kSdrAddrTableAddr + (1u * sizeof(uint32_t)), kSdrSqBaseAddr); - (void)writeRpcU32(kSdrAddrTableAddr + (2u * sizeof(uint32_t)), kSdrDataBaseAddr); - - const uint32_t responseWord = (rpcNum == 0x12u) ? kSdrStatusAddr : kSdrAddrTableAddr; - if (recvBuf && recvSize >= sizeof(uint32_t)) - { - (void)writeRpcU32(recvBuf, responseWord); - if (recvSize > sizeof(uint32_t)) - { - rpcZeroRdram(rdram, recvBuf + sizeof(uint32_t), recvSize - sizeof(uint32_t)); - } - resultPtr = recvBuf; - } - - handled = true; - - if ((mode & kSifRpcModeNowait) != 0u) - { - uint32_t semaId = static_cast(client->hdr.sema_id); - if (semaId == 0xFFFFFFFFu || semaId == 0u) - { - semaId = endParam; - } - (void)signalRpcCompletionSema(semaId); - } - } - - if (recvBuf && recvSize > 0) - { - if (handled && resultPtr && resultPtr != recvBuf) - { - rpcCopyToRdram(rdram, recvBuf, resultPtr, recvSize); - } - else if (!handled && sendBuf && sendSize > 0 && sendBuf != recvBuf) - { - 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) - { - bool callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, endFunc, endParam, 0, 0, 0, nullptr); - - if (!callbackInvoked && (endFunc == 0x2fac20u || endFunc == 0x2fac30u)) - { - const uint32_t normalizedEndFunc = endFunc - 0x10000u; - callbackInvoked = rpcInvokeFunction(rdram, ctx, runtime, normalizedEndFunc, endParam, 0, 0, 0, nullptr); - } - - const bool isSoundRpcCallback = - (endFunc == 0x2eac20u || endFunc == 0x2eac30u || - endFunc == 0x2fac20u || endFunc == 0x2fac30u); - if (isSoundRpcCallback) - { - uint32_t semaId = static_cast(client->hdr.sema_id); - if (semaId == 0xFFFFFFFFu || semaId == 0u) - { - semaId = endParam; - } - (void)signalRpcCompletionSema(semaId); - if (rdram && (endFunc == 0x2eac30u || endFunc == 0x2fac30u)) - { - constexpr uint32_t kSndBusyFlagAddr = 0x01E212C8u; - if (uint32_t *busy = reinterpret_cast(getMemPtr(rdram, kSndBusyFlagAddr))) - { - *busy = 0u; - } - } - } - - if (!callbackInvoked) - { - uint32_t semaId = static_cast(client->hdr.sema_id); - if (semaId == 0xFFFFFFFFu || semaId == 0u) - { - semaId = endParam; - } - const bool fallbackSignaledSema = signalRpcCompletionSema(semaId); - - static uint32_t unresolvedEndFuncWarnCount = 0; - if (unresolvedEndFuncWarnCount < 32u) - { - std::cerr << "[SifCallRpc] unresolved end callback endFunc=0x" << std::hex << endFunc - << " semaId=0x" << semaId - << " fallbackSignal=" << std::dec << (fallbackSignaledSema ? 1 : 0) - << std::endl; - ++unresolvedEndFuncWarnCount; - } - } - } - - static int logCount = 0; - if (logCount < 10) - { - std::cout << "[SifCallRpc] client=0x" << std::hex << clientPtr - << " sid=0x" << sid - << " rpcNum=0x" << rpcNum - << " mode=0x" << mode - << " 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; - - { - std::lock_guard lock(g_rpc_mutex); - - 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; - } - } - } - } - - 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) -{ - 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) -{ - 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) -{ - 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; - } - - std::lock_guard lock(g_rpc_mutex); - - 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) -{ - SifCallRpc(rdram, ctx, runtime); -} - -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] cid=0x" << std::hex << cid - << " packet=0x" << packetAddr - << " psize=0x" << packetSize - << " extra=0x" << destExtra << std::dec << std::endl; - ++logCount; - } - - // Return non-zero on success. - setReturnS32(ctx, 1); -} - -void sceRpcGetPacket(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t queuePtr = getRegU32(ctx, 4); - setReturnS32(ctx, static_cast(queuePtr)); -} diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl deleted file mode 100644 index 30359322..00000000 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl +++ /dev/null @@ -1,935 +0,0 @@ -void GsSetCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int interlaced = getRegU32(ctx, 4); // $a0 - 0=non-interlaced, 1=interlaced - int videoMode = getRegU32(ctx, 5); // $a1 - 0=NTSC, 1=PAL, 2=VESA, 3=HiVision - int frameMode = getRegU32(ctx, 6); // $a2 - 0=field, 1=frame - - std::cout << "PS2 GsSetCrt: interlaced=" << interlaced - << ", videoMode=" << videoMode - << ", frameMode=" << frameMode << std::endl; -} - -void SetGsCrt(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - GsSetCrt(rdram, ctx, runtime); -} - -void GsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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; - - setReturnU64(ctx, imr); // Return in $v0/$v1 -} - -void iGsGetIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - GsGetIMR(rdram, ctx, runtime); -} - -void GsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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 iGsPutIMR(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - GsPutIMR(rdram, ctx, runtime); -} - -void GsSetVideoMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int mode = getRegU32(ctx, 4); // $a0 - video mode (various flags) - - std::cout << "PS2 GsSetVideoMode: mode=0x" << std::hex << mode << std::dec << std::endl; - - // Do nothing for now. -} - -void GetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure - - if (!getMemPtr(rdram, paramAddr)) - { - std::cerr << "PS2 GetOsdConfigParam error: Invalid parameter address: 0x" - << std::hex << paramAddr << std::dec << std::endl; - setReturnS32(ctx, -1); - return; - } - - uint32_t *param = reinterpret_cast(getMemPtr(rdram, paramAddr)); - - ensureOsdConfigInitialized(); - uint32_t raw; - { - std::lock_guard lock(g_osd_mutex); - raw = g_osd_config_raw; - } - - *param = raw; - - setReturnS32(ctx, 0); -} - -void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t paramAddr = getRegU32(ctx, 4); // $a0 - pointer to parameter structure - - if (!getConstMemPtr(rdram, paramAddr)) - { - std::cerr << "PS2 SetOsdConfigParam error: Invalid parameter address: 0x" - << std::hex << paramAddr << std::dec << std::endl; - setReturnS32(ctx, -1); - return; - } - - 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); -} - -void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t bufAddr = getRegU32(ctx, 4); // $a0 - size_t bufSize = getRegU32(ctx, 5); // $a1 - char *hostBuf = reinterpret_cast(getMemPtr(rdram, bufAddr)); - const char *romName = "ROMVER 0100"; - - if (!hostBuf) - { - std::cerr << "GetRomName error: Invalid buffer address" << std::endl; - setReturnS32(ctx, -1); // Error - return; - } - if (bufSize == 0) - { - setReturnS32(ctx, 0); - return; - } - - strncpy(hostBuf, romName, bufSize - 1); - hostBuf[bufSize - 1] = '\0'; - - // returns the length of the string (excluding null?) or error - setReturnS32(ctx, (int32_t)strlen(hostBuf)); -} - -void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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* - - std::string secName = readGuestCStringBounded(rdram, secNameAddr, kLoadfileArgMaxBytes); - if (secName.empty()) - { - secName = "all"; - } - - 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) -{ - // Use the same tracker as SifLoadModule so both APIs return the same module IDs. - SifLoadModule(rdram, ctx, runtime); -} - -void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - // a bit more detail mayber reomve old logic, lets get it more raw - std::cerr << "[Syscall TODO]" - << " encoded=0x" << std::hex << encodedSyscallId - << " v1=0x" << getRegU32(ctx, 3) - << " v0=0x" << getRegU32(ctx, 2) - << " a0=0x" << getRegU32(ctx, 4) - << " a1=0x" << getRegU32(ctx, 5) - << " a2=0x" << getRegU32(ctx, 6) - << " a3=0x" << getRegU32(ctx, 7) - << " pc=0x" << ctx->pc - << std::dec << std::endl; - - 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 - << ", 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; - - // Common syscalls: - // 0x04: Exit - // 0x06: LoadExecPS2 - // 0x07: ExecPS2 - if (syscallId == 0x04u) - { - std::cerr << " -> Syscall is Exit(), calling ExitThread stub." << std::endl; - ExitThread(rdram, ctx, runtime); - return; - } - - 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); -} - -static uint32_t computeBuiltinFindAddressResult(uint8_t *rdram, - uint32_t originalStart, - uint32_t originalEnd, - uint32_t target); - -static bool dispatchSyscallOverride(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t handler = 0u; - { - std::lock_guard lock(g_syscall_override_mutex); - auto it = g_syscall_overrides.find(syscallNumber); - if (it == g_syscall_overrides.end()) - { - return false; - } - handler = it->second; - } - - if (!runtime || !ctx || handler == 0u) - { - return false; - } - - const uint32_t overrideA0 = getRegU32(ctx, 4); - const uint32_t overrideA1 = getRegU32(ctx, 5); - const uint32_t overrideA2 = getRegU32(ctx, 6); - const uint32_t overrideA3 = getRegU32(ctx, 7); - const uint32_t overridePc = ctx->pc; - const uint32_t overrideRa = getRegU32(ctx, 31); - - thread_local std::vector s_activeSyscallOverrides; - if (std::find(s_activeSyscallOverrides.begin(), s_activeSyscallOverrides.end(), syscallNumber) != s_activeSyscallOverrides.end()) - { - static std::atomic s_reentrantLogs{0u}; - constexpr uint32_t kMaxReentrantLogs = 32u; - const uint32_t logIndex = s_reentrantLogs.fetch_add(1u, std::memory_order_relaxed); - if (logIndex < kMaxReentrantLogs) - { - std::cerr << "[SyscallOverride:reentrant]" - << " syscall=0x" << std::hex << syscallNumber - << " handler=0x" << handler - << " pc=0x" << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << std::dec << std::endl; - } - return false; - } - - s_activeSyscallOverrides.push_back(syscallNumber); - struct ScopedActiveOverride - { - std::vector &active; - ~ScopedActiveOverride() - { - if (!active.empty()) - { - active.pop_back(); - } - } - } scopedActiveOverride{s_activeSyscallOverrides}; - - uint32_t retV0 = 0u; - const bool invoked = rpcInvokeFunction(rdram, - ctx, - runtime, - handler, - getRegU32(ctx, 4), - getRegU32(ctx, 5), - getRegU32(ctx, 6), - getRegU32(ctx, 7), - &retV0); - - if (syscallNumber == 0x83u) - { - const uint32_t builtinRet = computeBuiltinFindAddressResult(rdram, overrideA0, overrideA1, overrideA2); - const bool mismatch = (retV0 != builtinRet); - - static std::atomic s_findAddressOverrideLogs{0u}; - static std::atomic s_findAddressOverrideMismatchLogs{0u}; - constexpr uint32_t kMaxFindAddressOverrideLogs = 64u; - constexpr uint32_t kMaxFindAddressOverrideMismatchLogs = 128u; - - const uint32_t logIndex = s_findAddressOverrideLogs.fetch_add(1u, std::memory_order_relaxed); - const uint32_t mismatchIndex = mismatch - ? s_findAddressOverrideMismatchLogs.fetch_add(1u, std::memory_order_relaxed) - : 0u; - if (logIndex < kMaxFindAddressOverrideLogs || - (mismatch && mismatchIndex < kMaxFindAddressOverrideMismatchLogs)) - { - const uint32_t guestMinus20c = (retV0 != 0u) ? (retV0 - 0x20Cu) : 0u; - const uint32_t guestMinus168 = (retV0 != 0u) ? (retV0 - 0x168u) : 0u; - const uint32_t builtinMinus20c = (builtinRet != 0u) ? (builtinRet - 0x20Cu) : 0u; - const uint32_t builtinMinus168 = (builtinRet != 0u) ? (builtinRet - 0x168u) : 0u; - - std::cerr << "[Syscall83:override]" - << " handler=0x" << std::hex << handler - << " invoked=" << (invoked ? "true" : "false") - << " pc=0x" << overridePc - << " ra=0x" << overrideRa - << " a0=0x" << overrideA0 - << " a1=0x" << overrideA1 - << " a2=0x" << overrideA2 - << " a3=0x" << overrideA3 - << " guestRet=0x" << retV0 - << " builtinRet=0x" << builtinRet - << " guest-20c=0x" << guestMinus20c - << " builtin-20c=0x" << builtinMinus20c - << " guest-168=0x" << guestMinus168 - << " builtin-168=0x" << builtinMinus168 - << " match=" << (mismatch ? "false" : "true") - << std::dec << std::endl; - } - } - - if (!invoked) - { - static std::atomic s_fallbackLogs{0u}; - constexpr uint32_t kMaxFallbackLogs = 64u; - const uint32_t logIndex = s_fallbackLogs.fetch_add(1u, std::memory_order_relaxed); - if (logIndex < kMaxFallbackLogs) - { - std::cerr << "[SyscallOverride:fallback]" - << " syscall=0x" << std::hex << syscallNumber - << " handler=0x" << handler - << " pc=0x" << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << std::dec << std::endl; - } - return false; - } - - setReturnU32(ctx, retV0); - return true; -} - -static bool tryResolveGuestSyscallMirrorAddr(uint32_t syscallIndex, uint32_t &guestAddr) -{ - const int64_t offsetBytes = - static_cast(static_cast(syscallIndex)) * static_cast(sizeof(uint32_t)); - const int64_t guestAddr64 = static_cast(kGuestSyscallTablePhysBase) + offsetBytes; - if (guestAddr64 < 0 || (guestAddr64 + static_cast(sizeof(uint32_t))) > static_cast(kGuestSyscallMirrorLimit)) - { - return false; - } - - guestAddr = static_cast(guestAddr64); - return true; -} - -static void writeGuestKernelWord(uint8_t *rdram, uint32_t guestAddr, uint32_t value) -{ - if (!rdram) - { - return; - } - - if (uint8_t *ptr = getMemPtr(rdram, guestAddr)) - { - std::memcpy(ptr, &value, sizeof(value)); - } -} - -static void seedGuestSyscallTableProbeLocked(uint8_t *rdram) -{ - writeGuestKernelWord(rdram, kGuestSyscallTableProbeBase + 0u, kGuestSyscallTableGuestBase >> 16); - writeGuestKernelWord(rdram, kGuestSyscallTableProbeBase + 8u, kGuestSyscallTableGuestBase & 0xFFFFu); - g_syscall_mirror_addrs.insert(kGuestSyscallTableProbeBase + 0u); - g_syscall_mirror_addrs.insert(kGuestSyscallTableProbeBase + 8u); -} - -static void mirrorGuestSyscallEntryLocked(uint8_t *rdram, uint32_t syscallIndex, uint32_t handler) -{ - uint32_t guestAddr = 0u; - if (!tryResolveGuestSyscallMirrorAddr(syscallIndex, guestAddr)) - { - return; - } - - writeGuestKernelWord(rdram, guestAddr, handler); - if (handler == 0u) - { - g_syscall_mirror_addrs.erase(guestAddr); - return; - } - - g_syscall_mirror_addrs.insert(guestAddr); -} - -void initializeGuestKernelState(uint8_t *rdram) -{ - if (!rdram) - { - return; - } - - std::lock_guard lock(g_syscall_override_mutex); - for (uint32_t guestAddr : g_syscall_mirror_addrs) - { - writeGuestKernelWord(rdram, guestAddr, 0u); - } - g_syscall_mirror_addrs.clear(); - - seedGuestSyscallTableProbeLocked(rdram); - - for (const auto &entry : g_syscall_overrides) - { - mirrorGuestSyscallEntryLocked(rdram, entry.first, entry.second); - } -} - -void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - const uint32_t syscallIndex = getRegU32(ctx, 4); - const uint32_t handler = getRegU32(ctx, 5); - - { - std::lock_guard lock(g_syscall_override_mutex); - if (handler == 0u) - { - g_syscall_overrides.erase(syscallIndex); - } - else - { - g_syscall_overrides[syscallIndex] = handler; - } - - mirrorGuestSyscallEntryLocked(rdram, syscallIndex, handler); - } - - setReturnS32(ctx, 0); -} - -// 0x3C SetupThread -// args: $a0 = gp, $a1 = stack, $a2 = stack_size, $a3 = args, $t0 = root_func -void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - const uint32_t gp = getRegU32(ctx, 4); - const uint32_t stack = getRegU32(ctx, 5); - const int32_t stackSizeSigned = static_cast(getRegU32(ctx, 6)); - const uint32_t currentSp = getRegU32(ctx, 29); - - if (gp != 0u) - { - setRegU32(ctx, 28, gp); - } - - uint32_t sp = currentSp; - if (stack == 0xFFFFFFFFu) - { - if (stackSizeSigned > 0) - { - const uint32_t requestedSize = static_cast(stackSizeSigned); - if (requestedSize < PS2_RAM_SIZE) - { - sp = PS2_RAM_SIZE - requestedSize; - } - else - { - sp = PS2_RAM_SIZE; - } - } - else - { - sp = PS2_RAM_SIZE; - } - } - else if (stack != 0u) - { - if (stackSizeSigned > 0) - { - sp = stack + static_cast(stackSizeSigned); - } - else - { - sp = stack; - } - } - - sp &= ~0xFu; - setReturnU32(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)); -} - -void GetMemorySize(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnU32(ctx, PS2_RAM_SIZE); -} - -static inline uint32_t normalizeKernelAlias(uint32_t addr) -{ - if (addr >= 0x80000000u && addr < 0xC0000000u) - { - return addr & 0x1FFFFFFFu; - } - return addr; -} - -static uint32_t computeBuiltinFindAddressResult(uint8_t *rdram, - uint32_t originalStart, - uint32_t originalEnd, - uint32_t target) -{ - uint32_t start = (originalStart + 3u) & ~0x3u; - uint32_t end = originalEnd & ~0x3u; - if (start >= end) - { - return 0u; - } - - const uint32_t targetNorm = normalizeKernelAlias(target); - for (uint32_t addr = start; addr < end; addr += sizeof(uint32_t)) - { - const uint8_t *entryPtr = getConstMemPtr(rdram, addr); - if (!entryPtr) - { - break; - } - - uint32_t entry = 0u; - std::memcpy(&entry, entryPtr, sizeof(entry)); - if (entry == target || normalizeKernelAlias(entry) == targetNorm) - { - return addr; - } - } - - return 0u; -} - -struct FindAddressWordSample -{ - uint32_t addr = 0u; - uint32_t value = 0u; -}; - -struct FindAddressMatchSample -{ - uint32_t addr = 0u; - uint32_t value = 0u; - bool aliasOnly = false; -}; - -static void logFindAddressDiagnostics(uint32_t callerPc, - uint32_t originalStart, - uint32_t originalEnd, - uint32_t alignedStart, - uint32_t alignedEnd, - uint32_t target, - uint32_t targetNorm, - bool found, - uint32_t resultAddr, - uint32_t scannedWords, - bool allZero, - bool aborted, - uint32_t abortedAddr, - const FindAddressWordSample *firstWords, - uint32_t firstWordCount, - const FindAddressWordSample *nonZeroWords, - uint32_t nonZeroWordCount, - const FindAddressMatchSample *matches, - uint32_t matchCount) -{ - static std::atomic s_findAddressHitLogs{0u}; - static std::atomic s_findAddressMissLogs{0u}; - constexpr uint32_t kMaxFindAddressHitLogs = 16u; - constexpr uint32_t kMaxFindAddressMissLogs = 128u; - - std::atomic &counter = found ? s_findAddressHitLogs : s_findAddressMissLogs; - const uint32_t logIndex = counter.fetch_add(1u, std::memory_order_relaxed); - const uint32_t logLimit = found ? kMaxFindAddressHitLogs : kMaxFindAddressMissLogs; - if (logIndex >= logLimit) - { - return; - } - - std::cerr << "[FindAddress:" << (found ? "hit" : "miss") << "]" - << " pc=0x" << std::hex << callerPc - << " start=0x" << originalStart - << " end=0x" << originalEnd - << " alignedStart=0x" << alignedStart - << " alignedEnd=0x" << alignedEnd - << " target=0x" << target - << " targetNorm=0x" << targetNorm - << " result=0x" << resultAddr - << std::dec - << " scannedWords=" << scannedWords - << " allZero=" << (allZero ? "true" : "false") - << " aborted=" << (aborted ? "true" : "false"); - if (aborted) - { - std::cerr << " abortedAddr=0x" << std::hex << abortedAddr << std::dec; - } - std::cerr << std::endl; - - std::cerr << " firstWords:"; - if (firstWordCount == 0u) - { - std::cerr << " none"; - } - else - { - for (uint32_t i = 0; i < firstWordCount; ++i) - { - std::cerr << " [0x" << std::hex << firstWords[i].addr - << "]=0x" << firstWords[i].value; - } - std::cerr << std::dec; - } - std::cerr << std::endl; - - std::cerr << " nonZeroSample:"; - if (nonZeroWordCount == 0u) - { - std::cerr << " none"; - } - else - { - for (uint32_t i = 0; i < nonZeroWordCount; ++i) - { - std::cerr << " [0x" << std::hex << nonZeroWords[i].addr - << "]=0x" << nonZeroWords[i].value; - } - std::cerr << std::dec; - } - std::cerr << std::endl; - - std::cerr << " matches:"; - if (matchCount == 0u) - { - std::cerr << " none"; - } - else - { - for (uint32_t i = 0; i < matchCount; ++i) - { - std::cerr << " [0x" << std::hex << matches[i].addr - << "]=0x" << matches[i].value - << (matches[i].aliasOnly ? "(alias)" : "(exact)"); - } - std::cerr << std::dec; - } - std::cerr << std::endl; -} - -// 0x83 FindAddress: -// - a0: table start (inclusive) -// - a1: table end (exclusive) -// - a2: target address to locate inside the table (word entries) -// Returns the guest address of the matching word entry, or 0 if not found. -void FindAddress(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)runtime; - - constexpr uint32_t kFindAddressWordSamples = 8u; - constexpr uint32_t kFindAddressMatchSamples = 4u; - - const uint32_t originalStart = getRegU32(ctx, 4); - const uint32_t originalEnd = getRegU32(ctx, 5); - const uint32_t target = getRegU32(ctx, 6); - const uint32_t targetNorm = normalizeKernelAlias(target); - const uint32_t callerPc = ctx->pc; - - uint32_t start = originalStart; - uint32_t end = originalEnd; - - // Word-scan semantics: align the search window to uint32 boundaries. - start = (start + 3u) & ~0x3u; - end &= ~0x3u; - - if (start >= end) - { - logFindAddressDiagnostics(callerPc, - originalStart, - originalEnd, - start, - end, - target, - targetNorm, - false, - 0u, - 0u, - true, - false, - 0u, - nullptr, - 0u, - nullptr, - 0u, - nullptr, - 0u); - setReturnU32(ctx, 0u); - return; - } - - FindAddressWordSample firstWords[kFindAddressWordSamples]{}; - FindAddressWordSample nonZeroWords[kFindAddressWordSamples]{}; - FindAddressMatchSample matches[kFindAddressMatchSamples]{}; - uint32_t firstWordCount = 0u; - uint32_t nonZeroWordCount = 0u; - uint32_t matchCount = 0u; - uint32_t scannedWords = 0u; - uint32_t resultAddr = 0u; - uint32_t abortedAddr = 0u; - bool aborted = false; - bool allZero = true; - bool foundMatch = false; - - for (uint32_t addr = start; addr < end; addr += sizeof(uint32_t)) - { - const uint8_t *entryPtr = getConstMemPtr(rdram, addr); - if (!entryPtr) - { - aborted = true; - abortedAddr = addr; - break; - } - - uint32_t entry = 0; - std::memcpy(&entry, entryPtr, sizeof(entry)); - ++scannedWords; - - if (firstWordCount < kFindAddressWordSamples) - { - firstWords[firstWordCount++] = {addr, entry}; - } - - if (entry != 0u) - { - allZero = false; - if (nonZeroWordCount < kFindAddressWordSamples) - { - nonZeroWords[nonZeroWordCount++] = {addr, entry}; - } - } - - const bool exactMatch = (entry == target); - const bool aliasMatch = !exactMatch && (normalizeKernelAlias(entry) == targetNorm); - if (exactMatch || aliasMatch) - { - if (!foundMatch) - { - resultAddr = addr; - foundMatch = true; - } - if (matchCount < kFindAddressMatchSamples) - { - matches[matchCount++] = {addr, entry, aliasMatch}; - } - } - } - - logFindAddressDiagnostics(callerPc, - originalStart, - originalEnd, - start, - end, - target, - targetNorm, - foundMatch, - resultAddr, - scannedWords, - allZero, - aborted, - abortedAddr, - firstWords, - firstWordCount, - nonZeroWords, - nonZeroWordCount, - matches, - matchCount); - - setReturnU32(ctx, resultAddr); -} - -void Deci2Call(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - (void)rdram; - (void)runtime; - setReturnS32(ctx, KE_OK); -} - -// 0x5A QueryBootMode (stub): return 0 for now -void QueryBootMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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) -{ - 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/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl deleted file mode 100644 index 05352594..00000000 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl +++ /dev/null @@ -1,1106 +0,0 @@ -static void applySuspendStatusLocked(ThreadInfo &info) -{ - if (info.waitType != TSW_NONE) - { - info.status = THS_WAITSUSPEND; - } - else - { - info.status = THS_SUSPEND; - } -} - -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 &) - { - } - } -} - -void FlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, KE_OK); -} - -void iFlushCache(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - FlushCache(rdram, ctx, runtime); -} - -void ResetEE(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - std::cerr << "Syscall: ResetEE - requesting runtime stop" << std::endl; - // runtime->requestStop(); - setReturnS32(ctx, KE_OK); -} - -void SetMemoryMode(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, KE_OK); -} - -void InitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - // This is a common ps2sdk helper that some games link against. - setReturnS32(ctx, 1); -} - -void CreateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - uint32_t paramAddr = getRegU32(ctx, 4); // $a0 points to ThreadParam - if (paramAddr == 0u) - { - std::cerr << "CreateThread error: null ThreadParam pointer" << std::endl; - setReturnS32(ctx, KE_ERROR); - return; - } - - const uint32_t *param = reinterpret_cast(getConstMemPtr(rdram, paramAddr)); - - if (!param) - { - std::cerr << "CreateThread error: invalid ThreadParam address 0x" << std::hex << paramAddr << std::dec << std::endl; - setReturnS32(ctx, KE_ERROR); - return; - } - - auto info = std::make_shared(); - info->attr = param[0]; - info->entry = param[1]; - info->stack = param[2]; - info->stackSize = param[3]; - - 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]; - if (info->priority == 0) - { - info->priority = 1; - } - if (info->priority >= 128) - { - info->priority = 127; - } - info->currentPriority = static_cast(info->priority); - - int id = 0; - { - std::lock_guard lock(g_thread_map_mutex); - // Keep IDs in the classic low range used by patched libkernel helpers. - for (int attempts = 0; attempts < 0xFE; ++attempts) - { - if (g_nextThreadId < 2 || g_nextThreadId > 0xFF) - { - g_nextThreadId = 2; - } - - const int candidate = g_nextThreadId; - g_nextThreadId = (g_nextThreadId >= 0xFF) ? 2 : (g_nextThreadId + 1); - - if (g_threads.find(candidate) == g_threads.end()) - { - id = candidate; - break; - } - } - - if (id == 0) - { - setReturnS32(ctx, KE_ERROR); - return; - } - - 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; - - setReturnS32(ctx, id); -} - -void DeleteThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int tid = static_cast(getRegU32(ctx, 4)); // $a0 - if (tid == 0) - { - setReturnS32(ctx, KE_ILLEGAL_THID); - return; - } - - auto info = lookupThreadInfo(tid); - if (!info) - { - setReturnS32(ctx, KE_UNKNOWN_THID); - return; - } - - uint32_t autoStackToFree = 0; - { - std::lock_guard lock(info->m); - if (info->started || info->status != THS_DORMANT) - { - setReturnS32(ctx, KE_NOT_DORMANT); - return; - } - - if (info->ownsStack && info->stack != 0) - { - autoStackToFree = info->stack; - info->stack = 0; - info->stackSize = 0; - info->ownsStack = false; - } - } - - { - std::lock_guard lock(g_thread_map_mutex); - g_threads.erase(tid); - } - - { - std::lock_guard lock(g_exit_handler_mutex); - g_exit_handlers.erase(tid); - } - - if (runtime && autoStackToFree != 0) - { - runtime->guestFree(autoStackToFree); - } - - setReturnS32(ctx, KE_OK); -} - -void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int tid = static_cast(getRegU32(ctx, 4)); // $a0 = thread id - uint32_t arg = getRegU32(ctx, 5); // $a1 = user arg - if (tid == 0) - { - setReturnS32(ctx, KE_ILLEGAL_THID); - return; - } - - auto info = lookupThreadInfo(tid); - if (!info) - { - std::cerr << "StartThread error: unknown thread id " << tid << std::endl; - setReturnS32(ctx, KE_UNKNOWN_THID); - return; - } - - if (!runtime || !runtime->hasFunction(info->entry)) - { - std::cerr << "[StartThread] entry 0x" << std::hex << info->entry << std::dec << " is not registered" << std::endl; - setReturnS32(ctx, KE_ERROR); - return; - } - if (runtime->isStopRequested()) - { - setReturnS32(ctx, KE_ERROR); - return; - } - - joinHostThreadById(tid); - - const uint32_t callerSp = getRegU32(ctx, 29); - const uint32_t callerGp = getRegU32(ctx, 28); - - { - std::lock_guard lock(info->m); - if (info->started || info->status != THS_DORMANT) - { - setReturnS32(ctx, KE_NOT_DORMANT); - return; - } - - info->started = true; - info->status = THS_READY; - info->arg = arg; - info->terminated = false; - info->forceRelease = false; - info->waitType = TSW_NONE; - info->waitId = 0; - info->wakeupCount = 0; - info->suspendCount = 0; - if (info->stack == 0 && info->stackSize != 0) - { - const uint32_t autoStack = runtime->guestMalloc(info->stackSize, 16u); - if (autoStack != 0) - { - info->stack = autoStack; - info->ownsStack = true; - 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; - } - } - - g_activeThreads.fetch_add(1, std::memory_order_relaxed); - try - { - std::thread worker([=]() mutable - { - { - std::string name = "PS2Thread_" + std::to_string(tid); - ThreadNaming::SetCurrentThreadName(name); - } - R5900Context threadCtxCopy{}; - R5900Context *threadCtx = &threadCtxCopy; - - { - std::lock_guard lock(info->m); - info->status = THS_RUN; - } - - uint32_t threadSp = callerSp; - if (info->stack) - { - const uint32_t stackSize = (info->stackSize != 0) ? info->stackSize : 0x800u; - threadSp = (info->stack + stackSize) & ~0xFu; - } - uint32_t threadGp = info->gp; - const uint32_t normalizedGp = threadGp & 0x1FFFFFFFu; - if (threadGp == 0 || normalizedGp < 0x10000u || normalizedGp >= PS2_RAM_SIZE) - { - threadGp = callerGp; - } - - 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; - - g_currentThreadId = tid; - - std::cout << "[StartThread] id=" << tid - << " 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; - - bool exited = false; - try - { - uint32_t lastPc = 0xFFFFFFFFu; - uint32_t samePcCount = 0; - constexpr uint32_t kSamePcYieldMask = 0x3FFFu; - constexpr uint32_t kSamePcWarnInterval = 0x20000u; - uint64_t stepCount = 0u; - - while (runtime && !runtime->isStopRequested()) - { - ++stepCount; - if (info->terminated.load(std::memory_order_relaxed)) - { - throw ThreadExitException(); - } - - waitWhileSuspended(info, runtime); - - const uint32_t pc = threadCtx->pc; - if (pc == 0u) - { - break; - } - - if ((stepCount & 0x1FFFFFu) == 0u) - { - std::cout << "[StartThread] id=" << tid - << " heartbeat pc=0x" << std::hex << pc - << " ra=0x" << GPR_U32(threadCtx, 31) - << " sp=0x" << GPR_U32(threadCtx, 29) - << " gp=0x" << GPR_U32(threadCtx, 28) - << std::dec << std::endl; - } - - if (pc == lastPc) - { - ++samePcCount; - if ((samePcCount & kSamePcYieldMask) == 0u) - { - std::this_thread::yield(); - } - if ((samePcCount % kSamePcWarnInterval) == 0u) - { - std::cout << "[StartThread] id=" << tid - << " spinning at pc=0x" << std::hex << pc - << " ra=0x" << GPR_U32(threadCtx, 31) - << std::dec << std::endl; - } - } - else - { - samePcCount = 0; - lastPc = pc; - } - - thread_local uint32_t s_adxProbeLogs = 0u; - if (s_adxProbeLogs < 256u) - { - const uint32_t raProbe = GPR_U32(threadCtx, 31); - const bool probeAdxSetCmd = (pc == 0x2F22E0u) && - ((raProbe < 0x00100000u) || (raProbe == 0x2F45B0u)); - const bool probeAdxUnlock = (pc == 0x2F45B0u) && - (raProbe < 0x00100000u); - const bool probeLowPc = (pc < 0x00100000u); - if (probeAdxSetCmd || probeAdxUnlock || probeLowPc) - { - auto flags = std::cerr.flags(); - std::cerr << "[StartThread:adx-probe] tid=" << tid - << " pc=0x" << std::hex << pc - << " ra=0x" << raProbe - << " sp=0x" << GPR_U32(threadCtx, 29) - << " gp=0x" << GPR_U32(threadCtx, 28) - << " a0=0x" << GPR_U32(threadCtx, 4) - << " a1=0x" << GPR_U32(threadCtx, 5) - << " a2=0x" << GPR_U32(threadCtx, 6) - << " a3=0x" << GPR_U32(threadCtx, 7) - << std::dec << std::endl; - std::cerr.flags(flags); - ++s_adxProbeLogs; - } - } - - PS2Runtime::RecompiledFunction step = runtime->lookupFunction(pc); - if (!step) - { - std::cerr << "[StartThread] id=" << tid << " missing function for pc=0x" - << std::hex << pc << std::dec << std::endl; - throw ThreadExitException(); - } - { - PS2Runtime::GuestExecutionScope guestExecution(runtime); - step(rdram, threadCtx, runtime); - } - } - } - catch (const ThreadExitException &) - { - exited = true; - } - catch (const std::exception &e) - { - std::cerr << "[StartThread] id=" << tid << " exception: " << e.what() << 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); - - uint32_t detachedAutoStack = 0; - { - std::lock_guard lock(info->m); - info->started = false; - info->status = THS_DORMANT; - info->waitType = TSW_NONE; - info->waitId = 0; - info->wakeupCount = 0; - info->suspendCount = 0; - info->forceRelease = false; - info->terminated = false; - } - - bool stillRegistered = false; - { - std::lock_guard lock(g_thread_map_mutex); - stillRegistered = (g_threads.find(tid) != g_threads.end()); - } - if (!stillRegistered) - { - // ExitDeleteThread removes the record immediately; reclaim auto stack here. - std::lock_guard lock(info->m); - if (info->ownsStack && info->stack != 0) - { - detachedAutoStack = info->stack; - info->stack = 0; - info->stackSize = 0; - info->ownsStack = false; - } - } - - if (detachedAutoStack != 0 && runtime) - { - runtime->guestFree(detachedAutoStack); - } - - // Notify anybody waiting for termination (like TerminateThread) - info->cv.notify_all(); - - g_activeThreads.fetch_sub(1, std::memory_order_relaxed); }); - registerHostThread(tid, std::move(worker)); - } - catch (const std::exception &e) - { - std::cerr << "[StartThread] failed to spawn host thread for tid=" << tid << ": " << e.what() << std::endl; - g_activeThreads.fetch_sub(1, std::memory_order_relaxed); - std::lock_guard lock(info->m); - info->started = false; - info->status = THS_DORMANT; - info->waitType = TSW_NONE; - info->waitId = 0; - info->wakeupCount = 0; - info->suspendCount = 0; - info->forceRelease = false; - info->terminated = false; - setReturnS32(ctx, KE_ERROR); - return; - } - - setReturnS32(ctx, KE_OK); -} - -void ExitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - 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->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 = 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->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)); - if (tid == 0) - tid = g_currentThreadId; - - 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; - } - info->terminated = true; - info->forceRelease = true; - } - info->cv.notify_all(); - - if (tid == g_currentThreadId) - { - runExitHandlersForThread(tid, rdram, ctx, runtime); - throw ThreadExitException(); - } - else - { - // Block until the target thread actually finishes unwinding and becomes dormant - std::unique_lock lock(info->m); - { - PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); - info->cv.wait(lock, [&]() - { return !info->started && info->status == THS_DORMANT; }); - } - } - - setReturnS32(ctx, KE_OK); -} - -void SuspendThread(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, KE_UNKNOWN_THID); - return; - } - - { - std::lock_guard lock(info->m); - if (info->status == THS_DORMANT) - { - setReturnS32(ctx, KE_DORMANT); - return; - } - info->suspendCount++; - applySuspendStatusLocked(*info); - } - info->cv.notify_all(); - - if (tid == g_currentThreadId) - { - std::unique_lock lock(info->m); - { - PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); - info->cv.wait(lock, [&]() - { return info->suspendCount == 0 || info->terminated.load(); }); - } - if (info->terminated.load()) - { - throw ThreadExitException(); - } - info->status = THS_RUN; - } - - setReturnS32(ctx, KE_OK); -} - -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, KE_UNKNOWN_THID); - return; - } - - { - std::lock_guard lock(info->m); - if (info->status == THS_DORMANT) - { - setReturnS32(ctx, KE_DORMANT); - return; - } - if (info->suspendCount <= 0) - { - setReturnS32(ctx, KE_NOT_SUSPEND); - 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, KE_OK); -} - -void GetThreadId(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - setReturnS32(ctx, g_currentThreadId); -} - -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, KE_UNKNOWN_THID); - return; - } - - ee_thread_status_t *status = reinterpret_cast(getMemPtr(rdram, statusAddr)); - if (!status) - { - setReturnS32(ctx, KE_ERROR); - 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, KE_OK); -} - -void iReferThreadStatus(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ReferThreadStatus(rdram, ctx, runtime); -} - -void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - auto info = ensureCurrentThreadInfo(ctx); - if (!info) - { - setReturnS32(ctx, KE_UNKNOWN_THID); - return; - } - - 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 - { - static std::atomic s_sleepBlockLogs{0}; - const uint32_t sleepBlockLog = s_sleepBlockLogs.fetch_add(1, std::memory_order_relaxed); - if (sleepBlockLog < 256u) - { - std::cout << "[SleepThread:block] tid=" << g_currentThreadId - << " pc=0x" << std::hex << ctx->pc - << " ra=0x" << getRegU32(ctx, 31) - << std::dec << std::endl; - } - - info->status = THS_WAIT; - info->waitType = TSW_SLEEP; - info->waitId = 0; - info->forceRelease = false; - - { - PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime); - 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; - } - } - - static std::atomic s_sleepWakeLogs{0}; - const uint32_t sleepWakeLog = s_sleepWakeLogs.fetch_add(1, std::memory_order_relaxed); - if (sleepWakeLog < 256u) - { - std::cout << "[SleepThread:wake] tid=" << g_currentThreadId - << " ret=" << ret - << " wakeupCount=" << info->wakeupCount - << std::endl; - } - - lock.unlock(); - waitWhileSuspended(info, runtime); - setReturnS32(ctx, ret); -} - -void WakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int tid = static_cast(getRegU32(ctx, 4)); - if (tid == 0) - { - setReturnS32(ctx, KE_ILLEGAL_THID); - return; - } - if (tid == g_currentThreadId) - { - setReturnS32(ctx, KE_ILLEGAL_THID); - return; - } - - auto info = lookupThreadInfo(tid); - if (!info) - { - setReturnS32(ctx, KE_UNKNOWN_THID); - return; - } - - int newWakeupCount = 0; - int statusAfter = THS_DORMANT; - { - 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++; - } - newWakeupCount = info->wakeupCount; - statusAfter = info->status; - } - - static std::atomic s_wakeupLogs{0}; - const uint32_t wakeupLog = s_wakeupLogs.fetch_add(1, std::memory_order_relaxed); - if (wakeupLog < 256u) - { - std::cout << "[WakeupThread] tid=" << g_currentThreadId - << " target=" << tid - << " status=" << statusAfter - << " wakeupCount=" << newWakeupCount - << std::endl; - } - setReturnS32(ctx, KE_OK); -} - -void iWakeupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - WakeupThread(rdram, ctx, runtime); -} - -void CancelWakeupThread(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, KE_UNKNOWN_THID); - return; - } - - 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) -{ - 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; - } - - 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)); - - if (tid == 0) - tid = g_currentThreadId; - - 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 (newPrio == 0) - { - newPrio = (info->currentPriority > 0) ? info->currentPriority : 1; - } - if (newPrio <= 0 || newPrio >= 128) - { - setReturnS32(ctx, KE_ILLEGAL_PRIORITY); - return; - } - - info->currentPriority = newPrio; - } - - setReturnS32(ctx, KE_OK); -} - -void iChangeThreadPriority(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ChangeThreadPriority(rdram, ctx, runtime); -} - -void RotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - static int logCount = 0; - int prio = static_cast(getRegU32(ctx, 4)); - if (prio == 0) - { - auto current = ensureCurrentThreadInfo(ctx); - if (current) - { - std::lock_guard lock(current->m); - prio = (current->currentPriority > 0) ? current->currentPriority : 1; - } - } - if (logCount < 16) - { - std::cout << "[RotateThreadReadyQueue] prio=" << prio << std::endl; - ++logCount; - } - if (prio <= 0 || prio >= 128) - { - setReturnS32(ctx, KE_ILLEGAL_PRIORITY); - return; - } - - std::this_thread::yield(); - - setReturnS32(ctx, KE_OK); -} - -void iRotateThreadReadyQueue(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - RotateThreadReadyQueue(rdram, ctx, runtime); -} - -void ReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - int tid = static_cast(getRegU32(ctx, 4)); - if (tid == 0 || tid == g_currentThreadId) - { - 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 || info->status == THS_WAITSUSPEND) - { - 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, KE_OK); -} - -void iReleaseWaitThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) -{ - ReleaseWaitThread(rdram, ctx, runtime); -} diff --git a/ps2xRuntime/src/main.cpp b/ps2xRuntime/src/main.cpp index dc3425b9..21cd2c63 100644 --- a/ps2xRuntime/src/main.cpp +++ b/ps2xRuntime/src/main.cpp @@ -1,6 +1,7 @@ #include "ps2_runtime.h" #include "register_functions.h" #include "games_database.h" + #ifdef _DEBUG #include "ps2_log.h" #endif @@ -8,66 +9,144 @@ #include #include #include +#include +#include +#include -std::string normalizeGameId(const std::string& folderName) +namespace { - std::string result = folderName; + void setupTerminateLogger() // to help on release build crashs + { + std::set_terminate([]() + { + std::cerr << "[terminate] unhandled exception" << std::endl; + const std::exception_ptr ep = std::current_exception(); + if (ep) + { + try + { + std::rethrow_exception(ep); + } + catch (const std::system_error &e) + { + std::cerr << "[terminate] std::system_error code=" << e.code().value() + << " category=" << e.code().category().name() + << " message=" << e.what() << std::endl; + } + catch (const std::exception &e) + { + std::cerr << "[terminate] std::exception: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "[terminate] non-std exception" << std::endl; + } + } + std::abort(); }); + } + + std::string normalizeGameId(const std::string &folderName) + { + std::string result = folderName; + + size_t underscore = result.find('_'); + if (underscore != std::string::npos) + result[underscore] = '-'; - size_t underscore = result.find('_'); - if (underscore != std::string::npos) - result[underscore] = '-'; + size_t dot = result.find('.'); + if (dot != std::string::npos) + result.erase(dot, 1); - size_t dot = result.find('.'); - if (dot != std::string::npos) - result.erase(dot, 1); + std::ranges::transform(result, result.begin(), [](unsigned char character) + { return static_cast(std::toupper(character)); }); + + return result; + } - return result; + std::filesystem::path getExecutablePath(int argc, char *argv[]) + { + if (argc >= 2 && argv[1] && argv[1][0] != '\0') + { + std::cout << "Using argv boot path" << std::endl; + return std::filesystem::path(argv[1]); + } +#if defined(PS2X_DEFAULT_BOOT_ELF) + std::cout << "Using default boot file" << std::endl; + const std::filesystem::path configuredPath = std::filesystem::path(PS2X_DEFAULT_BOOT_ELF); +#if defined(PLATFORM_VITA) + return configuredPath; +#endif + if (configuredPath.is_absolute()) + { + return configuredPath; + } + return (std::filesystem::current_path() / configuredPath).lexically_normal(); +#else + throw std::runtime_error("Unable to determine executable path. Pass the guest ELF as argv[1] or define PS2X_DEFAULT_BOOT_ELF."); +#endif + } } -int main(int argc, char* argv[]) +int main(int argc, char *argv[]) { - if (argc < 2) + setupTerminateLogger(); + + try { - std::cout << "Usage: " << argv[0] << " " << std::endl; - return 1; - } + std::filesystem::path pathObj = getExecutablePath(argc, argv); - std::string elfPath = argv[1]; - std::filesystem::path pathObj(elfPath); - std::string folderName = pathObj.filename().string(); - std::string normalizedId = normalizeGameId(folderName); + std::string filePathStr = pathObj.string(); + std::string elfName = pathObj.filename().string(); + std::string normalizedId = normalizeGameId(elfName); - std::string windowTitle = "PS2-Recomp | "; - const char* gameName = getGameName(normalizedId); + std::string windowTitle = "PS2-Recomp | "; + const char *gameName = getGameName(normalizedId); - if (gameName) - { - windowTitle += std::string(gameName) + " | " + folderName; - } - else - { - windowTitle += folderName; - } +#if !defined(PLATFORM_VITA) + if (gameName) + { + windowTitle += std::string(gameName) + " | " + elfName; + } + else +#endif + { + windowTitle += elfName; + } - PS2Runtime runtime; - if (!runtime.initialize(windowTitle.c_str())) - { - std::cerr << "Failed to initialize PS2 runtime" << std::endl; - return 1; - } + PS2Runtime runtime; + if (!runtime.initialize(windowTitle.c_str())) + { + std::cerr << "Failed to initialize PS2 runtime" << std::endl; + return 1; + } - registerAllFunctions(runtime); + registerAllFunctions(runtime); - if (!runtime.loadELF(elfPath)) - { - std::cerr << "Failed to load ELF file: " << elfPath << std::endl; - return 1; - } + if (!runtime.loadELF(filePathStr)) + { + std::cerr << "Failed to load ELF file: " << filePathStr << std::endl; + return 1; + } - runtime.run(); + runtime.run(); #ifdef _DEBUG - ps2_log::print_saved_location(); + ps2_log::print_saved_location(); #endif - return 0; -} \ No newline at end of file + std::cout.flush(); + std::cerr.flush(); + std::_Exit(0); + } + catch (const std::exception &e) + { + std::cerr << "[main] fatal exception: " << e.what() << std::endl; + } + catch (...) + { + std::cerr << "[main] fatal exception: unknown" << std::endl; + } + + std::cout.flush(); + std::cerr.flush(); + std::_Exit(1); +} diff --git a/ps2xStudio/CMakeLists.txt b/ps2xStudio/CMakeLists.txt index f662322b..7d739a27 100644 --- a/ps2xStudio/CMakeLists.txt +++ b/ps2xStudio/CMakeLists.txt @@ -39,7 +39,7 @@ FetchContent_Declare( FetchContent_Declare( SDL2 GIT_REPOSITORY https://github.com/libsdl-org/SDL.git - GIT_TAG release-2.30.9 + GIT_TAG release-2.32.10 GIT_SHALLOW TRUE ) @@ -128,3 +128,12 @@ target_link_libraries(ps2xStudio PRIVATE if(WIN32) target_link_libraries(ps2xStudio PRIVATE SDL2::SDL2main) endif() + +include("${CMAKE_SOURCE_DIR}/ps2xRuntime/cmake/ReleaseMode.cmake") + +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + EnableFastReleaseMode(imgui_lib) + EnableFastReleaseMode(imgui_colortextedit_lib) + EnableFastReleaseMode(imgui_filedialog_lib) + EnableFastReleaseMode(ps2xStudio) +endif() diff --git a/ps2xTest/CMakeLists.txt b/ps2xTest/CMakeLists.txt index 2a3e2ccd..a429123f 100644 --- a/ps2xTest/CMakeLists.txt +++ b/ps2xTest/CMakeLists.txt @@ -59,3 +59,10 @@ target_link_libraries(ps2x_tests PRIVATE ps2_analyzer_lib ps2_runtime ) + +include("${CMAKE_SOURCE_DIR}/ps2xRuntime/cmake/ReleaseMode.cmake") + +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + EnableFastReleaseMode(ps2_test_lib) + EnableFastReleaseMode(ps2x_tests) +endif() diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 1bc55eb0..16f5a021 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -204,6 +204,202 @@ void register_code_generator_tests() t.IsTrue(generated.find("goto label_2004;") != std::string::npos, "branch to delay slot should use goto"); }); + tc.Run("control-flow analysis keeps same-function JAL target internal and promotes only the return pc", [](TestCase &t) { + Function func; + func.name = "same_function_call"; + func.start = 0x1000; + func.end = 0x101C; + func.isRecompiled = true; + func.isStub = false; + + std::vector instructions; + instructions.push_back(makeJal(0x1000, 0x100C)); + instructions.push_back(makeNop(0x1004)); + instructions.push_back(makeNop(0x1008)); + instructions.push_back(makeNop(0x100C)); + instructions.push_back(makeNop(0x1010)); + instructions.push_back(makeJr(0x1014, 31)); + instructions.push_back(makeNop(0x1018)); + + std::vector functions{func}; + std::vector
sections = { + {".text", 0x1000u, 0x100u, 0u, true, false, false, true, nullptr} + }; + + CodeGenerator gen({}, sections); + CodeGenerator::AnalysisResult analysis = + gen.collectInternalBranchTargets(func, instructions, &functions); + + t.IsTrue(analysis.entryPoints.contains(0x100Cu), + "same-function JAL target should stay as an internal label target"); + t.IsTrue(analysis.resumeEntryPoints.contains(0x1008u), + "same-function JAL should mark the fallthrough pc as resumable"); + t.IsFalse(analysis.externalEntryPoints.contains(0x100Cu), + "same-function JAL target should not become an external entry candidate"); + }); + + tc.Run("control-flow analysis reports cross-function mid-function jumps as external entry candidates", [](TestCase &t) { + Function caller; + caller.name = "caller"; + caller.start = 0x4000; + caller.end = 0x4008; + caller.isRecompiled = true; + caller.isStub = false; + + Function target; + target.name = "target"; + target.start = 0x5000; + target.end = 0x5010; + target.isRecompiled = true; + target.isStub = false; + + Instruction j{}; + j.address = 0x4000; + j.opcode = OPCODE_J; + j.target = (0x5004u >> 2) & 0x3FFFFFFu; + j.hasDelaySlot = true; + j.raw = (OPCODE_J << 26) | (j.target & 0x3FFFFFFu); + + std::vector instructions{j, makeNop(0x4004)}; + std::vector functions{caller, target}; + std::vector
sections = { + {".text", 0x4000u, 0x2000u, 0u, true, false, false, true, nullptr} + }; + + CodeGenerator gen({}, sections); + CodeGenerator::AnalysisResult analysis = + gen.collectInternalBranchTargets(caller, instructions, &functions); + + t.IsTrue(analysis.externalEntryPoints.contains(0x5004u), + "cross-function jump into the middle of a function should become an external entry candidate"); + }); + + tc.Run("control-flow analysis promotes JALR fallthrough as a resumable entry", [](TestCase &t) { + Function func; + func.name = "jalr_resume"; + func.start = 0x1200; + func.end = 0x1218; + func.isRecompiled = true; + func.isStub = false; + + std::vector instructions; + instructions.push_back(makeNop(0x1200)); + instructions.push_back(makeJalr(0x1204, 2, 31)); + instructions.push_back(makeNop(0x1208)); + instructions.push_back(makeNop(0x120C)); + instructions.push_back(makeJr(0x1210, 31)); + instructions.push_back(makeNop(0x1214)); + + std::vector functions{func}; + std::vector
sections = { + {".text", 0x1200u, 0x100u, 0u, true, false, false, true, nullptr} + }; + + CodeGenerator gen({}, sections); + CodeGenerator::AnalysisResult analysis = + gen.collectInternalBranchTargets(func, instructions, &functions); + + t.IsTrue(analysis.resumeEntryPoints.contains(0x120Cu), + "JALR should mark its return/fallthrough pc as resumable"); + t.IsTrue(analysis.entryPoints.contains(0x120Cu), + "JALR resume pc should also be emitted as an internal label"); + }); + + tc.Run("resume entry targets emit a top-level pc switch in the owner wrapper", [](TestCase &t) { + Function func; + func.name = "resume_owner"; + func.start = 0x6000; + func.end = 0x6010; + func.isRecompiled = true; + func.isStub = false; + + std::vector instructions{ + makeNop(0x6000), + makeNop(0x6004), + makeNop(0x6008), + makeNop(0x600C) + }; + + CodeGenerator gen({}, {}); + gen.setResumeEntryTargets({{0x6000u, {0x6008u}}}); + + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("resume entry targets emit a top-level pc switch in the owner wrapper", generated); + + t.IsTrue(generated.find("switch (ctx->pc)") != std::string::npos, + "owner wrapper should dispatch resumable pcs with a switch"); + t.IsTrue(generated.find("case 0x6008u: goto label_6008;") != std::string::npos, + "resume pc should jump directly to the internal label"); + t.IsTrue(generated.find("label_6008:") != std::string::npos, + "resume pc should force label emission for that instruction"); + }); + + tc.Run("resume entry targets register to the owner wrapper", [](TestCase &t) { + Function func; + func.name = "resume_owner"; + func.start = 0x7000; + func.end = 0x7010; + func.isRecompiled = true; + func.isStub = false; + + CodeGenerator gen({}, {}); + gen.setRenamedFunctions({{0x7000u, "resume_owner_0x7000"}}); + gen.setResumeEntryTargets({{0x7000u, {0x7008u, 0x700Cu}}}); + + std::string registration = gen.generateFunctionRegistration({func}, {}); + printGeneratedCode("resume entry targets register to the owner wrapper", registration); + + t.IsTrue(registration.find("runtime.registerFunction(0x7008, resume_owner_0x7000);") != std::string::npos, + "resume entry pc should register to the owner wrapper"); + t.IsTrue(registration.find("runtime.registerFunction(0x700c, resume_owner_0x7000);") != std::string::npos, + "multiple resume pcs should register to the same owner wrapper"); + }); + + tc.Run("external mid-function entry can register to the owner wrapper", [](TestCase &t) { + Function caller; + caller.name = "caller"; + caller.start = 0x4000; + caller.end = 0x4008; + caller.isRecompiled = true; + caller.isStub = false; + + Function owner; + owner.name = "owner"; + owner.start = 0x5000; + owner.end = 0x5010; + owner.isRecompiled = true; + owner.isStub = false; + + Instruction j{}; + j.address = 0x4000; + j.opcode = OPCODE_J; + j.target = (0x5004u >> 2) & 0x3FFFFFFu; + j.hasDelaySlot = true; + j.raw = (OPCODE_J << 26) | (j.target & 0x3FFFFFFu); + + std::vector callerInstructions{j, makeNop(0x4004)}; + std::vector functions{caller, owner}; + std::vector
sections = { + {".text", 0x4000u, 0x2000u, 0u, true, false, false, true, nullptr} + }; + + CodeGenerator gen({}, sections); + CodeGenerator::AnalysisResult analysis = + gen.collectInternalBranchTargets(caller, callerInstructions, &functions); + + t.IsTrue(analysis.externalEntryPoints.contains(0x5004u), + "cross-function jump should identify the mid-function target as externally reachable"); + + gen.setRenamedFunctions({{0x5000u, "owner_0x5000"}}); + gen.setResumeEntryTargets({{0x5000u, {0x5004u}}}); + + std::string registration = gen.generateFunctionRegistration({owner}, {}); + printGeneratedCode("external mid-function entry can register to the owner wrapper", registration); + + t.IsTrue(registration.find("runtime.registerFunction(0x5004, owner_0x5000);") != std::string::npos, + "mid-function external entry should register back to the owner wrapper"); + }); + tc.Run("branches outside function still set pc", [](TestCase &t) { Function func; func.name = "external_branch"; @@ -810,6 +1006,30 @@ void register_code_generator_tests() t.IsTrue(generated.find("goto label_c010;") != std::string::npos, "Internal JAL should use goto"); }); + tc.Run("backward internal JAL stays inline and does not return to dispatcher", [](TestCase &t) { + Function func; + func.name = "jal_internal_backward"; + func.start = 0xC100; + func.end = 0xC120; + func.isRecompiled = true; + func.isStub = false; + + Instruction loopHead = makeNop(0xC100); + Instruction backwardJal = makeJal(0xC108, 0xC100); + Instruction delay = makeNop(0xC10C); + + CodeGenerator gen({}, {}); + std::string generated = gen.generateFunction(func, {loopHead, backwardJal, delay}, false); + printGeneratedCode("backward internal JAL stays inline and does not return to dispatcher", generated); + + t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xC110u);") != std::string::npos, + "backward internal JAL should still set RA"); + t.IsTrue(generated.find("goto label_c100;") != std::string::npos, + "backward internal JAL should still re-enter the internal target directly"); + t.IsTrue(generated.find("if (runtime->shouldPreemptGuestExecution())") == std::string::npos, + "backward internal JAL should not expose a mid-call dispatcher return"); + }); + tc.Run("JALR emits indirect call", [](TestCase &t) { Function func; func.name = "jalr_test"; @@ -838,7 +1058,7 @@ void register_code_generator_tests() t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC"); }); - tc.Run("backward BEQ yields cooperatively on sign-extended internal loop", [](TestCase &t) { + tc.Run("backward BEQ returns to the dispatcher through a resumable loop head", [](TestCase &t) { Function func; func.name = "backward_branch"; func.start = 0x1100; @@ -847,31 +1067,43 @@ void register_code_generator_tests() func.isStub = false; // 0x1100: nop - // 0x1104: beq $1,$1, target 0x1100 (offset = -2 words) - // 0x1108: nop (delay) + // 0x1104: nop (loop head) + // 0x1108: beq $1,$1, target 0x1104 (offset = -2 words) + // 0x110c: nop (delay) std::vector instructions; instructions.push_back(makeNop(0x1100)); + instructions.push_back(makeNop(0x1104)); - Instruction br = makeBranch(0x1104, 0); - br.simmediate = static_cast(static_cast(-2)); + Instruction br = makeBranch(0x1108, 0); + br.simmediate = static_cast(static_cast(-2)); instructions.push_back(br); - instructions.push_back(makeNop(0x1108)); instructions.push_back(makeNop(0x110c)); + instructions.push_back(makeNop(0x1110)); CodeGenerator gen({}, {}); - std::string generated = gen.generateFunction(func, instructions, false); - printGeneratedCode("backward BEQ yields cooperatively on sign-extended internal loop", generated); + CodeGenerator::AnalysisResult analysis = gen.collectInternalBranchTargets(func, instructions); + t.IsTrue(analysis.resumeEntryPoints.contains(0x1104u), + "mid-function backward loop head should be resumable so preemption can return to the dispatcher"); - t.IsTrue(generated.find("label_1100:") != std::string::npos, "target should emit a label"); - t.IsTrue(generated.find("ctx->pc = 0x1100u;") != std::string::npos, + std::string generated = gen.generateFunction(func, instructions, false); + printGeneratedCode("backward BEQ returns to the dispatcher through a resumable loop head", generated); + + t.IsTrue(generated.find("label_1104:") != std::string::npos, "target should emit a label"); + t.IsTrue(generated.find("switch (ctx->pc)") != std::string::npos, + "resumable backward loop heads should emit a wrapper resume switch"); + t.IsTrue(generated.find("case 0x1104u: goto label_1104;") != std::string::npos, + "mid-function backward loop head should be re-enterable after returning to the dispatcher"); + t.IsTrue(generated.find("ctx->pc = 0x1104u;") != std::string::npos, "backward internal branch should preserve the loop target in ctx->pc"); - t.IsTrue(generated.find("runtime->cooperativeGuestYield();") != std::string::npos, - "backward internal branch should yield guest execution before re-entering the loop"); - t.IsTrue(generated.find("goto label_1100;") != std::string::npos, - "backward internal branch should re-enter the in-function label after yielding"); - t.IsTrue(generated.find("ctx->pc = 0x1100u;\n return;") == std::string::npos, - "backward internal branch should not return to the dispatcher for a non-entry internal label"); + t.IsTrue(generated.find("if (runtime->shouldPreemptGuestExecution()) {") != std::string::npos, + "backward internal branch should consult the runtime preemption policy before re-entering the loop"); + t.IsTrue(generated.find("return;") != std::string::npos, + "backward internal branch should return to the dispatcher when the runtime preemption policy requests it"); + t.IsTrue(generated.find("runtime->cooperativeGuestYield();") == std::string::npos, + "backward internal branch should no longer emit an unconditional cooperative-yield call"); + t.IsTrue(generated.find("goto label_1104;") != std::string::npos, + "backward internal branch should still re-enter the in-function label when it keeps the current slice"); }); tc.Run("branch-likely places delay slot only in taken path", [](TestCase &t) { @@ -987,6 +1219,8 @@ void register_code_generator_tests() t.IsTrue(generated.find("switch (jumpTarget)") != std::string::npos, "JR via non-RA register should emit switch for internal targets"); + t.IsTrue(generated.find("switch (ctx->pc)") == std::string::npos, + "JR fallback labels should not be promoted to dispatcher resume sites"); t.IsTrue(generated.find("case 0x1400u: goto label_1400;") != std::string::npos, "switch should include in-function entry label"); t.IsTrue(generated.find("case 0x140Cu: goto label_140c;") != std::string::npos, @@ -1053,6 +1287,15 @@ void register_code_generator_tests() CodeGenerator gen({}, {}); gen.setConfiguredJumpTables({configured}); + CodeGenerator::AnalysisResult analysis = gen.collectInternalBranchTargets( + func, + {lui, addiu, sll, addu, lw, jr, jrDelay, target0, target1}); + + t.IsFalse(analysis.resumeEntryPoints.contains(0x1620u), + "configured JR table targets should stay in-function dispatch labels"); + t.IsFalse(analysis.resumeEntryPoints.contains(0x1630u), + "configured JR table targets should not become dispatcher resume sites"); + std::string generated = gen.generateFunction( func, {lui, addiu, sll, addu, lw, jr, jrDelay, target0, target1}, @@ -1065,6 +1308,8 @@ void register_code_generator_tests() "configured table target 0x1620 should be emitted"); t.IsTrue(generated.find("case 0x1630u: goto label_1630;") != std::string::npos, "configured table target 0x1630 should be emitted"); + t.IsTrue(generated.find("switch (ctx->pc)") == std::string::npos, + "configured JR table labels should not emit a top-level dispatcher resume switch"); t.IsTrue(generated.find("case 0x1600u: goto label_1600;") == std::string::npos, "configured table should avoid broad JR fallback labels"); }); diff --git a/ps2xTest/src/main.cpp b/ps2xTest/src/main.cpp index 8f19d9cf..5a5da810 100644 --- a/ps2xTest/src/main.cpp +++ b/ps2xTest/src/main.cpp @@ -1,4 +1,6 @@ #include "MiniTest.h" +#include +#include void register_code_generator_tests(); void register_r5900_decoder_tests(); @@ -29,5 +31,8 @@ int main() register_ps2_sif_dma_tests(); register_ps2_recompiler_tests(); register_ps2_runtime_expansion_tests(); - return MiniTest::Run(); + int res = MiniTest::Run(); + std::cout.flush(); + std::cerr.flush(); + std::_Exit(res); } diff --git a/ps2xTest/src/pad_input_tests.cpp b/ps2xTest/src/pad_input_tests.cpp index b3a18f07..4d973527 100644 --- a/ps2xTest/src/pad_input_tests.cpp +++ b/ps2xTest/src/pad_input_tests.cpp @@ -1,10 +1,13 @@ #include "MiniTest.h" #include "ps2_stubs.h" +#include "ps2_syscalls.h" +#include "Stubs/Pad.h" #include #include #include + namespace { constexpr uint32_t kPadDataAddr = 0x1000; @@ -31,8 +34,25 @@ namespace ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); } + void openPadPort(R5900Context &ctx, std::vector &rdram, uint32_t port = 0, uint32_t slot = 0) + { + setRegU32(ctx, 4, port); + setRegU32(ctx, 5, slot); + setRegU32(ctx, 6, kPadDataAddr + 0x200u); + ps2_stubs::scePadPortOpen(rdram.data(), &ctx, nullptr); + } + + void closePadPort(R5900Context &ctx, std::vector &rdram, uint32_t port = 0, uint32_t slot = 0) + { + setRegU32(ctx, 4, port); + setRegU32(ctx, 5, slot); + ps2_stubs::scePadPortClose(rdram.data(), &ctx, nullptr); + } + void runPadRead(R5900Context &ctx, std::vector &rdram) { + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); setRegU32(ctx, 6, kPadDataAddr); // a2 ps2_stubs::scePadRead(rdram.data(), &ctx, nullptr); } @@ -53,6 +73,9 @@ void register_pad_input_tests() std::vector rdram(PS2_RAM_SIZE, 0); R5900Context ctx; + ps2_stubs::scePadInit(rdram.data(), &ctx, nullptr); + openPadPort(ctx, rdram); + const uint16_t buttons = static_cast(0xFFFFu & ~kPadBtnCross & ~kPadBtnStart); ps2_stubs::setPadOverrideState(buttons, 0x00, 0xFF, 0x10, 0xEE); @@ -67,6 +90,7 @@ void register_pad_input_tests() t.Equals(data[7], static_cast(0xFF), "ly should match override"); ps2_stubs::clearPadOverrideState(); + closePadPort(ctx, rdram); }); tc.Run("scePadRead button bits are active-low", [](TestCase &t) @@ -74,6 +98,9 @@ void register_pad_input_tests() std::vector rdram(PS2_RAM_SIZE, 0); R5900Context ctx; + ps2_stubs::scePadInit(rdram.data(), &ctx, nullptr); + openPadPort(ctx, rdram); + struct ButtonCase { uint16_t mask; @@ -104,11 +131,13 @@ void register_pad_input_tests() ps2_stubs::setPadOverrideState(buttons, 0x80, 0x80, 0x80, 0x80); runPadRead(ctx, rdram); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadRead should succeed for opened ports"); const uint16_t mask = readButtons(rdram); t.IsTrue((mask & entry.mask) == 0, std::string("button should be active-low: ").append(entry.name)); } ps2_stubs::clearPadOverrideState(); + closePadPort(ctx, rdram); }); tc.Run("scePadGetButtonMask returns all buttons", [](TestCase &t) @@ -120,34 +149,50 @@ void register_pad_input_tests() tc.Run("basic pad init/port/state functions return expected values", [](TestCase &t) { + std::vector rdram(PS2_RAM_SIZE, 0); R5900Context ctx; - ps2_stubs::scePadInit(nullptr, &ctx, nullptr); + ps2_stubs::scePadInit(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInit should succeed"); - ps2_stubs::scePadInit2(nullptr, &ctx, nullptr); + ps2_stubs::scePadInit2(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInit2 should succeed"); - ps2_stubs::scePadPortOpen(nullptr, &ctx, nullptr); - t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadPortOpen should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + ps2_stubs::scePadGetState(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "closed port should report DISCONNECTED"); - ps2_stubs::scePadPortClose(nullptr, &ctx, nullptr); - t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadPortClose should succeed"); + openPadPort(ctx, rdram); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadPortOpen should succeed"); - ps2_stubs::scePadGetState(nullptr, &ctx, nullptr); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + ps2_stubs::scePadGetState(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(6), "scePadGetState should return STABLE"); - ps2_stubs::scePadGetReqState(nullptr, &ctx, nullptr); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + ps2_stubs::scePadGetReqState(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadGetReqState should return completed"); - ps2_stubs::scePadGetPortMax(nullptr, &ctx, nullptr); + ps2_stubs::scePadGetPortMax(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(2), "scePadGetPortMax should be 2"); - ps2_stubs::scePadGetSlotMax(nullptr, &ctx, nullptr); + setRegU32(ctx, 4, 0u); + ps2_stubs::scePadGetSlotMax(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadGetSlotMax should be 1"); - ps2_stubs::scePadGetModVersion(nullptr, &ctx, nullptr); + ps2_stubs::scePadGetModVersion(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0x0200), "scePadGetModVersion should be 0x0200"); + + closePadPort(ctx, rdram); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadPortClose should succeed"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + ps2_stubs::scePadGetState(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "closed port should return DISCONNECTED after close"); }); tc.Run("pad info and mode helpers return consistent values", [](TestCase &t) @@ -155,24 +200,64 @@ void register_pad_input_tests() std::vector rdram(PS2_RAM_SIZE, 0); R5900Context ctx; + ps2_stubs::scePadInit(rdram.data(), &ctx, nullptr); + openPadPort(ctx, rdram); + + setRegU32(ctx, 6, static_cast(-1)); ps2_stubs::scePadInfoAct(rdram.data(), &ctx, nullptr); - t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadInfoAct should return 0"); + t.IsTrue(static_cast(getRegU32(&ctx, 2)) >= 1u, "scePadInfoAct should report at least one actuator descriptor"); ps2_stubs::scePadInfoComb(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadInfoComb should return 0"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); setRegU32(ctx, 6, 1); setRegU32(ctx, 7, 0); ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(7), "scePadInfoMode CURID should return DualShock"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); setRegU32(ctx, 6, 4); setRegU32(ctx, 7, static_cast(-1)); ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr); - t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInfoMode table count should be 1"); + t.IsTrue(static_cast(getRegU32(&ctx, 2)) >= 1u, "scePadInfoMode table count should be non-zero"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadInfoPressMode(rdram.data(), &ctx, nullptr); - t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadInfoPressMode should be 0"); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadInfoPressMode should report pressure support"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 0u); + setRegU32(ctx, 7, 3u); + ps2_stubs::scePadSetMainMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetMainMode should accept digital mode"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 1u); + setRegU32(ctx, 7, 0u); + ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(4), "CURID should switch to digital mode"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 1u); + setRegU32(ctx, 7, 3u); + ps2_stubs::scePadSetMainMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetMainMode should accept analog mode"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 4); + setRegU32(ctx, 7, 0u); + ps2_stubs::scePadInfoMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(7), "mode table entry should return DualShock in analog mode"); + + closePadPort(ctx, rdram); }); tc.Run("pad setters return success", [](TestCase &t) @@ -180,35 +265,124 @@ void register_pad_input_tests() std::vector rdram(PS2_RAM_SIZE, 0); R5900Context ctx; + ps2_stubs::scePadInit(rdram.data(), &ctx, nullptr); + openPadPort(ctx, rdram); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadSetActAlign(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetActAlign should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadSetActDirect(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetActDirect should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 0xFFFFu); ps2_stubs::scePadSetButtonInfo(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetButtonInfo should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 1u); + setRegU32(ctx, 7, 3u); ps2_stubs::scePadSetMainMode(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetMainMode should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadSetReqState(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetReqState should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadSetVrefParam(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetVrefParam should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadSetWarningLevel(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(0), "scePadSetWarningLevel should return 0"); ps2_stubs::scePadEnd(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadEnd should succeed"); + openPadPort(ctx, rdram); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadEnterPressMode(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadEnterPressMode should succeed"); + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); ps2_stubs::scePadExitPressMode(rdram.data(), &ctx, nullptr); t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadExitPressMode should succeed"); + + closePadPort(ctx, rdram); + }); + + tc.Run("scePadRead fills pressure bytes and honors button info mask", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0); + R5900Context ctx; + + ps2_stubs::scePadInit(rdram.data(), &ctx, nullptr); + openPadPort(ctx, rdram); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 0xFFFFu); + ps2_stubs::scePadSetButtonInfo(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetButtonInfo should accept all buttons"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + ps2_stubs::scePadEnterPressMode(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadEnterPressMode should enable pressure data"); + + const uint16_t pressedButtons = static_cast(0xFFFFu & + ~kPadBtnLeft & + ~kPadBtnUp & + ~kPadBtnTriangle & + ~kPadBtnCross & + ~kPadBtnL1 & + ~kPadBtnR2); + ps2_stubs::setPadOverrideState(pressedButtons, 0x80, 0x80, 0x80, 0x80); + runPadRead(ctx, rdram); + + const uint8_t *data = rdram.data() + kPadDataAddr; + t.Equals(data[8], static_cast(0x00), "right pressure should be clear when not pressed"); + t.Equals(data[9], static_cast(0xFF), "left pressure should be populated when pressed"); + t.Equals(data[10], static_cast(0xFF), "up pressure should be populated when pressed"); + t.Equals(data[11], static_cast(0x00), "down pressure should be clear when not pressed"); + t.Equals(data[12], static_cast(0xFF), "triangle pressure should be populated when pressed"); + t.Equals(data[13], static_cast(0x00), "circle pressure should be clear when not pressed"); + t.Equals(data[14], static_cast(0xFF), "cross pressure should be populated when pressed"); + t.Equals(data[15], static_cast(0x00), "square pressure should be clear when not pressed"); + t.Equals(data[16], static_cast(0xFF), "L1 pressure should be populated when pressed"); + t.Equals(data[17], static_cast(0x00), "L2 pressure should be clear when not pressed"); + t.Equals(data[18], static_cast(0x00), "R1 pressure should be clear when not pressed"); + t.Equals(data[19], static_cast(0xFF), "R2 pressure should be populated when pressed"); + + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, static_cast(kPadBtnL1 | kPadBtnR2)); + ps2_stubs::scePadSetButtonInfo(rdram.data(), &ctx, nullptr); + t.Equals(static_cast(getRegU32(&ctx, 2)), static_cast(1), "scePadSetButtonInfo should narrow the enabled pressure mask"); + + runPadRead(ctx, rdram); + + t.Equals(data[9], static_cast(0x00), "masked-out direction pressure should clear"); + t.Equals(data[10], static_cast(0x00), "masked-out direction pressure should clear"); + t.Equals(data[12], static_cast(0x00), "masked-out face-button pressure should clear"); + t.Equals(data[14], static_cast(0x00), "masked-out face-button pressure should clear"); + t.Equals(data[16], static_cast(0xFF), "enabled L1 pressure should remain populated"); + t.Equals(data[19], static_cast(0xFF), "enabled R2 pressure should remain populated"); + + ps2_stubs::clearPadOverrideState(); + closePadPort(ctx, rdram); }); tc.Run("pad string helpers map state codes", [](TestCase &t) diff --git a/ps2xTest/src/ps2_gs_tests.cpp b/ps2xTest/src/ps2_gs_tests.cpp index d5c00f06..536b1ccc 100644 --- a/ps2xTest/src/ps2_gs_tests.cpp +++ b/ps2xTest/src/ps2_gs_tests.cpp @@ -1,10 +1,14 @@ #include "MiniTest.h" -#include "ps2_memory.h" +#include "runtime/ps2_memory.h" #include "ps2_runtime.h" #include "ps2_stubs.h" #include "ps2_syscalls.h" -#include "ps2_gs_gpu.h" -#include "ps2_gs_psmt4.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_gs_psmct32.h" +#include "runtime/ps2_gs_psmt4.h" +#include "runtime/ps2_gs_psmt8.h" +#include "Stubs/Helpers/Support.h" +#include "Stubs/GS.h" #include #include @@ -20,6 +24,8 @@ namespace std::atomic g_gsSyncCallbackHits{0u}; std::atomic g_gsSyncCallbackLastTick{0u}; + static_assert(sizeof(GsImageMem) == 12, "GsImageMem size mismatch"); + void setRegU32(R5900Context &ctx, int reg, uint32_t value) { ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); @@ -79,6 +85,206 @@ namespace g_gsSyncCallbackHits.fetch_add(1u, std::memory_order_relaxed); ctx->pc = 0u; } + + void writeGsImageTest(uint8_t *rdram, uint32_t addr, const GsImageMem &image) + { + std::memcpy(rdram + addr, &image, sizeof(image)); + } + + void writeGsImageTest(std::vector &rdram, uint32_t addr, const GsImageMem &image) + { + writeGsImageTest(rdram.data(), addr, image); + } + + void writePSMT4Texel(std::vector &vram, uint32_t tbp, uint32_t tbw, uint32_t x, uint32_t y, uint8_t index) + { + const uint32_t nibbleAddr = GSPSMT4::addrPSMT4(tbp, tbw, x, y); + const uint32_t byteOff = nibbleAddr >> 1; + uint8_t &packed = vram[byteOff]; + if ((nibbleAddr & 1u) != 0u) + { + packed = static_cast((packed & 0x0Fu) | ((index & 0x0Fu) << 4)); + } + else + { + packed = static_cast((packed & 0xF0u) | (index & 0x0Fu)); + } + } + + uint32_t referenceAddrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + static constexpr uint8_t kBlockTable4[8][4] = { + {0, 2, 8, 10}, + {1, 3, 9, 11}, + {4, 6, 12, 14}, + {5, 7, 13, 15}, + {16, 18, 24, 26}, + {17, 19, 25, 27}, + {20, 22, 28, 30}, + {21, 23, 29, 31}, + }; + + static constexpr uint16_t kColumnTable4[16][32] = { + {0, 8, 32, 40, 64, 72, 96, 104, 2, 10, 34, 42, 66, 74, 98, 106, 4, 12, 36, 44, 68, 76, 100, 108, 6, 14, 38, 46, 70, 78, 102, 110}, + {16, 24, 48, 56, 80, 88, 112, 120, 18, 26, 50, 58, 82, 90, 114, 122, 20, 28, 52, 60, 84, 92, 116, 124, 22, 30, 54, 62, 86, 94, 118, 126}, + {65, 73, 97, 105, 1, 9, 33, 41, 67, 75, 99, 107, 3, 11, 35, 43, 69, 77, 101, 109, 5, 13, 37, 45, 71, 79, 103, 111, 7, 15, 39, 47}, + {81, 89, 113, 121, 17, 25, 49, 57, 83, 91, 115, 123, 19, 27, 51, 59, 85, 93, 117, 125, 21, 29, 53, 61, 87, 95, 119, 127, 23, 31, 55, 63}, + {192, 200, 224, 232, 128, 136, 160, 168, 194, 202, 226, 234, 130, 138, 162, 170, 196, 204, 228, 236, 132, 140, 164, 172, 198, 206, 230, 238, 134, 142, 166, 174}, + {208, 216, 240, 248, 144, 152, 176, 184, 210, 218, 242, 250, 146, 154, 178, 186, 212, 220, 244, 252, 148, 156, 180, 188, 214, 222, 246, 254, 150, 158, 182, 190}, + {129, 137, 161, 169, 193, 201, 225, 233, 131, 139, 163, 171, 195, 203, 227, 235, 133, 141, 165, 173, 197, 205, 229, 237, 135, 143, 167, 175, 199, 207, 231, 239}, + {145, 153, 177, 185, 209, 217, 241, 249, 147, 155, 179, 187, 211, 219, 243, 251, 149, 157, 181, 189, 213, 221, 245, 253, 151, 159, 183, 191, 215, 223, 247, 255}, + {256, 264, 288, 296, 320, 328, 352, 360, 258, 266, 290, 298, 322, 330, 354, 362, 260, 268, 292, 300, 324, 332, 356, 364, 262, 270, 294, 302, 326, 334, 358, 366}, + {272, 280, 304, 312, 336, 344, 368, 376, 274, 282, 306, 314, 338, 346, 370, 378, 276, 284, 308, 316, 340, 348, 372, 380, 278, 286, 310, 318, 342, 350, 374, 382}, + {321, 329, 353, 361, 257, 265, 289, 297, 323, 331, 355, 363, 259, 267, 291, 299, 325, 333, 357, 365, 261, 269, 293, 301, 327, 335, 359, 367, 263, 271, 295, 303}, + {337, 345, 369, 377, 273, 281, 305, 313, 339, 347, 371, 379, 275, 283, 307, 315, 341, 349, 373, 381, 277, 285, 309, 317, 343, 351, 375, 383, 279, 287, 311, 319}, + {448, 456, 480, 488, 384, 392, 416, 424, 450, 458, 482, 490, 386, 394, 418, 426, 452, 460, 484, 492, 388, 396, 420, 428, 454, 462, 486, 494, 390, 398, 422, 430}, + {464, 472, 496, 504, 400, 408, 432, 440, 466, 474, 498, 506, 402, 410, 434, 442, 468, 476, 500, 508, 404, 412, 436, 444, 470, 478, 502, 510, 406, 414, 438, 446}, + {385, 393, 417, 425, 449, 457, 481, 489, 387, 395, 419, 427, 451, 459, 483, 491, 389, 397, 421, 429, 453, 461, 485, 493, 391, 399, 423, 431, 455, 463, 487, 495}, + {401, 409, 433, 441, 465, 473, 497, 505, 403, 411, 435, 443, 467, 475, 499, 507, 405, 413, 437, 445, 469, 477, 501, 509, 407, 415, 439, 447, 471, 479, 503, 511}, + }; + + const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u; + const uint32_t page = (block >> 5u) + (y >> 7u) * pagesPerRow + (x >> 7u); + const uint32_t blockId = (block & 0x1Fu) + kBlockTable4[(y >> 4u) & 7u][(x >> 5u) & 3u]; + const uint32_t pageOffset = (blockId >> 5u) << 14u; + const uint32_t localBlock = blockId & 0x1Fu; + return (page << 14u) + pageOffset + localBlock * 512u + kColumnTable4[y & 0x0Fu][x & 0x1Fu]; + } + + void writeReferencePSMT4Texel(std::vector &vram, uint32_t tbp, uint32_t tbw, uint32_t x, uint32_t y, uint8_t index) + { + const uint32_t nibbleAddr = referenceAddrPSMT4(tbp, tbw, x, y); + const uint32_t byteOff = nibbleAddr >> 1; + uint8_t &packed = vram[byteOff]; + if ((nibbleAddr & 1u) != 0u) + { + packed = static_cast((packed & 0x0Fu) | ((index & 0x0Fu) << 4)); + } + else + { + packed = static_cast((packed & 0xF0u) | (index & 0x0Fu)); + } + } + + uint32_t referenceAddrPSMT8(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + static constexpr uint8_t kBlockTable8[4][8] = { + {0, 1, 4, 5, 16, 17, 20, 21}, + {2, 3, 6, 7, 18, 19, 22, 23}, + {8, 9, 12, 13, 24, 25, 28, 29}, + {10, 11, 14, 15, 26, 27, 30, 31}, + }; + + static constexpr uint8_t kColumnTable8[16][16] = { + {0, 4, 16, 20, 32, 36, 48, 52, 2, 6, 18, 22, 34, 38, 50, 54}, + {8, 12, 24, 28, 40, 44, 56, 60, 10, 14, 26, 30, 42, 46, 58, 62}, + {33, 37, 49, 53, 1, 5, 17, 21, 35, 39, 51, 55, 3, 7, 19, 23}, + {41, 45, 57, 61, 9, 13, 25, 29, 43, 47, 59, 63, 11, 15, 27, 31}, + {96, 100, 112, 116, 64, 68, 80, 84, 98, 102, 114, 118, 66, 70, 82, 86}, + {104, 108, 120, 124, 72, 76, 88, 92, 106, 110, 122, 126, 74, 78, 90, 94}, + {65, 69, 81, 85, 97, 101, 113, 117, 67, 71, 83, 87, 99, 103, 115, 119}, + {73, 77, 89, 93, 105, 109, 121, 125, 75, 79, 91, 95, 107, 111, 123, 127}, + {128, 132, 144, 148, 160, 164, 176, 180, 130, 134, 146, 150, 162, 166, 178, 182}, + {136, 140, 152, 156, 168, 172, 184, 188, 138, 142, 154, 158, 170, 174, 186, 190}, + {161, 165, 177, 181, 129, 133, 145, 149, 163, 167, 179, 183, 131, 135, 147, 151}, + {169, 173, 185, 189, 137, 141, 153, 157, 171, 175, 187, 191, 139, 143, 155, 159}, + {224, 228, 240, 244, 192, 196, 208, 212, 226, 230, 242, 246, 194, 198, 210, 214}, + {232, 236, 248, 252, 200, 204, 216, 220, 234, 238, 250, 254, 202, 206, 218, 222}, + {193, 197, 209, 213, 225, 229, 241, 245, 195, 199, 211, 215, 227, 231, 243, 247}, + {201, 205, 217, 221, 233, 237, 249, 253, 203, 207, 219, 223, 235, 239, 251, 255}, + }; + + const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u; + const uint32_t page = (block >> 5u) + (y >> 6u) * pagesPerRow + (x >> 7u); + const uint32_t blockId = (block & 0x1Fu) + kBlockTable8[(y >> 4u) & 3u][(x >> 4u) & 7u]; + const uint32_t pageOffset = (blockId >> 5u) << 13u; + const uint32_t localBlock = blockId & 0x1Fu; + return (page << 13u) + pageOffset + localBlock * 256u + kColumnTable8[y & 0x0Fu][x & 0x0Fu]; + } + + uint32_t referenceAddrPSMCT32(uint32_t block, uint32_t width, uint32_t x, uint32_t y) + { + static constexpr uint8_t kBlockTable32[4][8] = { + {0, 1, 4, 5, 16, 17, 20, 21}, + {2, 3, 6, 7, 18, 19, 22, 23}, + {8, 9, 12, 13, 24, 25, 28, 29}, + {10, 11, 14, 15, 26, 27, 30, 31}, + }; + + static constexpr uint8_t kColumnTable32[8][8] = { + {0, 1, 4, 5, 8, 9, 12, 13}, + {2, 3, 6, 7, 10, 11, 14, 15}, + {16, 17, 20, 21, 24, 25, 28, 29}, + {18, 19, 22, 23, 26, 27, 30, 31}, + {32, 33, 36, 37, 40, 41, 44, 45}, + {34, 35, 38, 39, 42, 43, 46, 47}, + {48, 49, 52, 53, 56, 57, 60, 61}, + {50, 51, 54, 55, 58, 59, 62, 63}, + }; + + const uint32_t pagesPerRow = (width != 0u) ? width : 1u; + const uint32_t page = (block >> 5u) + (y >> 5u) * pagesPerRow + (x >> 6u); + const uint32_t blockId = (block & 0x1Fu) + kBlockTable32[(y >> 3u) & 3u][(x >> 3u) & 7u]; + const uint32_t pageOffset = (blockId >> 5u) << 13u; + const uint32_t localBlock = blockId & 0x1Fu; + return (page << 13u) + pageOffset + localBlock * 256u + + static_cast(kColumnTable32[y & 0x7u][x & 0x7u]) * 4u; + } + + void writeReferencePSMCT32Pixel(std::vector &vram, + uint32_t fbp, + uint32_t fbw, + uint32_t x, + uint32_t y, + uint32_t pixel) + { + const uint32_t off = referenceAddrPSMCT32(fbp, (fbw != 0u) ? fbw : 1u, x, y); + std::memcpy(vram.data() + off, &pixel, sizeof(pixel)); + } + + uint32_t readReferencePSMCT32Pixel(const std::vector &vram, + uint32_t fbp, + uint32_t fbw, + uint32_t x, + uint32_t y) + { + const uint32_t off = referenceAddrPSMCT32(fbp, (fbw != 0u) ? fbw : 1u, x, y); + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data() + off, sizeof(pixel)); + return pixel; + } + + uint32_t frameBaseToBlock(uint32_t fbp) + { + return fbp << 5u; + } + + void writeReferenceFramePSMCT32Pixel(std::vector &vram, + uint32_t fbp, + uint32_t fbw, + uint32_t x, + uint32_t y, + uint32_t pixel) + { + writeReferencePSMCT32Pixel(vram, frameBaseToBlock(fbp), fbw, x, y, pixel); + } + + uint32_t readReferenceFramePSMCT32Pixel(const std::vector &vram, + uint32_t fbp, + uint32_t fbw, + uint32_t x, + uint32_t y) + { + return readReferencePSMCT32Pixel(vram, frameBaseToBlock(fbp), fbw, x, y); + } + + void expectGuestHeapReusable(TestCase &t, PS2Runtime &runtime, const char *message) + { + const uint32_t expectedBase = runtime.guestHeapBase(); + const uint32_t probe = runtime.guestMalloc(16u, 16u); + t.Equals(probe, expectedBase, message); + runtime.guestFree(probe); + } } void register_ps2_gs_tests() @@ -167,6 +373,29 @@ void register_ps2_gs_tests() t.Equals(currentImr, 0x3333444411112222ull, "GsGetIMR should return current GS IMR"); }); + tc.Run("GsSetCrt updates SMODE2 for host presentation mode", [](TestCase &t) + { + PS2Runtime runtime; + t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed"); + + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + setRegU32(ctx, 4, 1u); // interlaced + setRegU32(ctx, 5, 0u); // NTSC + setRegU32(ctx, 6, 0u); // field mode + + runtime.memory().gs().pmode = 0u; + runtime.memory().gs().smode2 = 0u; + GsSetCrt(rdram.data(), &ctx, &runtime); + + t.Equals(runtime.memory().gs().smode2, 0x1ull, + "GsSetCrt should publish interlaced field mode through SMODE2"); + t.Equals(runtime.memory().gs().pmode & 0x3ull, 0x1ull, + "GsSetCrt should leave CRT1 enabled for presentation"); + t.Equals(getRegU32Test(ctx, 2), 0u, + "GsSetCrt should return success"); + }); + tc.Run("sceGsSetDefDBuffDc seeds display envs and swap applies the selected page", [](TestCase &t) { PS2Runtime runtime; @@ -232,238 +461,1460 @@ void register_ps2_gs_tests() "sceGsSwapDBuffDc should preserve the display width from the seeded env"); }); - tc.Run("GIF PACKED A+D writes DISPFB1 and DISPLAY1 privileged registers", [](TestCase &t) + tc.Run("sceGsSetDefDBuffDc seeds a clear packet and swap clears the draw buffer", [](TestCase &t) { - std::vector vram(PS2_GS_VRAM_SIZE, 0u); - GSRegisters regs{}; - GS gs; - gs.init(vram.data(), static_cast(vram.size()), ®s); + PS2Runtime runtime; + t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed"); - std::vector packet; - appendU64(packet, makeGifTag(2u, GIF_FMT_PACKED, 1u, true)); - appendU64(packet, 0x0Eull); // REGS[0] = A+D + std::vector rdram(PS2_RAM_SIZE, 0u); + constexpr uint32_t kEnvAddr = 0x5000u; + constexpr uint32_t kDBuffSize = 0x330u; + constexpr uint32_t kClear0Offset = 0x160u; + constexpr uint32_t kTestAAddrOffset = kClear0Offset + 0x08u; + constexpr uint32_t kPrimAddrOffset = kClear0Offset + 0x18u; + constexpr uint32_t kRgbaqOffset = kClear0Offset + 0x20u; + constexpr uint32_t kRgbaqAddrOffset = kClear0Offset + 0x28u; + constexpr uint32_t kXyz2AAddrOffset = kClear0Offset + 0x38u; + constexpr uint32_t kXyz2BAddrOffset = kClear0Offset + 0x48u; + constexpr uint32_t kTestBAddrOffset = kClear0Offset + 0x58u; + constexpr uint32_t kClearColor = 0x80402010u; + constexpr uint32_t kStackAddr = 0x900u; + const uint32_t kZTest = 2u; + const uint32_t kEnableClear = 1u; - const uint64_t dispfb1 = 0x0123456789ABCDEFull; - const uint64_t display1 = 0x1111222233334444ull; - appendU64(packet, dispfb1); - appendU64(packet, 0x59ull); // DISPFB1 - appendU64(packet, display1); - appendU64(packet, 0x5Aull); // DISPLAY1 + R5900Context ctx{}; + setRegU32(ctx, 4, kEnvAddr); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 640u); + setRegU32(ctx, 7, 448u); + setRegU32(ctx, 29, kStackAddr); + std::memset(rdram.data() + kEnvAddr, 0xCD, kDBuffSize); + std::memcpy(rdram.data() + kStackAddr + 16u, &kZTest, sizeof(kZTest)); + std::memcpy(rdram.data() + kStackAddr + 24u, &kEnableClear, sizeof(kEnableClear)); + std::memset(runtime.memory().getGSVRAM(), 0xAB, 16u); + ps2_stubs::sceGsSetDefDBuffDc(rdram.data(), &ctx, &runtime); - gs.processGIFPacket(packet.data(), static_cast(packet.size())); + uint64_t testAAddr = 0u; + uint64_t primAddr = 0u; + uint64_t rgbaqAddr = 0u; + uint64_t xyz2AAddr = 0u; + uint64_t xyz2BAddr = 0u; + uint64_t testBAddr = 0u; + std::memcpy(&testAAddr, rdram.data() + kEnvAddr + kTestAAddrOffset, sizeof(testAAddr)); + std::memcpy(&primAddr, rdram.data() + kEnvAddr + kPrimAddrOffset, sizeof(primAddr)); + std::memcpy(&rgbaqAddr, rdram.data() + kEnvAddr + kRgbaqAddrOffset, sizeof(rgbaqAddr)); + std::memcpy(&xyz2AAddr, rdram.data() + kEnvAddr + kXyz2AAddrOffset, sizeof(xyz2AAddr)); + std::memcpy(&xyz2BAddr, rdram.data() + kEnvAddr + kXyz2BAddrOffset, sizeof(xyz2BAddr)); + std::memcpy(&testBAddr, rdram.data() + kEnvAddr + kTestBAddrOffset, sizeof(testBAddr)); + + t.Equals(testAAddr, 0x47ull, "dbuff clear packet should program TEST_1 before clearing"); + t.Equals(primAddr, 0x00ull, "dbuff clear packet should program PRIM before clearing"); + t.Equals(rgbaqAddr, 0x01ull, "dbuff clear packet should expose RGBAQ for runtime color updates"); + t.Equals(xyz2AAddr, 0x05ull, "dbuff clear packet should seed the first clear vertex as XYZ2"); + t.Equals(xyz2BAddr, 0x05ull, "dbuff clear packet should seed the second clear vertex as XYZ2"); + t.Equals(testBAddr, 0x47ull, "dbuff clear packet should restore TEST_1 after clearing"); + + uint64_t rgbaq = static_cast(kClearColor); + std::memcpy(rdram.data() + kEnvAddr + kRgbaqOffset, &rgbaq, sizeof(rgbaq)); - t.Equals(regs.dispfb1, dispfb1, "A+D should write GS DISPFB1"); - t.Equals(regs.display1, display1, "A+D should write GS DISPLAY1"); + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kEnvAddr); + setRegU32(ctx, 5, 0u); + ps2_stubs::sceGsSwapDBuffDc(rdram.data(), &ctx, &runtime); + + uint32_t clearedPixel = 0u; + std::memcpy(&clearedPixel, runtime.memory().getGSVRAM(), sizeof(clearedPixel)); + t.Equals(clearedPixel, kClearColor, + "sceGsSwapDBuffDc should execute the seeded clear packet against the active draw buffer"); + + constexpr uint32_t kMidX = 320u; + constexpr uint32_t kMidY = 200u; + const uint32_t kMidOffset = ((kMidY * 640u) + kMidX) * 4u; + uint32_t clearedMidPixel = 0u; + std::memcpy(&clearedMidPixel, runtime.memory().getGSVRAM() + kMidOffset, sizeof(clearedMidPixel)); + t.Equals(clearedMidPixel, kClearColor, + "sceGsSwapDBuffDc should clear the interior of the active draw buffer, not just the first pixel"); }); - tc.Run("PSMT4 address mapping matches Veronica Conv4to32 layout", [](TestCase &t) + tc.Run("sceGsSetDefDBuffDc accepts trailing args from the recompiler register ABI", [](TestCase &t) { - constexpr uint32_t kBaseBlock = 0u; - constexpr uint32_t kWidth = 2u; // One 128x128 PSMT4 page. + PS2Runtime runtime; + t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 0u), 0u, - "PSMT4 origin should map to nibble offset 0"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 1u, 0u), 8u, - "PSMT4 x=1 should advance to the next packed nibble group"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 1u), 512u, - "PSMT4 second source row should land on the next CT32 row stride"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 2u), 33u, - "PSMT4 third source row should keep Veronica's interleaved ordering"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 3u), 545u, - "PSMT4 fourth source row should include both interleave and CT32 row stride"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 31u, 15u), 3647u, - "PSMT4 final texel in the first 32x16 block should match Veronica's block layout"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 32u, 0u), 4096u, - "PSMT4 x=32 should advance to the next CT32 block column"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 32u, 16u), 4160u, - "PSMT4 x=32,y=16 should include both block-column and block-row offsets"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 64u, 0u), 8192u, - "PSMT4 x=64 should advance to the third block column in the page"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 96u, 112u), 12736u, - "PSMT4 bottom-right block origin should match Veronica's page permutation"); - t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 127u, 127u), 16383u, - "PSMT4 final texel in a 128x128 page should land at the end of the page"); + std::vector rdram(PS2_RAM_SIZE, 0u); + constexpr uint32_t kEnvAddr = 0x5400u; + constexpr uint32_t kDBuffSize = 0x330u; + constexpr uint32_t kClear0Offset = 0x160u; + constexpr uint32_t kRgbaqOffset = kClear0Offset + 0x20u; + constexpr uint32_t kRgbaqAddrOffset = kClear0Offset + 0x28u; + constexpr uint32_t kStackAddr = 0xA00u; + constexpr uint32_t kClearColor = 0x40201008u; + + R5900Context ctx{}; + setRegU32(ctx, 4, kEnvAddr); + setRegU32(ctx, 5, 0u); + setRegU32(ctx, 6, 640u); + setRegU32(ctx, 7, 448u); + setRegU32(ctx, 8, 2u); + setRegU32(ctx, 9, 58u); + setRegU32(ctx, 10, 1u); + setRegU32(ctx, 29, kStackAddr); + std::memset(rdram.data() + kEnvAddr, 0xCD, kDBuffSize); + std::memset(runtime.memory().getGSVRAM(), 0xAB, 16u); + + ps2_stubs::sceGsSetDefDBuffDc(rdram.data(), &ctx, &runtime); + + uint64_t rgbaqAddr = 0u; + std::memcpy(&rgbaqAddr, rdram.data() + kEnvAddr + kRgbaqAddrOffset, sizeof(rgbaqAddr)); + t.Equals(rgbaqAddr, 0x01ull, + "sceGsSetDefDBuffDc should seed the clear packet when trailing args arrive in t0-t2"); + + const uint64_t rgbaq = static_cast(kClearColor); + std::memcpy(rdram.data() + kEnvAddr + kRgbaqOffset, &rgbaq, sizeof(rgbaq)); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kEnvAddr); + setRegU32(ctx, 5, 0u); + ps2_stubs::sceGsSwapDBuffDc(rdram.data(), &ctx, &runtime); + + uint32_t clearedPixel = 0u; + std::memcpy(&clearedPixel, runtime.memory().getGSVRAM(), sizeof(clearedPixel)); + t.Equals(clearedPixel, kClearColor, + "sceGsSwapDBuffDc should honor a clear packet seeded from register-based trailing args"); }); - tc.Run("GIF REGLIST with odd register count consumes 128-bit padding before next tag", [](TestCase &t) + tc.Run("clearFramebufferContext clears the requested context even if another context is active", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - const uint64_t bitblt = - (static_cast(0u) << 0) | - (static_cast(1u) << 16) | - (static_cast(0u) << 24) | - (static_cast(0u) << 32) | - (static_cast(1u) << 48) | - (static_cast(0u) << 56); - gs.writeRegister(GS_REG_BITBLTBUF, bitblt); - gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); - gs.writeRegister(GS_REG_TRXDIR, 0ull); + constexpr uint32_t kCtx0Color = 0x11223344u; + constexpr uint32_t kCtx1Sentinel = 0xAABBCCDDu; - std::vector packet; - appendU64(packet, makeGifTag(1u, GIF_FMT_REGLIST, 1u, false)); - appendU64(packet, 0x0ull); // REGS[0] = PRIM - appendU64(packet, 0x0000000000000006ull); // PRIM write - appendU64(packet, 0xDEADBEEFCAFEBABEull); // required REGLIST pad qword + gs.writeRegister(GS_REG_FRAME_1, (1ull << 16)); // FBP=0, FBW=1, PSMCT32 + gs.writeRegister(GS_REG_SCISSOR_1, (0ull << 0) | (0ull << 16) | (1ull << 32) | (1ull << 48)); + gs.writeRegister(GS_REG_FRAME_2, 150ull | (1ull << 16)); + gs.writeRegister(GS_REG_SCISSOR_2, (0ull << 0) | (0ull << 16) | (1ull << 32) | (1ull << 48)); + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT) | (1ull << 9)); - appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packet, 0ull); - const uint8_t payload[16] = { - 0x31u, 0x32u, 0x33u, 0x34u, - 0x35u, 0x36u, 0x37u, 0x38u, - 0x39u, 0x3Au, 0x3Bu, 0x3Cu, - 0x3Du, 0x3Eu, 0x3Fu, 0x40u, - }; - packet.insert(packet.end(), payload, payload + sizeof(payload)); + writeReferenceFramePSMCT32Pixel(vram, 150u, 1u, 0u, 1u, kCtx1Sentinel); - gs.processGIFPacket(packet.data(), static_cast(packet.size())); + t.IsTrue(gs.clearFramebufferContext(0u, kCtx0Color), + "context-targeted clear should succeed for a configured CT32 framebuffer"); - bool imageOk = true; - for (uint32_t i = 0; i < 16u; ++i) - { - if (vram[i] != payload[i]) - { - imageOk = false; - break; - } - } - t.IsTrue(imageOk, "odd REGLIST payload should not corrupt alignment of the following IMAGE tag"); + const uint32_t ctx0Pixel = readReferenceFramePSMCT32Pixel(vram, 0u, 1u, 0u, 1u); + t.Equals(ctx0Pixel, kCtx0Color, + "context-targeted clear should write the requested context even when PRIM.ctxt points elsewhere"); + + const uint32_t ctx1Pixel = readReferenceFramePSMCT32Pixel(vram, 150u, 1u, 0u, 1u); + t.Equals(ctx1Pixel, kCtx1Sentinel, + "context-targeted clear should leave the other context framebuffer untouched"); }); - tc.Run("GIF REGLIST NREG=0 is treated as sixteen descriptors", [](TestCase &t) + tc.Run("PABE bypasses alpha blend for low-alpha source pixels", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - const uint64_t bitblt = - (static_cast(0u) << 0) | - (static_cast(1u) << 16) | - (static_cast(0u) << 24) | - (static_cast(0u) << 32) | - (static_cast(1u) << 48) | - (static_cast(0u) << 56); - gs.writeRegister(GS_REG_BITBLTBUF, bitblt); - gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); - gs.writeRegister(GS_REG_TRXDIR, 0ull); - - std::vector packet; - appendU64(packet, makeGifTag(1u, GIF_FMT_REGLIST, 0u, false)); // NREG=0 -> 16 regs - appendU64(packet, 0ull); // 16x PRIM descriptors - for (uint32_t i = 0; i < 16u; ++i) - { - appendU64(packet, static_cast(i)); - } + gs.writeRegister(GS_REG_FRAME_1, (1ull << 16)); // FBW=1, PSMCT32, FBP=0 + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0x6000000064ull); + gs.writeRegister(GS_REG_TEST_1, 0x30000ull); + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT) | (1ull << 6)); - appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packet, 0ull); - const uint8_t payload[16] = { - 0x51u, 0x52u, 0x53u, 0x54u, - 0x55u, 0x56u, 0x57u, 0x58u, - 0x59u, 0x5Au, 0x5Bu, 0x5Cu, - 0x5Du, 0x5Eu, 0x5Fu, 0x60u, - }; - packet.insert(packet.end(), payload, payload + sizeof(payload)); + const uint32_t dstWhite = 0xFFFFFFFFu; + std::memcpy(vram.data(), &dstWhite, sizeof(dstWhite)); - gs.processGIFPacket(packet.data(), static_cast(packet.size())); + gs.writeRegister(GS_REG_PABE, 0ull); + gs.writeRegister(GS_REG_RGBAQ, 0x01000000ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); - bool imageOk = true; - for (uint32_t i = 0; i < 16u; ++i) - { - if (vram[i] != payload[i]) - { - imageOk = false; - break; - } - } - t.IsTrue(imageOk, "NREG=0 REGLIST should consume 16 data words and keep following tag aligned"); - }); + uint32_t blendedPixel = 0u; + std::memcpy(&blendedPixel, vram.data(), sizeof(blendedPixel)); + t.Equals(blendedPixel, 0x013F3F3Fu, + "without PABE, low-alpha fullscreen copies should still apply ALPHA blending"); - tc.Run("GS SIGNAL and FINISH set CSR bits that clear by CSR write-one acknowledge", [](TestCase &t) - { - PS2Memory mem; - t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + std::memcpy(vram.data(), &dstWhite, sizeof(dstWhite)); - GS gs; - gs.init(mem.getGSVRAM(), static_cast(PS2_GS_VRAM_SIZE), &mem.gs()); + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT) | (1ull << 6)); + gs.writeRegister(GS_REG_PABE, 1ull); + gs.writeRegister(GS_REG_RGBAQ, 0x01000000ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); - const uint64_t signalValue = (0xFFFFFFFFull << 32) | 0x11223344ull; - gs.writeRegister(GS_REG_SIGNAL, signalValue); - gs.writeRegister(GS_REG_FINISH, 0u); + uint32_t pabeBypassedPixel = 0u; + std::memcpy(&pabeBypassedPixel, vram.data(), sizeof(pabeBypassedPixel)); + t.Equals(pabeBypassedPixel, 0x01000000u, + "with PABE enabled, low-alpha source pixels should bypass ALPHA blending and overwrite the destination"); - t.IsTrue((mem.gs().csr & 0x1ull) != 0ull, "SIGNAL should raise CSR.SIGNAL"); - t.IsTrue((mem.gs().csr & 0x2ull) != 0ull, "FINISH should raise CSR.FINISH"); - t.Equals(static_cast(mem.gs().siglblid & 0xFFFFFFFFull), 0x11223344u, "SIGNAL should update SIGLBLID low dword"); + std::memcpy(vram.data(), &dstWhite, sizeof(dstWhite)); - mem.write64(0x12001000u, 0x1ull); - t.IsTrue((mem.gs().csr & 0x1ull) == 0ull, "writing CSR bit0 should acknowledge SIGNAL"); - t.IsTrue((mem.gs().csr & 0x2ull) != 0ull, "acknowledging SIGNAL should not clear FINISH"); + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT) | (1ull << 6)); + gs.writeRegister(GS_REG_PABE, 1ull); + gs.writeRegister(GS_REG_RGBAQ, 0x80000000ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); - mem.write32(0x12001000u, 0x2u); - t.IsTrue((mem.gs().csr & 0x2ull) == 0ull, "writing CSR bit1 should acknowledge FINISH"); + uint32_t highAlphaPixel = 0u; + std::memcpy(&highAlphaPixel, vram.data(), sizeof(highAlphaPixel)); + t.Equals(highAlphaPixel, 0x803F3F3Fu, + "with PABE enabled, high-alpha source pixels should still use the configured ALPHA blend"); }); - tc.Run("GIF IMAGE packet writes host-to-local data into GS VRAM", [](TestCase &t) + tc.Run("FBA forces the framebuffer alpha high bit on CT32 writes", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - // Setup for host->local transfer to DBP=0, DBW=1, PSMCT32, rect 2x2. - const uint64_t bitblt = - (static_cast(0u) << 0) | // SBP - (static_cast(1u) << 16) | // SBW - (static_cast(0u) << 24) | // SPSM - (static_cast(0u) << 32) | // DBP - (static_cast(1u) << 48) | // DBW - (static_cast(0u) << 56); // DPSM (CT32) - gs.writeRegister(GS_REG_BITBLTBUF, bitblt); - gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, (2ull << 0) | (2ull << 32)); - gs.writeRegister(GS_REG_TRXDIR, 0ull); + gs.writeRegister(GS_REG_FRAME_1, (1ull << 16)); + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); - std::vector packet; - appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packet, 0ull); + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT)); + gs.writeRegister(GS_REG_FBA_1, 0ull); + gs.writeRegister(GS_REG_RGBAQ, 0x01112233ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); - const uint8_t payload[16] = { - 0x10u, 0x11u, 0x12u, 0x13u, - 0x20u, 0x21u, 0x22u, 0x23u, - 0x30u, 0x31u, 0x32u, 0x33u, - 0x40u, 0x41u, 0x42u, 0x43u, - }; - packet.insert(packet.end(), payload, payload + sizeof(payload)); + uint32_t pixelWithoutFba = 0u; + std::memcpy(&pixelWithoutFba, vram.data(), sizeof(pixelWithoutFba)); + t.Equals(pixelWithoutFba, 0x01112233u, + "without FBA, CT32 writes should preserve the source alpha byte"); - gs.processGIFPacket(packet.data(), static_cast(packet.size())); + std::memset(vram.data(), 0, sizeof(uint32_t)); - bool same = true; - for (size_t i = 0; i < 8u; ++i) - { - if (vram[i] != payload[i] || vram[256u + i] != payload[8u + i]) - { - same = false; - break; - } - } - t.IsTrue(same, "GIF IMAGE transfer should write payload bytes into GS VRAM"); + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT)); + gs.writeRegister(GS_REG_FBA_1, 1ull); + gs.writeRegister(GS_REG_RGBAQ, 0x01112233ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + + uint32_t pixelWithFba = 0u; + std::memcpy(&pixelWithFba, vram.data(), sizeof(pixelWithFba)); + t.Equals(pixelWithFba, 0x81112233u, + "with FBA enabled, CT32 writes should force the framebuffer alpha high bit"); }); - tc.Run("GS local-to-host transfer supports partial incremental reads", [](TestCase &t) + tc.Run("CT32 raster writes alias cleanly into later CT32 texture sampling", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - for (uint32_t i = 0; i < 16u; ++i) - { - vram[i] = static_cast(0xA0u + i); - } - - const uint64_t bitblt = + constexpr uint64_t kFrame1 = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor1 = + (0ull << 0) | + (1ull << 16) | + (0ull << 32) | + (1ull << 48); + constexpr uint64_t kFrame2 = + (150ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor2 = 0ull; + constexpr uint64_t kTex0_2 = + (0ull << 0) | + (1ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (0ull << 26) | + (1ull << 30) | + (1ull << 34) | + (1ull << 35); + constexpr uint64_t kPointPrim = static_cast(GS_PRIM_POINT); + constexpr uint64_t kCopyPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8) | + (1ull << 9); + constexpr uint64_t kSourceColor = 0x80665544ull; + constexpr uint64_t kPointXyz = + (0ull << 0) | + (16ull << 16); + constexpr uint64_t kUvRow1 = + (0ull << 0) | + (16ull << 16); + + gs.writeRegister(GS_REG_FRAME_1, kFrame1); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor1); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_PRIM, kPointPrim); + gs.writeRegister(GS_REG_RGBAQ, kSourceColor); + gs.writeRegister(GS_REG_XYZ2, kPointXyz); + + gs.writeRegister(GS_REG_FRAME_2, kFrame2); + gs.writeRegister(GS_REG_SCISSOR_2, kScissor2); + gs.writeRegister(GS_REG_XYOFFSET_2, 0ull); + gs.writeRegister(GS_REG_TEST_2, 0ull); + gs.writeRegister(GS_REG_TEX0_2, kTex0_2); + gs.writeRegister(GS_REG_PRIM, kCopyPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, kUvRow1); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, kUvRow1); + gs.writeRegister(GS_REG_XYZ2, 0ull); + + const uint32_t dstPixel = readReferenceFramePSMCT32Pixel(vram, 150u, 1u, 0u, 0u); + t.Equals(dstPixel, static_cast(kSourceColor), + "CT32 primitives should land in the same local-memory layout that later CT32 texture sampling expects"); + }); + + tc.Run("FST sprite 1:1 CT32 copies preserve source texels at the right and bottom edges", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kTexTbp = 64u; + constexpr uint64_t kFrame = + (150ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (3ull << 16) | + (0ull << 32) | + (3ull << 48); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (2ull << 26) | + (2ull << 30) | + (1ull << 34); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8); + constexpr uint64_t kXyz0 = 0ull; + constexpr uint64_t kXyz1 = + (static_cast(4u << 4) << 0) | + (static_cast(4u << 4) << 16); + constexpr uint64_t kUv0 = 0ull; + constexpr uint64_t kUv1 = + ((4ull * 16ull) << 0) | + ((4ull * 16ull) << 16); + constexpr uint32_t kSourcePixels[4] = { + 0x800000FFu, + 0x8000FF00u, + 0x80FF0000u, + 0x80FFFFFFu, + }; + + for (uint32_t y = 0u; y < 4u; ++y) + { + for (uint32_t x = 0u; x < 4u; ++x) + { + writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, x, y, kSourcePixels[x]); + } + } + + gs.writeRegister(GS_REG_FRAME_1, kFrame); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_TEX1_1, 0ull); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, kUv0); + gs.writeRegister(GS_REG_XYZ2, kXyz0); + gs.writeRegister(GS_REG_UV, kUv1); + gs.writeRegister(GS_REG_XYZ2, kXyz1); + + for (uint32_t y = 0u; y < 4u; ++y) + { + for (uint32_t x = 0u; x < 4u; ++x) + { + const uint32_t pixel = readReferenceFramePSMCT32Pixel(vram, 150u, 1u, x, y); + t.Equals(pixel, kSourcePixels[x], + "1:1 FST sprite copies should preserve each source texel without off-by-one edge skew"); + } + } + }); + + tc.Run("fullscreen display copy tracks the preferred presentation source frame", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint64_t kFrame2 = + 150ull | + (10ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor2 = + (0ull << 0) | + (639ull << 16) | + (0ull << 32) | + (479ull << 48); + constexpr uint64_t kXYOffset2 = + (static_cast(1728u << 4) << 0) | + (static_cast(1808u << 4) << 32); + constexpr uint64_t kAlpha2 = 0x6000000064ull; + constexpr uint64_t kTest2 = 0x30000ull; + constexpr uint64_t kTex0_2 = + (0ull << 0) | + (10ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (10ull << 26) | + (9ull << 30) | + (1ull << 34); + constexpr uint64_t kPrimCopy = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 6) | + (1ull << 8) | + (1ull << 9); + constexpr uint64_t kXyz0 = + (static_cast(1728u << 4) << 0) | + (static_cast(1808u << 4) << 16) | + (256ull << 32); + constexpr uint64_t kXyz1 = + (static_cast(2368u << 4) << 0) | + (static_cast(2288u << 4) << 16) | + (256ull << 32); + constexpr uint64_t kUv0 = + (8ull << 0) | + (8ull << 16); + constexpr uint64_t kUv1 = + ((8ull + (640ull * 16ull)) << 0) | + ((8ull + (480ull * 16ull)) << 16); + + gs.writeRegister(GS_REG_FRAME_2, kFrame2); + gs.writeRegister(GS_REG_SCISSOR_2, kScissor2); + gs.writeRegister(GS_REG_XYOFFSET_2, kXYOffset2); + gs.writeRegister(GS_REG_ALPHA_2, kAlpha2); + gs.writeRegister(GS_REG_TEST_2, kTest2); + gs.writeRegister(GS_REG_TEX0_2, kTex0_2); + gs.writeRegister(GS_REG_PRIM, kPrimCopy); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, kUv0); + gs.writeRegister(GS_REG_XYZ2, kXyz0); + gs.writeRegister(GS_REG_UV, kUv1); + gs.writeRegister(GS_REG_XYZ2, kXyz1); + + GSFrameReg preferredSource{}; + uint32_t preferredDestFbp = 0u; + t.IsTrue(gs.getPreferredDisplaySource(preferredSource, preferredDestFbp), + "fullscreen display-copy sprites should record their source frame for host presentation"); + t.Equals(preferredDestFbp, 150u, + "preferred presentation tracking should target the copied display page"); + t.Equals(preferredSource.fbp, 0u, + "preferred presentation tracking should expose the copy source frame base"); + t.Equals(preferredSource.fbw, 10u, + "preferred presentation tracking should expose the copy source width"); + t.Equals(static_cast(preferredSource.psm), static_cast(GS_PSM_CT32), + "preferred presentation tracking should expose the copy source format"); + + gs.writeRegister(GS_REG_PRIM, static_cast(GS_PRIM_POINT) | (1ull << 9)); + gs.writeRegister(GS_REG_RGBAQ, 0xFFFFFFFFull); + gs.writeRegister(GS_REG_XYZ2, kXyz0); + + t.IsFalse(gs.getPreferredDisplaySource(preferredSource, preferredDestFbp), + "non-copy primitives that touch the display target should invalidate the preferred presentation source"); + }); + + tc.Run("latched host presentation frame stays stable until the next latch", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 1ull; + regs.dispfb1 = + 150ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (447ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kDisplayPixel = 0x00332211u; + constexpr uint32_t kSourcePixel = 0x00665544u; + constexpr uint32_t kUpdatedSourcePixel = 0x00998877u; + writeReferenceFramePSMCT32Pixel(vram, 150u, 10u, 0u, 0u, kDisplayPixel); + std::memcpy(vram.data() + 0u, &kSourcePixel, sizeof(kSourcePixel)); + + constexpr uint64_t kFrame2 = + 150ull | + (10ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor2 = + (0ull << 0) | + (639ull << 16) | + (0ull << 32) | + (479ull << 48); + constexpr uint64_t kXYOffset2 = + (static_cast(1728u << 4) << 0) | + (static_cast(1808u << 4) << 32); + constexpr uint64_t kAlpha2 = 0x6000000064ull; + constexpr uint64_t kTest2 = 0x30000ull; + constexpr uint64_t kTex0_2 = + (0ull << 0) | + (10ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (10ull << 26) | + (9ull << 30) | + (1ull << 34); + constexpr uint64_t kPrimCopy = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 6) | + (1ull << 8) | + (1ull << 9); + constexpr uint64_t kXyz0 = + (static_cast(1728u << 4) << 0) | + (static_cast(1808u << 4) << 16) | + (256ull << 32); + constexpr uint64_t kXyz1 = + (static_cast(2368u << 4) << 0) | + (static_cast(2288u << 4) << 16) | + (256ull << 32); + constexpr uint64_t kUv0 = + (8ull << 0) | + (8ull << 16); + constexpr uint64_t kUv1 = + ((8ull + (640ull * 16ull)) << 0) | + ((8ull + (480ull * 16ull)) << 16); + + gs.writeRegister(GS_REG_FRAME_2, kFrame2); + gs.writeRegister(GS_REG_SCISSOR_2, kScissor2); + gs.writeRegister(GS_REG_XYOFFSET_2, kXYOffset2); + gs.writeRegister(GS_REG_ALPHA_2, kAlpha2); + gs.writeRegister(GS_REG_TEST_2, kTest2); + gs.writeRegister(GS_REG_TEX0_2, kTex0_2); + gs.writeRegister(GS_REG_PRIM, kPrimCopy); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, kUv0); + gs.writeRegister(GS_REG_XYZ2, kXyz0); + gs.writeRegister(GS_REG_UV, kUv1); + gs.writeRegister(GS_REG_XYZ2, kXyz1); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + uint32_t displayFbp = 0u; + uint32_t sourceFbp = 0u; + bool usedPreferred = false; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, + latchedWidth, + latchedHeight, + &displayFbp, + &sourceFbp, + &usedPreferred), + "host presentation latch should produce a readable frame"); + t.Equals(displayFbp, 150u, + "latched host presentation should remember the selected display page"); + t.Equals(sourceFbp, 0u, + "latched host presentation should switch to the fullscreen copy source"); + t.IsTrue(usedPreferred, + "latched host presentation should record when it used the preferred copy source"); + t.Equals(latchedWidth, 640u, + "latched host presentation should preserve display width"); + t.Equals(latchedHeight, 448u, + "latched host presentation should preserve display height"); + t.Equals(static_cast(latchedFrame[0]), 0x44u, + "latched host presentation should expose the source frame RGB data"); + t.Equals(static_cast(latchedFrame[1]), 0x55u, + "latched host presentation should preserve the source frame green channel"); + t.Equals(static_cast(latchedFrame[2]), 0x66u, + "latched host presentation should preserve the source frame blue channel"); + t.Equals(static_cast(latchedFrame[3]), 0xFFu, + "latched host presentation should normalize framebuffer alpha for host upload"); + + std::memcpy(vram.data() + 0u, &kUpdatedSourcePixel, sizeof(kUpdatedSourcePixel)); + + std::vector staleFrame; + uint32_t staleWidth = 0u; + uint32_t staleHeight = 0u; + t.IsTrue(gs.copyLatchedHostPresentationFrame(staleFrame, staleWidth, staleHeight), + "latched host presentation should remain readable without relatching"); + t.Equals(static_cast(staleFrame[0]), 0x44u, + "latched host presentation should stay stable until the next latch"); + + gs.latchHostPresentationFrame(); + + std::vector refreshedFrame; + uint32_t refreshedWidth = 0u; + uint32_t refreshedHeight = 0u; + t.IsTrue(gs.copyLatchedHostPresentationFrame(refreshedFrame, refreshedWidth, refreshedHeight), + "latched host presentation should refresh after a new latch"); + t.Equals(static_cast(refreshedFrame[0]), 0x77u, + "relatching should pick up the updated source frame contents"); + }); + + tc.Run("latched host presentation frame is returned tightly packed for narrower display widths", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 1ull; + regs.dispfb1 = + 150ull | + (1ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (63ull << 32) | + (63ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kTopLeft = 0xFF332211u; + constexpr uint32_t kTopRight = 0xFF665544u; + constexpr uint32_t kBottomLeft = 0xFF998877u; + constexpr uint32_t kBottomRight = 0xFFCCBBAAu; + writeReferenceFramePSMCT32Pixel(vram, 150u, 1u, 0u, 0u, kTopLeft); + writeReferenceFramePSMCT32Pixel(vram, 150u, 1u, 1u, 0u, kTopRight); + writeReferenceFramePSMCT32Pixel(vram, 150u, 1u, 0u, 1u, kBottomLeft); + writeReferenceFramePSMCT32Pixel(vram, 150u, 1u, 1u, 1u, kBottomRight); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, latchedWidth, latchedHeight), + "latched host presentation should be readable for narrow display widths"); + t.Equals(latchedWidth, 64u, + "latched host presentation should preserve the decoded display width"); + t.Equals(latchedHeight, 64u, + "latched host presentation should preserve the decoded display height"); + t.Equals(static_cast(latchedFrame.size()), 64u * 64u * 4u, + "latched host presentation should return a tightly packed RGBA buffer"); + + uint32_t pixel = 0u; + std::memcpy(&pixel, latchedFrame.data() + 0u, sizeof(pixel)); + t.Equals(pixel, kTopLeft, + "latched host presentation should keep the first row intact"); + std::memcpy(&pixel, latchedFrame.data() + 4u, sizeof(pixel)); + t.Equals(pixel, kTopRight, + "latched host presentation should pack the first row contiguously"); + std::memcpy(&pixel, latchedFrame.data() + (64u * 4u), sizeof(pixel)); + t.Equals(pixel, kBottomLeft, + "latched host presentation should start the second row immediately after the first"); + std::memcpy(&pixel, latchedFrame.data() + (64u * 4u) + 4u, sizeof(pixel)); + t.Equals(pixel, kBottomRight, + "latched host presentation should preserve subsequent rows without the internal 640-pixel stride"); + }); + + tc.Run("latched host presentation reads preferred CT32 source with GS swizzle", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 1ull; + regs.dispfb1 = + 150ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (447ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kSourceTbp0 = 64u; + constexpr uint32_t kSourcePixelRow1 = 0x00665544u; + constexpr uint32_t kDisplayPixelRow1 = 0x00CCBBAAu; + const uint32_t swizzledSourceOff = GSPSMCT32::addrPSMCT32(kSourceTbp0, 10u, 0u, 1u); + std::memcpy(vram.data() + swizzledSourceOff, &kSourcePixelRow1, sizeof(kSourcePixelRow1)); + writeReferenceFramePSMCT32Pixel(vram, 150u, 10u, 0u, 1u, kDisplayPixelRow1); + + constexpr uint64_t kFrame2 = + 150ull | + (10ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor2 = + (0ull << 0) | + (639ull << 16) | + (0ull << 32) | + (479ull << 48); + constexpr uint64_t kXYOffset2 = + (static_cast(1728u << 4) << 0) | + (static_cast(1808u << 4) << 32); + constexpr uint64_t kAlpha2 = 0x6000000064ull; + constexpr uint64_t kTest2 = 0x30000ull; + constexpr uint64_t kTex0_2 = + (static_cast(kSourceTbp0) << 0) | + (10ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (10ull << 26) | + (9ull << 30) | + (1ull << 34); + constexpr uint64_t kPrimCopy = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 6) | + (1ull << 8) | + (1ull << 9); + constexpr uint64_t kXyz0 = + (static_cast(1728u << 4) << 0) | + (static_cast(1808u << 4) << 16) | + (256ull << 32); + constexpr uint64_t kXyz1 = + (static_cast(2368u << 4) << 0) | + (static_cast(2288u << 4) << 16) | + (256ull << 32); + constexpr uint64_t kUv0 = + (8ull << 0) | + (8ull << 16); + constexpr uint64_t kUv1 = + ((8ull + (640ull * 16ull)) << 0) | + ((8ull + (480ull * 16ull)) << 16); + + gs.writeRegister(GS_REG_FRAME_2, kFrame2); + gs.writeRegister(GS_REG_SCISSOR_2, kScissor2); + gs.writeRegister(GS_REG_XYOFFSET_2, kXYOffset2); + gs.writeRegister(GS_REG_ALPHA_2, kAlpha2); + gs.writeRegister(GS_REG_TEST_2, kTest2); + gs.writeRegister(GS_REG_TEX0_2, kTex0_2); + gs.writeRegister(GS_REG_PRIM, kPrimCopy); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, kUv0); + gs.writeRegister(GS_REG_XYZ2, kXyz0); + gs.writeRegister(GS_REG_UV, kUv1); + gs.writeRegister(GS_REG_XYZ2, kXyz1); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + uint32_t displayFbp = 0u; + uint32_t sourceFbp = 0u; + bool usedPreferred = false; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, + latchedWidth, + latchedHeight, + &displayFbp, + &sourceFbp, + &usedPreferred), + "preferred-source presentation should produce a host frame"); + t.IsTrue(usedPreferred, + "preferred-source presentation should use the fullscreen copy source"); + t.Equals(displayFbp, 150u, + "preferred-source presentation should still target the display page"); + t.Equals(sourceFbp, kSourceTbp0, + "preferred-source presentation should report the CT32 source frame"); + + const size_t row1Off = 640u * 4u; + t.Equals(static_cast(latchedFrame[row1Off + 0u]), 0x44u, + "preferred-source presentation should read row 1 red from the swizzled CT32 source"); + t.Equals(static_cast(latchedFrame[row1Off + 1u]), 0x55u, + "preferred-source presentation should read row 1 green from the swizzled CT32 source"); + t.Equals(static_cast(latchedFrame[row1Off + 2u]), 0x66u, + "preferred-source presentation should read row 1 blue from the swizzled CT32 source"); + t.Equals(static_cast(latchedFrame[row1Off + 3u]), 0xFFu, + "preferred-source presentation should normalize row 1 alpha for the host frame"); + }); + + tc.Run("latched host presentation reads direct CT32 display frames with GS swizzle", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 0x0001ull; + regs.dispfb1 = + 150ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (447ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kRow1Pixel = + 0x44u | + (0x55u << 8) | + (0x66u << 16) | + (0x77u << 24); + constexpr uint32_t kLinearGarbageRow1 = + 0xAAu | + (0xBBu << 8) | + (0xCCu << 16) | + (0xDDu << 24); + constexpr size_t kHostRow1Off = 640u * 4u; + + writeReferenceFramePSMCT32Pixel(vram, 150u, 10u, 0u, 1u, kRow1Pixel); + std::memcpy(vram.data() + (150u * 8192u) + kHostRow1Off, &kLinearGarbageRow1, sizeof(kLinearGarbageRow1)); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + uint32_t displayFbp = 0u; + bool usedPreferred = false; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, + latchedWidth, + latchedHeight, + &displayFbp, + nullptr, + &usedPreferred), + "direct CT32 display presentation should produce a host frame"); + t.Equals(displayFbp, 150u, + "direct CT32 presentation should report the active display page"); + t.IsFalse(usedPreferred, + "direct CT32 presentation should not claim it used a preferred copy source"); + t.Equals(static_cast(latchedFrame[kHostRow1Off + 0u]), 0x44u, + "direct CT32 presentation should read row 1 red from the GS-swizzled display page"); + t.Equals(static_cast(latchedFrame[kHostRow1Off + 1u]), 0x55u, + "direct CT32 presentation should read row 1 green from the GS-swizzled display page"); + t.Equals(static_cast(latchedFrame[kHostRow1Off + 2u]), 0x66u, + "direct CT32 presentation should read row 1 blue from the GS-swizzled display page"); + t.Equals(static_cast(latchedFrame[kHostRow1Off + 3u]), 0xFFu, + "direct CT32 presentation should normalize row 1 alpha for the host frame"); + }); + + tc.Run("latched host presentation merges both enabled PMODE circuits", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 0x8007ull; + regs.dispfb1 = + 150ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (447ull << 44); + regs.dispfb2 = + 0ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display2 = regs.display1; + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kCircuit1Pixel = + 200u | + (0u << 8) | + (0u << 16) | + (64u << 24); + constexpr uint32_t kCircuit2Pixel = + 0u | + (0u << 8) | + (200u << 16) | + (255u << 24); + + writeReferenceFramePSMCT32Pixel(vram, 150u, 10u, 0u, 0u, kCircuit1Pixel); + std::memcpy(vram.data(), &kCircuit2Pixel, sizeof(kCircuit2Pixel)); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + uint32_t displayFbp = 0u; + bool usedPreferred = false; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, + latchedWidth, + latchedHeight, + &displayFbp, + nullptr, + &usedPreferred), + "dual-circuit PMODE presentation should produce a host frame"); + t.Equals(displayFbp, 150u, + "dual-circuit presentation should still report the primary display page"); + t.IsFalse(usedPreferred, + "dual-circuit PMODE presentation should not bypass the first circuit with the preferred-copy shortcut"); + t.Equals(latchedWidth, 640u, + "dual-circuit presentation should preserve the display width"); + t.Equals(latchedHeight, 448u, + "dual-circuit presentation should preserve the display height"); + t.Equals(static_cast(latchedFrame[0]), 100u, + "dual-circuit presentation should blend the first circuit red channel over the second circuit"); + t.Equals(static_cast(latchedFrame[1]), 0u, + "dual-circuit presentation should preserve a zero green channel"); + t.Equals(static_cast(latchedFrame[2]), 100u, + "dual-circuit presentation should blend the second circuit blue channel under the first circuit"); + t.Equals(static_cast(latchedFrame[3]), 0xFFu, + "dual-circuit presentation should normalize the final host alpha"); + }); + + tc.Run("latched host presentation normalizes alpha for single-circuit display", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 0x0001ull; + regs.dispfb1 = + 150ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (447ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kPixel = + 0x22u | + (0x44u << 8) | + (0x66u << 16) | + (0x01u << 24); + writeReferenceFramePSMCT32Pixel(vram, 150u, 10u, 0u, 0u, kPixel); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, latchedWidth, latchedHeight), + "single-circuit presentation should produce a host frame"); + t.Equals(static_cast(latchedFrame[0]), 0x22u, + "single-circuit presentation should preserve the red channel"); + t.Equals(static_cast(latchedFrame[1]), 0x44u, + "single-circuit presentation should preserve the green channel"); + t.Equals(static_cast(latchedFrame[2]), 0x66u, + "single-circuit presentation should preserve the blue channel"); + t.Equals(static_cast(latchedFrame[3]), 0xFFu, + "single-circuit presentation should upload an opaque host alpha"); + }); + + tc.Run("latched host presentation preserves 480-line display height", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 0x0001ull; + regs.dispfb1 = + 150ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (479ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kLastRowPixel = + 0x12u | + (0x34u << 8) | + (0x56u << 16) | + (0x78u << 24); + writeReferenceFramePSMCT32Pixel(vram, 150u, 10u, 0u, 479u, kLastRowPixel); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, latchedWidth, latchedHeight), + "480-line single-circuit presentation should produce a host frame"); + t.Equals(latchedWidth, 640u, + "480-line presentation should preserve the display width"); + t.Equals(latchedHeight, 480u, + "480-line presentation should preserve the full display height"); + + const size_t lastRowOffset = static_cast(479u) * 640u * 4u; + t.Equals(static_cast(latchedFrame[lastRowOffset + 0u]), 0x12u, + "480-line presentation should keep the last row red channel"); + t.Equals(static_cast(latchedFrame[lastRowOffset + 1u]), 0x34u, + "480-line presentation should keep the last row green channel"); + t.Equals(static_cast(latchedFrame[lastRowOffset + 2u]), 0x56u, + "480-line presentation should keep the last row blue channel"); + t.Equals(static_cast(latchedFrame[lastRowOffset + 3u]), 0xFFu, + "single-circuit presentation should normalize the last row alpha"); + }); + + tc.Run("latched host presentation line-doubles interlaced field output", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + regs.pmode = 0x0001ull; + regs.smode2 = 0x0001ull; // interlaced, field mode + regs.dispfb1 = + 0ull | + (10ull << 9) | + (static_cast(GS_PSM_CT32) << 15); + regs.display1 = + (639ull << 32) | + (447ull << 44); + + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + constexpr uint32_t kLine0 = 0x000000FFu; + constexpr uint32_t kLine1 = 0x0000FF00u; + constexpr uint32_t kLine2 = 0x00FF0000u; + constexpr uint32_t kLine3 = 0x00FFFF00u; + writeReferencePSMCT32Pixel(vram, 0u, 10u, 0u, 0u, kLine0); + writeReferencePSMCT32Pixel(vram, 0u, 10u, 0u, 1u, kLine1); + writeReferencePSMCT32Pixel(vram, 0u, 10u, 0u, 2u, kLine2); + writeReferencePSMCT32Pixel(vram, 0u, 10u, 0u, 3u, kLine3); + + gs.latchHostPresentationFrame(); + + std::vector latchedFrame; + uint32_t latchedWidth = 0u; + uint32_t latchedHeight = 0u; + t.IsTrue(gs.copyLatchedHostPresentationFrame(latchedFrame, latchedWidth, latchedHeight), + "interlaced field presentation should produce a host frame"); + t.Equals(latchedWidth, 640u, + "field presentation should preserve display width"); + t.Equals(latchedHeight, 448u, + "field presentation should preserve display height"); + + auto pixelAtRow = [&](uint32_t row) -> uint32_t + { + const size_t off = static_cast(row) * 640u * 4u; + return static_cast(latchedFrame[off + 0u]) | + (static_cast(latchedFrame[off + 1u]) << 8) | + (static_cast(latchedFrame[off + 2u]) << 16); + }; + + const uint32_t row0 = pixelAtRow(0u); + const uint32_t row1 = pixelAtRow(1u); + const uint32_t row2 = pixelAtRow(2u); + const uint32_t row3 = pixelAtRow(3u); + + t.Equals(row0, row1, + "field presentation should duplicate the active field into the next scanline"); + t.Equals(row2, row3, + "field presentation should duplicate later field scanlines as well"); + t.IsTrue(row0 != row2, + "field presentation should still preserve different source content across field rows"); + }); + + tc.Run("GIF PACKED A+D writes DISPFB1 and DISPLAY1 privileged registers", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + std::vector packet; + appendU64(packet, makeGifTag(2u, GIF_FMT_PACKED, 1u, true)); + appendU64(packet, 0x0Eull); // REGS[0] = A+D + + const uint64_t dispfb1 = 0x0123456789ABCDEFull; + const uint64_t display1 = 0x1111222233334444ull; + appendU64(packet, dispfb1); + appendU64(packet, 0x59ull); // DISPFB1 + appendU64(packet, display1); + appendU64(packet, 0x5Aull); // DISPLAY1 + + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + t.Equals(regs.dispfb1, dispfb1, "A+D should write GS DISPFB1"); + t.Equals(regs.display1, display1, "A+D should write GS DISPLAY1"); + }); + + tc.Run("GIF PACKED A+D writes DISPFB2 and DISPLAY2 privileged registers", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GSRegisters regs{}; + GS gs; + gs.init(vram.data(), static_cast(vram.size()), ®s); + + std::vector packet; + appendU64(packet, makeGifTag(2u, GIF_FMT_PACKED, 1u, true)); + appendU64(packet, 0x0Eull); // REGS[0] = A+D + + const uint64_t dispfb2 = 0x2222333344445555ull; + const uint64_t display2 = 0x6666777788889999ull; + appendU64(packet, dispfb2); + appendU64(packet, 0x5Bull); // DISPFB2 + appendU64(packet, display2); + appendU64(packet, 0x5Cull); // DISPLAY2 + + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + t.Equals(regs.dispfb2, dispfb2, "A+D should write GS DISPFB2"); + t.Equals(regs.display2, display2, "A+D should write GS DISPLAY2"); + }); + + tc.Run("PSMT4 address mapping matches GS manual layout", [](TestCase &t) + { + constexpr uint32_t kBaseBlock = 0u; + constexpr uint32_t kWidth = 2u; // One 128x128 PSMT4 page. + + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 0u), 0u, + "PSMT4 origin should map to nibble offset 0"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 1u, 0u), 8u, + "PSMT4 x=1 should advance to the next packed nibble group"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 1u), 16u, + "PSMT4 second source row should follow the manual's row packing"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 2u), 65u, + "PSMT4 third source row should include the manual's odd-row permutation"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 3u), 81u, + "PSMT4 fourth source row should stay in the first block's manual column layout"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 31u, 15u), 511u, + "PSMT4 final texel in the first 32x16 block should land at the end of the block"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 32u, 0u), 1024u, + "PSMT4 x=32 should advance to the next swizzled block in the page"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 32u, 16u), 1536u, + "PSMT4 x=32,y=16 should follow the manual's second block-row permutation"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 64u, 0u), 4096u, + "PSMT4 x=64 should advance to the third swizzled block column in the page"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 96u, 112u), 15872u, + "PSMT4 bottom-right block origin should match the manual's page permutation"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 127u, 127u), 16383u, + "PSMT4 final texel in a 128x128 page should land at the end of the page"); + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 128u, 0u), 16384u, + "PSMT4 x=128 should advance to the next page of nibble addresses"); + }); + + tc.Run("PSMT4 large atlases keep manual page layout across 512x512 textures", [](TestCase &t) + { + constexpr uint32_t kBaseBlock = 64u; + constexpr uint32_t kWidth = 8u; // 512 pixel-wide T4 atlas, like Veronica font pages. + constexpr uint32_t kCoords[][2] = { + {0u, 0u}, + {31u, 15u}, + {32u, 0u}, + {95u, 31u}, + {127u, 127u}, + {128u, 0u}, + {255u, 127u}, + {256u, 0u}, + {383u, 127u}, + {384u, 128u}, + {511u, 511u}, + }; + + for (const auto &coord : kCoords) + { + const uint32_t x = coord[0]; + const uint32_t y = coord[1]; + t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, x, y), + referenceAddrPSMT4(kBaseBlock, kWidth, x, y), + "PSMT4 512x512 atlas mapping should match the GS manual for every sampled page boundary"); + } + }); + + tc.Run("GS T4 triangle sampling reads manual-layout texels from a 512x512 atlas", [](TestCase &t) + { + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kClutCbp = 128u; + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (4ull << 16) | + (0ull << 32) | + (4ull << 48); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (8ull << 14) | + (static_cast(GS_PSM_T4) << 20) | + (9ull << 26) | + (9ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51) | + (1ull << 55); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_TRIANGLE) | + (1ull << 4); + constexpr uint64_t kRgbaq = 0x3F80000080808080ull; + + auto packFloat = [](float value) -> uint32_t + { + uint32_t bits = 0u; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; + }; + + auto packSt = [&](float s, float tVal) -> uint64_t + { + return static_cast(packFloat(s)) | + (static_cast(packFloat(tVal)) << 32); + }; + + const struct SampleCase + { + uint32_t x; + uint32_t y; + uint8_t index; + uint32_t color; + } cases[] = { + {5u, 5u, 1u, 0xFF0000FFu}, + {129u, 5u, 2u, 0xFF00FF00u}, + {257u, 5u, 3u, 0xFFFF0000u}, + {385u, 129u, 4u, 0xFFFFFFFFu}, + }; + + for (const auto &sample : cases) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + writeReferencePSMT4Texel(vram, kTexTbp, 8u, sample.x, sample.y, sample.index); + const uint32_t clutOff = referenceAddrPSMCT32(kClutCbp, 1u, sample.index, 0u); + std::memcpy(vram.data() + clutOff, &sample.color, sizeof(sample.color)); + + gs.writeRegister(GS_REG_FRAME_1, kFrame); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_TEX1_1, 0ull); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, kRgbaq); + + const float s = (static_cast(sample.x) + 0.25f) / 512.0f; + const float tVal = (static_cast(sample.y) + 0.25f) / 512.0f; + gs.writeRegister(GS_REG_ST, packSt(s, tVal)); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_ST, packSt(s, tVal)); + gs.writeRegister(GS_REG_XYZ2, (64ull << 0) | (0ull << 16)); + gs.writeRegister(GS_REG_ST, packSt(s, tVal)); + gs.writeRegister(GS_REG_XYZ2, (0ull << 0) | (64ull << 16)); + + const uint32_t pixel = readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 1u); + t.Equals(pixel, sample.color, + "T4 triangle sampling should fetch the manual-layout atlas texel from the correct 128x128 page"); + } + }); + + tc.Run("PSMT8 address mapping matches Veronica Conv8to32 layout", [](TestCase &t) + { + constexpr uint32_t kBaseBlock = 0u; + constexpr uint32_t kWidth = 2u; // One 128x64 PSMT8 page. + + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 0u, 0u), 0u, + "PSMT8 origin should map to byte offset 0"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 1u, 0u), 4u, + "PSMT8 x=1 should follow Veronica's Conv8to32 byte interleave"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 0u, 1u), 8u, + "PSMT8 second source row should land on the next Conv8to32 row stride"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 0u, 2u), 33u, + "PSMT8 third source row should preserve Veronica's odd-row shuffle"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 0u, 3u), 41u, + "PSMT8 fourth source row should preserve Veronica's alternating block rows"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 15u, 15u), 255u, + "PSMT8 final texel in the first 16x16 block should end at byte 255"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 16u, 0u), 256u, + "PSMT8 x=16 should advance to the next 16x16 block"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 16u, 16u), 768u, + "PSMT8 x=16,y=16 should include both block-column and block-row offsets"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 32u, 0u), 1024u, + "PSMT8 x=32 should advance to the third block column in the page"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 64u, 0u), 4096u, + "PSMT8 x=64 should advance to the second page half"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 96u, 48u), 7680u, + "PSMT8 lower-right interior block should follow Veronica's page permutation"); + t.Equals(GSPSMT8::addrPSMT8(kBaseBlock, kWidth, 127u, 63u), 8191u, + "PSMT8 final texel in a 128x64 page should land at the final byte"); + }); + + tc.Run("GIF REGLIST with odd register count consumes 128-bit padding before next tag", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(0u) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(0u) << 56); + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(1u, GIF_FMT_REGLIST, 1u, false)); + appendU64(packet, 0x0ull); // REGS[0] = PRIM + appendU64(packet, 0x0000000000000006ull); // PRIM write + appendU64(packet, 0xDEADBEEFCAFEBABEull); // required REGLIST pad qword + + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + const uint8_t payload[16] = { + 0x31u, 0x32u, 0x33u, 0x34u, + 0x35u, 0x36u, 0x37u, 0x38u, + 0x39u, 0x3Au, 0x3Bu, 0x3Cu, + 0x3Du, 0x3Eu, 0x3Fu, 0x40u, + }; + packet.insert(packet.end(), payload, payload + sizeof(payload)); + + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + bool imageOk = true; + for (uint32_t x = 0; x < 4u && imageOk; ++x) + { + const uint32_t off = referenceAddrPSMCT32(0u, 1u, x, 0u); + for (uint32_t c = 0; c < 4u; ++c) + { + if (vram[off + c] != payload[x * 4u + c]) + { + imageOk = false; + break; + } + } + } + t.IsTrue(imageOk, "odd REGLIST payload should not corrupt alignment of the following IMAGE tag"); + }); + + tc.Run("GIF REGLIST NREG=0 is treated as sixteen descriptors", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(0u) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(0u) << 56); + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(1u, GIF_FMT_REGLIST, 0u, false)); // NREG=0 -> 16 regs + appendU64(packet, 0ull); // 16x PRIM descriptors + for (uint32_t i = 0; i < 16u; ++i) + { + appendU64(packet, static_cast(i)); + } + + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + const uint8_t payload[16] = { + 0x51u, 0x52u, 0x53u, 0x54u, + 0x55u, 0x56u, 0x57u, 0x58u, + 0x59u, 0x5Au, 0x5Bu, 0x5Cu, + 0x5Du, 0x5Eu, 0x5Fu, 0x60u, + }; + packet.insert(packet.end(), payload, payload + sizeof(payload)); + + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + bool imageOk = true; + for (uint32_t x = 0; x < 4u && imageOk; ++x) + { + const uint32_t off = referenceAddrPSMCT32(0u, 1u, x, 0u); + for (uint32_t c = 0; c < 4u; ++c) + { + if (vram[off + c] != payload[x * 4u + c]) + { + imageOk = false; + break; + } + } + } + t.IsTrue(imageOk, "NREG=0 REGLIST should consume 16 data words and keep following tag aligned"); + }); + + tc.Run("GS SIGNAL and FINISH set CSR bits that clear by CSR write-one acknowledge", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + GS gs; + gs.init(mem.getGSVRAM(), static_cast(PS2_GS_VRAM_SIZE), &mem.gs()); + + const uint64_t signalValue = (0xFFFFFFFFull << 32) | 0x11223344ull; + gs.writeRegister(GS_REG_SIGNAL, signalValue); + gs.writeRegister(GS_REG_FINISH, 0u); + + t.IsTrue((mem.gs().csr & 0x1ull) != 0ull, "SIGNAL should raise CSR.SIGNAL"); + t.IsTrue((mem.gs().csr & 0x2ull) != 0ull, "FINISH should raise CSR.FINISH"); + t.Equals(static_cast(mem.gs().siglblid & 0xFFFFFFFFull), 0x11223344u, "SIGNAL should update SIGLBLID low dword"); + + mem.write64(0x12001000u, 0x1ull); + t.IsTrue((mem.gs().csr & 0x1ull) == 0ull, "writing CSR bit0 should acknowledge SIGNAL"); + t.IsTrue((mem.gs().csr & 0x2ull) != 0ull, "acknowledging SIGNAL should not clear FINISH"); + + mem.write32(0x12001000u, 0x2u); + t.IsTrue((mem.gs().csr & 0x2ull) == 0ull, "writing CSR bit1 should acknowledge FINISH"); + }); + + tc.Run("GIF IMAGE packet writes host-to-local data into GS VRAM", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + // Setup for host->local transfer to DBP=0, DBW=1, PSMCT32, rect 2x2. + const uint64_t bitblt = + (static_cast(0u) << 0) | // SBP + (static_cast(1u) << 16) | // SBW + (static_cast(0u) << 24) | // SPSM + (static_cast(0u) << 32) | // DBP + (static_cast(1u) << 48) | // DBW + (static_cast(0u) << 56); // DPSM (CT32) + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (2ull << 0) | (2ull << 32)); + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + + const uint8_t payload[16] = { + 0x10u, 0x11u, 0x12u, 0x13u, + 0x20u, 0x21u, 0x22u, 0x23u, + 0x30u, 0x31u, 0x32u, 0x33u, + 0x40u, 0x41u, 0x42u, 0x43u, + }; + packet.insert(packet.end(), payload, payload + sizeof(payload)); + + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + bool same = true; + for (uint32_t y = 0; y < 2u && same; ++y) + { + for (uint32_t x = 0; x < 2u; ++x) + { + const uint32_t pixelIndex = y * 2u + x; + const uint32_t off = referenceAddrPSMCT32(0u, 1u, x, y); + for (uint32_t c = 0; c < 4u; ++c) + { + if (vram[off + c] != payload[pixelIndex * 4u + c]) + { + same = false; + break; + } + } + if (!same) + break; + } + } + t.IsTrue(same, "GIF IMAGE transfer should write payload bytes into GS VRAM"); + }); + + tc.Run("GS local-to-host transfer supports partial incremental reads", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + for (uint32_t x = 0; x < 4u; ++x) + { + const uint32_t off = referenceAddrPSMCT32(0u, 1u, x, 0u); + for (uint32_t c = 0; c < 4u; ++c) + { + vram[off + c] = static_cast(0xA0u + x * 4u + c); + } + } + + const uint64_t bitblt = (static_cast(0u) << 0) | // SBP (static_cast(1u) << 16) | // SBW (static_cast(0u) << 24) | // SPSM (CT32) @@ -482,278 +1933,942 @@ void register_ps2_gs_tests() const uint32_t nB = gs.consumeLocalToHostBytes(bufB, 16u); const uint32_t nC = gs.consumeLocalToHostBytes(bufB, 4u); - t.Equals(nA, 6u, "first partial read should consume requested bytes"); - t.Equals(nB, 10u, "second read should consume the remaining bytes"); - t.Equals(nC, 0u, "buffer should be empty after all bytes are consumed"); + t.Equals(nA, 6u, "first partial read should consume requested bytes"); + t.Equals(nB, 10u, "second read should consume the remaining bytes"); + t.Equals(nC, 0u, "buffer should be empty after all bytes are consumed"); + + bool bytesOk = true; + for (uint32_t i = 0; i < 6u; ++i) + { + if (bufA[i] != static_cast(0xA0u + i)) + bytesOk = false; + } + for (uint32_t i = 0; i < 10u; ++i) + { + if (bufB[i] != static_cast(0xA6u + i)) + bytesOk = false; + } + t.IsTrue(bytesOk, "partial reads should return local->host data in-order"); + }); + + tc.Run("GS CT24 host-local-host transfer preserves 24-bit RGB payload", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | // SBP + (static_cast(1u) << 16) | // SBW + (static_cast(1u) << 24) | // SPSM CT24 + (static_cast(0u) << 32) | // DBP + (static_cast(1u) << 48) | // DBW + (static_cast(1u) << 56); // DPSM CT24 + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (2ull << 0) | (1ull << 32)); // 2 pixels + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + const uint8_t rgbData[16] = { + 0x11u, 0x22u, 0x33u, + 0x44u, 0x55u, 0x66u, + 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u + }; + packet.insert(packet.end(), rgbData, rgbData + sizeof(rgbData)); + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + // Read back from local to host in CT24. + gs.writeRegister(GS_REG_TRXDIR, 1ull); + uint8_t out[16] = {}; + const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + + t.Equals(outBytes, 6u, "CT24 local->host read should output 3 bytes per pixel"); + t.Equals(out[0], static_cast(0x11u), "pixel0 R should roundtrip"); + t.Equals(out[1], static_cast(0x22u), "pixel0 G should roundtrip"); + t.Equals(out[2], static_cast(0x33u), "pixel0 B should roundtrip"); + t.Equals(out[3], static_cast(0x44u), "pixel1 R should roundtrip"); + t.Equals(out[4], static_cast(0x55u), "pixel1 G should roundtrip"); + t.Equals(out[5], static_cast(0x66u), "pixel1 B should roundtrip"); + }); + + tc.Run("GS PSMT4 host-local-host keeps nibble packing stable", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | // SBP + (static_cast(1u) << 16) | // SBW + (static_cast(20u) << 24) | // SPSM PSMT4 + (static_cast(0u) << 32) | // DBP + (static_cast(1u) << 48) | // DBW + (static_cast(20u) << 56); // DPSM PSMT4 + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); // 4 texels => 2 bytes + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + const uint8_t nibbleData[16] = {0x21u, 0x43u}; + packet.insert(packet.end(), nibbleData, nibbleData + sizeof(nibbleData)); + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + gs.writeRegister(GS_REG_TRXDIR, 1ull); + uint8_t out[8] = {}; + const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + + t.Equals(outBytes, 2u, "PSMT4 local->host should return packed nibble bytes"); + t.Equals(out[0], static_cast(0x21u), "packed nibble byte 0 should roundtrip"); + t.Equals(out[1], static_cast(0x43u), "packed nibble byte 1 should roundtrip"); + }); + + tc.Run("GS PSMT4 host-local upload keeps position across split IMAGE packets", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(GS_PSM_T4) << 56); + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (8ull << 0) | (8ull << 32)); // 64 texels => 32 bytes + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + uint8_t packedSource[32] = {}; + for (uint32_t i = 0; i < 32u; ++i) + { + packedSource[i] = static_cast(0x10u + i); + } + + std::vector packetA; + appendU64(packetA, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packetA, 0ull); + packetA.insert(packetA.end(), packedSource, packedSource + 16u); + + std::vector packetB; + appendU64(packetB, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packetB, 0ull); + packetB.insert(packetB.end(), packedSource + 16u, packedSource + 32u); + + gs.processGIFPacket(packetA.data(), static_cast(packetA.size())); + gs.processGIFPacket(packetB.data(), static_cast(packetB.size())); + + gs.writeRegister(GS_REG_TRXDIR, 1ull); + uint8_t out[32] = {}; + const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + + t.Equals(outBytes, 32u, "split T4 IMAGE upload should fill the full packed byte range"); + for (uint32_t i = 0; i < 32u; ++i) + { + t.Equals(out[i], packedSource[i], "split T4 IMAGE upload should preserve packed nibble order"); + } + }); + + tc.Run("GS CT32 upload aliases cleanly into later PSMT8 sampling", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kTexWidth = 128u; + constexpr uint32_t kTexHeight = 64u; + constexpr uint32_t kUploadWidth = 64u; + constexpr uint32_t kUploadHeight = 32u; + constexpr uint32_t kTexTbp = 0u; + constexpr uint32_t kTexTbw = 2u; + + std::vector source(kTexWidth * kTexHeight, 0u); + for (uint32_t i = 0; i < source.size(); ++i) + { + source[i] = static_cast((i * 37u + 11u) & 0xFFu); + } + + std::vector rawToUpload(8192u, 0xFFFFu); + for (uint32_t y = 0; y < kUploadHeight; ++y) + { + for (uint32_t x = 0; x < kUploadWidth; ++x) + { + const uint32_t rawBase = referenceAddrPSMCT32(kTexTbp, 1u, x, y); + const uint32_t uploadBase = ((y * kUploadWidth) + x) * 4u; + for (uint32_t c = 0; c < 4u; ++c) + { + rawToUpload[rawBase + c] = static_cast(uploadBase + c); + } + } + } + + bool inverseComplete = true; + for (uint16_t byteOff : rawToUpload) + { + if (byteOff == 0xFFFFu) + { + inverseComplete = false; + break; + } + } + t.IsTrue(inverseComplete, + "reference CT32 raw-to-upload map should cover every byte in a 64x32 CT32 page"); + + std::vector upload(kUploadWidth * kUploadHeight * 4u, 0u); + for (uint32_t y = 0; y < kTexHeight; ++y) + { + for (uint32_t x = 0; x < kTexWidth; ++x) + { + const uint32_t texelIndex = y * kTexWidth + x; + const uint32_t rawOff = referenceAddrPSMT8(kTexTbp, kTexTbw, x, y); + const uint32_t uploadOff = rawToUpload[rawOff]; + upload[uploadOff] = source[texelIndex]; + } + } + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(GS_PSM_CT32) << 24) | + (static_cast(kTexTbp) << 32) | + (static_cast(1u) << 48) | + (static_cast(GS_PSM_CT32) << 56); + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (static_cast(kUploadWidth) << 0) | + (static_cast(kUploadHeight) << 32)); + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(static_cast(upload.size() / 16u), GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + packet.insert(packet.end(), upload.begin(), upload.end()); + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + bool aliasOk = true; + uint32_t badX = 0u; + uint32_t badY = 0u; + uint32_t got = 0u; + uint32_t expected = 0u; + for (uint32_t y = 0; y < kTexHeight && aliasOk; ++y) + { + for (uint32_t x = 0; x < kTexWidth; ++x) + { + const uint32_t texelOff = GSPSMT8::addrPSMT8(kTexTbp, kTexTbw, x, y); + got = vram[texelOff]; + expected = source[y * kTexWidth + x]; + if (got != expected) + { + aliasOk = false; + badX = x; + badY = y; + break; + } + } + } + + if (!aliasOk) + { + t.Fail("CT32 image upload should preserve Veronica's later PSMT8 sampling layout " + "(first mismatch at x=" + std::to_string(badX) + + ", y=" + std::to_string(badY) + + ", got " + std::to_string(got) + + ", expected " + std::to_string(expected) + ")"); + } + }); + + tc.Run("GS PSMT4 local-local copy respects swizzled page layout", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kSrcBp = 64u; + constexpr uint32_t kDstBp = 96u; + constexpr uint64_t kUploadBitblt = + (static_cast(0u) << 0) | + (static_cast(2u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(kSrcBp) << 32) | + (static_cast(2u) << 48) | + (static_cast(GS_PSM_T4) << 56); + constexpr uint64_t kCopyBitblt = + (static_cast(kSrcBp) << 0) | + (static_cast(2u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(kDstBp) << 32) | + (static_cast(2u) << 48) | + (static_cast(GS_PSM_T4) << 56); + constexpr uint64_t kCopyPos = + (static_cast(0u) << 0) | + (static_cast(0u) << 16) | + (static_cast(32u) << 32) | + (static_cast(16u) << 48); + constexpr uint64_t kReadBitblt = + (static_cast(kDstBp) << 0) | + (static_cast(2u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(0u) << 32) | + (static_cast(2u) << 48) | + (static_cast(GS_PSM_T4) << 56); + constexpr uint64_t kReadPos = + (static_cast(32u) << 0) | + (static_cast(16u) << 16); + constexpr uint64_t kRect = (8ull << 0) | (4ull << 32); + const uint8_t packedSource[16] = { + 0x10u, 0x32u, 0x54u, 0x76u, + 0x98u, 0xBAu, 0xDCu, 0xFEu, + 0x01u, 0x23u, 0x45u, 0x67u, + 0x89u, 0xABu, 0xCDu, 0xEFu + }; + + gs.writeRegister(GS_REG_BITBLTBUF, kUploadBitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, kRect); + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + packet.insert(packet.end(), packedSource, packedSource + sizeof(packedSource)); + gs.processGIFPacket(packet.data(), static_cast(packet.size())); + + gs.writeRegister(GS_REG_BITBLTBUF, kCopyBitblt); + gs.writeRegister(GS_REG_TRXPOS, kCopyPos); + gs.writeRegister(GS_REG_TRXREG, kRect); + gs.writeRegister(GS_REG_TRXDIR, 2ull); - bool bytesOk = true; - for (uint32_t i = 0; i < 6u; ++i) - { - if (bufA[i] != static_cast(0xA0u + i)) - bytesOk = false; - } - for (uint32_t i = 0; i < 10u; ++i) + gs.writeRegister(GS_REG_BITBLTBUF, kReadBitblt); + gs.writeRegister(GS_REG_TRXPOS, kReadPos); + gs.writeRegister(GS_REG_TRXREG, kRect); + gs.writeRegister(GS_REG_TRXDIR, 1ull); + + uint8_t out[16] = {}; + const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + t.Equals(outBytes, 16u, "PSMT4 local-local copy should preserve the full packed byte count"); + for (size_t i = 0; i < sizeof(packedSource); ++i) { - if (bufB[i] != static_cast(0xA6u + i)) - bytesOk = false; + t.Equals(out[i], packedSource[i], "PSMT4 local-local copy should preserve packed nibble order"); } - t.IsTrue(bytesOk, "partial reads should return local->host data in-order"); }); - tc.Run("GS CT24 host-local-host transfer preserves 24-bit RGB payload", [](TestCase &t) + tc.Run("GS T4 CSM1 lookup matches Veronica ClutCopy layout", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - const uint64_t bitblt = - (static_cast(0u) << 0) | // SBP - (static_cast(1u) << 16) | // SBW - (static_cast(1u) << 24) | // SPSM CT24 - (static_cast(0u) << 32) | // DBP - (static_cast(1u) << 48) | // DBW - (static_cast(1u) << 56); // DPSM CT24 - gs.writeRegister(GS_REG_BITBLTBUF, bitblt); - gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, (2ull << 0) | (1ull << 32)); // 2 pixels - gs.writeRegister(GS_REG_TRXDIR, 0ull); + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kClutCbp = 128u; + constexpr uint32_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_T4) << 20) | + (0ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | // TME + (1ull << 8); // FST + constexpr uint32_t kExpectedColor = 0x800000FFu; // RGBA = (255,0,0,128) + constexpr uint32_t kWrongColor = 0x8000FF00u; // RGBA = (0,255,0,128) - std::vector packet; - appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packet, 0ull); - const uint8_t rgbData[16] = { - 0x11u, 0x22u, 0x33u, - 0x44u, 0x55u, 0x66u, - 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u - }; - packet.insert(packet.end(), rgbData, rgbData + sizeof(rgbData)); - gs.processGIFPacket(packet.data(), static_cast(packet.size())); + const uint32_t texNibbleAddr = GSPSMT4::addrPSMT4(kTexTbp, 1u, 0u, 0u); + const uint32_t texByteOff = texNibbleAddr >> 1; + vram[texByteOff] = static_cast((vram[texByteOff] & 0xF0u) | 0x08u); - // Read back from local to host in CT24. - gs.writeRegister(GS_REG_TRXDIR, 1ull); - uint8_t out[16] = {}; - const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + // Veronica uploads CSM1 CLUT rows with a 64-pixel GS stride, so logical entry 8 + // resolves to row 1, column 0 after the CSM1 swizzle. + const uint32_t wrongClutOff = GSPSMCT32::addrPSMCT32(kClutCbp, 1u, 8u, 0u); + const uint32_t expectedClutOff = GSPSMCT32::addrPSMCT32(kClutCbp, 1u, 0u, 1u); + std::memcpy(vram.data() + wrongClutOff, &kWrongColor, sizeof(kWrongColor)); + std::memcpy(vram.data() + expectedClutOff, &kExpectedColor, sizeof(kExpectedColor)); - t.Equals(outBytes, 6u, "CT24 local->host read should output 3 bytes per pixel"); - t.Equals(out[0], static_cast(0x11u), "pixel0 R should roundtrip"); - t.Equals(out[1], static_cast(0x22u), "pixel0 G should roundtrip"); - t.Equals(out[2], static_cast(0x33u), "pixel0 B should roundtrip"); - t.Equals(out[3], static_cast(0x44u), "pixel1 R should roundtrip"); - t.Equals(out[4], static_cast(0x55u), "pixel1 G should roundtrip"); - t.Equals(out[5], static_cast(0x66u), "pixel1 B should roundtrip"); + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, kExpectedColor, + "T4 CSM1 lookup should follow Veronica's swizzled CLUT row layout for logical index 8"); }); - tc.Run("GS PSMT4 host-local-host keeps nibble packing stable", [](TestCase &t) + tc.Run("GS T8 CT32-uploaded CSM1 CLUT follows swizzled palette layout", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - const uint64_t bitblt = - (static_cast(0u) << 0) | // SBP - (static_cast(1u) << 16) | // SBW - (static_cast(20u) << 24) | // SPSM PSMT4 - (static_cast(0u) << 32) | // DBP - (static_cast(1u) << 48) | // DBW - (static_cast(20u) << 56); // DPSM PSMT4 - gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kClutCbp = 128u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_T8) << 20) | + (0ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | // TME + (1ull << 8); // FST + constexpr uint64_t kClutBitblt = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24) | + (static_cast(kClutCbp) << 32) | + (1ull << 48) | + (static_cast(GS_PSM_CT32) << 56); + constexpr uint64_t kClutRect = + (16ull << 0) | + (2ull << 32); + constexpr uint32_t kExpectedColor = 0x80FFFFFFu; + + const uint32_t texOff = GSPSMT8::addrPSMT8(kTexTbp, 1u, 0u, 0u); + vram[texOff] = 8u; + + std::vector clut(32u, 0u); + clut[16] = kExpectedColor; + + gs.writeRegister(GS_REG_BITBLTBUF, kClutBitblt); gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); // 4 texels => 2 bytes + gs.writeRegister(GS_REG_TRXREG, kClutRect); gs.writeRegister(GS_REG_TRXDIR, 0ull); std::vector packet; - appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, + makeGifTag(static_cast((clut.size() * sizeof(uint32_t)) / 16u), + GIF_FMT_IMAGE, + 0u, + true)); appendU64(packet, 0ull); - const uint8_t nibbleData[16] = {0x21u, 0x43u}; - packet.insert(packet.end(), nibbleData, nibbleData + sizeof(nibbleData)); + const size_t payloadOffset = packet.size(); + packet.resize(payloadOffset + clut.size() * sizeof(uint32_t)); + std::memcpy(packet.data() + payloadOffset, clut.data(), clut.size() * sizeof(uint32_t)); gs.processGIFPacket(packet.data(), static_cast(packet.size())); - gs.writeRegister(GS_REG_TRXDIR, 1ull); - uint8_t out[8] = {}; - const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); - t.Equals(outBytes, 2u, "PSMT4 local->host should return packed nibble bytes"); - t.Equals(out[0], static_cast(0x21u), "packed nibble byte 0 should roundtrip"); - t.Equals(out[1], static_cast(0x43u), "packed nibble byte 1 should roundtrip"); + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, kExpectedColor, + "T8 CSM1 CLUT sampling should read CT32-uploaded palette entries through GS swizzled addressing"); }); - tc.Run("GS PSMT4 host-local upload keeps position across split IMAGE packets", [](TestCase &t) + tc.Run("GS TEX2 updates CLUT state independently from TEX0", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - const uint64_t bitblt = - (static_cast(0u) << 0) | - (static_cast(1u) << 16) | - (static_cast(GS_PSM_T4) << 24) | - (static_cast(0u) << 32) | - (static_cast(1u) << 48) | - (static_cast(GS_PSM_T4) << 56); - gs.writeRegister(GS_REG_BITBLTBUF, bitblt); - gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, (8ull << 0) | (8ull << 32)); // 64 texels => 32 bytes - gs.writeRegister(GS_REG_TRXDIR, 0ull); + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kWrongClutCbp = 128u; + constexpr uint32_t kExpectedClutCbp = 192u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_T8) << 20) | + (0ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kWrongClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51) | + (1ull << 55); + constexpr uint64_t kTex2 = + (static_cast(GS_PSM_T8) << 20) | + (static_cast(kExpectedClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51) | + (1ull << 55); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8); + constexpr uint32_t kWrongColor = 0xFF00FF00u; + constexpr uint32_t kExpectedColor = 0xFF0000FFu; - uint8_t packedSource[32] = {}; - for (uint32_t i = 0; i < 32u; ++i) - { - packedSource[i] = static_cast(0x10u + i); - } + const uint32_t texOff = GSPSMT8::addrPSMT8(kTexTbp, 1u, 0u, 0u); + vram[texOff] = 8u; - std::vector packetA; - appendU64(packetA, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packetA, 0ull); - packetA.insert(packetA.end(), packedSource, packedSource + 16u); + const uint32_t wrongClutOff = GSPSMCT32::addrPSMCT32(kWrongClutCbp, 1u, 8u, 0u); + const uint32_t expectedClutOff = GSPSMCT32::addrPSMCT32(kExpectedClutCbp, 1u, 8u, 0u); + std::memcpy(vram.data() + wrongClutOff, &kWrongColor, sizeof(kWrongColor)); + std::memcpy(vram.data() + expectedClutOff, &kExpectedColor, sizeof(kExpectedColor)); - std::vector packetB; - appendU64(packetB, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packetB, 0ull); - packetB.insert(packetB.end(), packedSource + 16u, packedSource + 32u); + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_TEX2_1, kTex2); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); - gs.processGIFPacket(packetA.data(), static_cast(packetA.size())); - gs.processGIFPacket(packetB.data(), static_cast(packetB.size())); + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, kExpectedColor, + "TEX2 should override the active CLUT base and format state without requiring a new TEX0 write"); + }); - gs.writeRegister(GS_REG_TRXDIR, 1ull); - uint8_t out[32] = {}; - const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); + tc.Run("GS TEXCLUT offsets T8 CLUT fetch coordinates", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); - t.Equals(outBytes, 32u, "split T4 IMAGE upload should fill the full packed byte range"); - for (uint32_t i = 0; i < 32u; ++i) - { - t.Equals(out[i], packedSource[i], "split T4 IMAGE upload should preserve packed nibble order"); - } + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kClutCbp = 128u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_T8) << 20) | + (0ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51) | + (1ull << 55); + constexpr uint64_t kTexClut = + (1ull << 0) | + (3ull << 6) | + (2ull << 12); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8); + constexpr uint32_t kWrongColor = 0xFF00FF00u; + constexpr uint32_t kExpectedColor = 0xFF3366CCu; + + const uint32_t texOff = GSPSMT8::addrPSMT8(kTexTbp, 1u, 0u, 0u); + vram[texOff] = 0u; + + const uint32_t wrongClutOff = GSPSMCT32::addrPSMCT32(kClutCbp, 1u, 0u, 0u); + const uint32_t expectedClutOff = GSPSMCT32::addrPSMCT32(kClutCbp, 1u, 3u, 2u); + std::memcpy(vram.data() + wrongClutOff, &kWrongColor, sizeof(kWrongColor)); + std::memcpy(vram.data() + expectedClutOff, &kExpectedColor, sizeof(kExpectedColor)); + + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_TEXCLUT, kTexClut); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); + + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data(), sizeof(pixel)); + t.Equals(pixel, kExpectedColor, + "TEXCLUT should offset the CLUT lookup coordinates instead of always starting from the CLUT base"); }); - tc.Run("GS PSMT4 local-local copy respects swizzled page layout", [](TestCase &t) + tc.Run("GS TEXA expands CT24 alpha and honors AEM for black texels", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); - constexpr uint32_t kSrcBp = 64u; - constexpr uint32_t kDstBp = 96u; - constexpr uint64_t kUploadBitblt = - (static_cast(0u) << 0) | - (static_cast(2u) << 16) | - (static_cast(GS_PSM_T4) << 24) | - (static_cast(kSrcBp) << 32) | - (static_cast(2u) << 48) | - (static_cast(GS_PSM_T4) << 56); - constexpr uint64_t kCopyBitblt = - (static_cast(kSrcBp) << 0) | - (static_cast(2u) << 16) | - (static_cast(GS_PSM_T4) << 24) | - (static_cast(kDstBp) << 32) | - (static_cast(2u) << 48) | - (static_cast(GS_PSM_T4) << 56); - constexpr uint64_t kCopyPos = - (static_cast(0u) << 0) | - (static_cast(0u) << 16) | - (static_cast(32u) << 32) | - (static_cast(16u) << 48); - constexpr uint64_t kReadBitblt = - (static_cast(kDstBp) << 0) | - (static_cast(2u) << 16) | - (static_cast(GS_PSM_T4) << 24) | - (static_cast(0u) << 32) | - (static_cast(2u) << 48) | - (static_cast(GS_PSM_T4) << 56); - constexpr uint64_t kReadPos = - (static_cast(32u) << 0) | - (static_cast(16u) << 16); - constexpr uint64_t kRect = (8ull << 0) | (4ull << 32); - const uint8_t packedSource[16] = { - 0x10u, 0x32u, 0x54u, 0x76u, - 0x98u, 0xBAu, 0xDCu, 0xFEu, - 0x01u, 0x23u, 0x45u, 0x67u, - 0x89u, 0xABu, 0xCDu, 0xEFu - }; + constexpr uint32_t kTexTbp = 64u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (1ull << 16) | + (0ull << 32) | + (0ull << 48); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_CT24) << 20) | + (1ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35); + constexpr uint64_t kTexa = + (0x55ull << 0) | + (1ull << 15) | + (0xAAull << 32); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8); + constexpr uint32_t kExpectedRed = 0x550000FFu; + constexpr uint32_t kExpectedBlack = 0x00000000u; + + const uint32_t redOff = GSPSMCT32::addrPSMCT32(kTexTbp, 1u, 0u, 0u); + vram[redOff + 0u] = 0xFFu; + vram[redOff + 1u] = 0x00u; + vram[redOff + 2u] = 0x00u; + vram[redOff + 3u] = 0x00u; + + const uint32_t blackOff = GSPSMCT32::addrPSMCT32(kTexTbp, 1u, 1u, 0u); + vram[blackOff + 0u] = 0x00u; + vram[blackOff + 1u] = 0x00u; + vram[blackOff + 2u] = 0x00u; + vram[blackOff + 3u] = 0x00u; - gs.writeRegister(GS_REG_BITBLTBUF, kUploadBitblt); - gs.writeRegister(GS_REG_TRXPOS, 0ull); - gs.writeRegister(GS_REG_TRXREG, kRect); - gs.writeRegister(GS_REG_TRXDIR, 0ull); + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_TEXA, kTexa); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); - std::vector packet; - appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); - appendU64(packet, 0ull); - packet.insert(packet.end(), packedSource, packedSource + sizeof(packedSource)); - gs.processGIFPacket(packet.data(), static_cast(packet.size())); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); + + gs.writeRegister(GS_REG_UV, (16ull << 0)); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0)); + gs.writeRegister(GS_REG_UV, (16ull << 0)); + gs.writeRegister(GS_REG_XYZ2, (32ull << 0) | (16ull << 16)); + + const uint32_t redPixel = readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u); + const uint32_t blackPixel = readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 0u); + t.Equals(redPixel, kExpectedRed, + "TEXA should supply TA0 as the alpha for non-alpha CT24 texels"); + t.Equals(blackPixel, kExpectedBlack, + "TEXA AEM should force zero alpha when a CT24 texel is RGB=0"); + }); - gs.writeRegister(GS_REG_BITBLTBUF, kCopyBitblt); - gs.writeRegister(GS_REG_TRXPOS, kCopyPos); - gs.writeRegister(GS_REG_TRXREG, kRect); - gs.writeRegister(GS_REG_TRXDIR, 2ull); + tc.Run("GS TCC=0 MODULATE uses texture RGB and keeps vertex alpha", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); - gs.writeRegister(GS_REG_BITBLTBUF, kReadBitblt); - gs.writeRegister(GS_REG_TRXPOS, kReadPos); - gs.writeRegister(GS_REG_TRXREG, kRect); - gs.writeRegister(GS_REG_TRXDIR, 1ull); + constexpr uint32_t kTexTbp = 64u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (0ull << 16) | + (0ull << 32) | + (0ull << 48); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (0ull << 26) | + (0ull << 30) | + (0ull << 34) | + (0ull << 35); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8); + constexpr uint32_t kTexturePixel = + 0x12u | + (0x34u << 8) | + (0x56u << 16) | + (0x78u << 24); + constexpr uint32_t kExpectedPixel = + 0x12u | + (0x34u << 8) | + (0x56u << 16) | + (0x44u << 24); + + const uint32_t texOff = GSPSMCT32::addrPSMCT32(kTexTbp, 1u, 0u, 0u); + std::memcpy(vram.data() + texOff, &kTexturePixel, sizeof(kTexturePixel)); - uint8_t out[16] = {}; - const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out)); - t.Equals(outBytes, 16u, "PSMT4 local-local copy should preserve the full packed byte count"); - for (size_t i = 0; i < sizeof(packedSource); ++i) - { - t.Equals(out[i], packedSource[i], "PSMT4 local-local copy should preserve packed nibble order"); - } + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x44808080ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); + + const uint32_t pixel = readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u); + t.Equals(pixel, kExpectedPixel, + "TCC=0 MODULATE should still use texture RGB while sourcing alpha from the shaded vertex"); }); - tc.Run("GS T4 CSM1 lookup matches Veronica ClutCopy layout", [](TestCase &t) + tc.Run("GS HIGHLIGHT adds vertex alpha into RGB and texture alpha into A", [](TestCase &t) { std::vector vram(PS2_GS_VRAM_SIZE, 0u); GS gs; gs.init(vram.data(), static_cast(vram.size()), nullptr); constexpr uint32_t kTexTbp = 64u; - constexpr uint32_t kClutCbp = 128u; - constexpr uint32_t kFrameReg = + constexpr uint64_t kFrameReg = (0ull << 0) | (1ull << 16) | (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (0ull << 16) | + (0ull << 32) | + (0ull << 48); constexpr uint64_t kTex0 = (static_cast(kTexTbp) << 0) | (1ull << 14) | - (static_cast(GS_PSM_T4) << 20) | + (static_cast(GS_PSM_CT32) << 20) | (0ull << 26) | (0ull << 30) | (1ull << 34) | - (1ull << 35) | - (static_cast(kClutCbp) << 37) | - (static_cast(GS_PSM_CT32) << 51); + (2ull << 35); constexpr uint64_t kPrim = static_cast(GS_PRIM_SPRITE) | - (1ull << 4) | // TME - (1ull << 8); // FST - constexpr uint32_t kExpectedColor = 0x800000FFu; // RGBA = (255,0,0,128) - constexpr uint32_t kWrongColor = 0x8000FF00u; // RGBA = (0,255,0,128) + (1ull << 4) | + (1ull << 8); + constexpr uint32_t kTexturePixel = + 0x20u | + (0x40u << 8) | + (0x60u << 16) | + (0x10u << 24); + constexpr uint32_t kExpectedPixel = + 0x40u | + (0x60u << 8) | + (0x80u << 16) | + (0x30u << 24); + + const uint32_t texOff = GSPSMCT32::addrPSMCT32(kTexTbp, 1u, 0u, 0u); + std::memcpy(vram.data() + texOff, &kTexturePixel, sizeof(kTexturePixel)); - const uint32_t texNibbleAddr = GSPSMT4::addrPSMT4(kTexTbp, 1u, 0u, 0u); - const uint32_t texByteOff = texNibbleAddr >> 1; - vram[texByteOff] = static_cast((vram[texByteOff] & 0xF0u) | 0x08u); + gs.writeRegister(GS_REG_FRAME_1, kFrameReg); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, 0x20808080ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_UV, 0ull); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); - // Veronica's ClutCopy stores logical entries 8..15 into physical slots 16..23. - std::memcpy(vram.data() + kClutCbp * 256u + 8u * 4u, &kWrongColor, sizeof(kWrongColor)); - std::memcpy(vram.data() + kClutCbp * 256u + 16u * 4u, &kExpectedColor, sizeof(kExpectedColor)); + const uint32_t pixel = readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u); + t.Equals(pixel, kExpectedPixel, + "HIGHLIGHT should add the shaded vertex alpha into RGB and accumulate texture plus vertex alpha"); + }); + + tc.Run("GS HIGHLIGHT2 keeps texture alpha while adding vertex alpha into RGB", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kTexTbp = 64u; + constexpr uint64_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (0ull << 16) | + (0ull << 32) | + (0ull << 48); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_CT32) << 20) | + (0ull << 26) | + (0ull << 30) | + (1ull << 34) | + (3ull << 35); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_SPRITE) | + (1ull << 4) | + (1ull << 8); + constexpr uint32_t kTexturePixel = + 0x20u | + (0x40u << 8) | + (0x60u << 16) | + (0x10u << 24); + constexpr uint32_t kExpectedPixel = + 0x40u | + (0x60u << 8) | + (0x80u << 16) | + (0x10u << 24); + + const uint32_t texOff = GSPSMCT32::addrPSMCT32(kTexTbp, 1u, 0u, 0u); + std::memcpy(vram.data() + texOff, &kTexturePixel, sizeof(kTexturePixel)); gs.writeRegister(GS_REG_FRAME_1, kFrameReg); - gs.writeRegister(GS_REG_SCISSOR_1, 0ull); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); gs.writeRegister(GS_REG_TEST_1, 0ull); gs.writeRegister(GS_REG_ALPHA_1, 0ull); gs.writeRegister(GS_REG_TEX0_1, kTex0); gs.writeRegister(GS_REG_PRIM, kPrim); - gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull); + gs.writeRegister(GS_REG_RGBAQ, 0x20808080ull); gs.writeRegister(GS_REG_UV, 0ull); gs.writeRegister(GS_REG_XYZ2, 0ull); gs.writeRegister(GS_REG_UV, 0ull); - gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_XYZ2, (16ull << 0) | (16ull << 16)); - uint32_t pixel = 0u; - std::memcpy(&pixel, vram.data(), sizeof(pixel)); - t.Equals(pixel, kExpectedColor, - "T4 CSM1 lookup should follow Veronica's swizzled CLUT layout for logical index 8"); + const uint32_t pixel = readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u); + t.Equals(pixel, kExpectedPixel, + "HIGHLIGHT2 should add the shaded vertex alpha into RGB while preserving the texture alpha"); + }); + + tc.Run("GS TEX1 linear filter blends T4 STQ triangle samples", [](TestCase &t) + { + auto renderSamplePixel = [](uint64_t tex1Reg) -> uint32_t + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kClutCbp = 128u; + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (4ull << 16) | + (0ull << 32) | + (4ull << 48); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_T4) << 20) | + (1ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_TRIANGLE) | + (1ull << 4) | + (0ull << 8); + constexpr uint64_t kRgbaq = 0x3F80000080808080ull; + constexpr uint32_t kBlack = 0x80000000u; + constexpr uint32_t kWhite = 0x80FFFFFFu; + + writePSMT4Texel(vram, kTexTbp, 1u, 0u, 0u, 0u); + writePSMT4Texel(vram, kTexTbp, 1u, 1u, 0u, 1u); + std::memcpy(vram.data() + kClutCbp * 256u + 0u * 4u, &kBlack, sizeof(kBlack)); + std::memcpy(vram.data() + kClutCbp * 256u + 1u * 4u, &kWhite, sizeof(kWhite)); + + auto packFloat = [](float value) -> uint32_t + { + uint32_t bits = 0u; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; + }; + + auto packSt = [&](float s, float tVal) -> uint64_t + { + return static_cast(packFloat(s)) | + (static_cast(packFloat(tVal)) << 32); + }; + + gs.writeRegister(GS_REG_FRAME_1, kFrame); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_ALPHA_1, 0ull); + gs.writeRegister(GS_REG_TEX0_1, kTex0); + gs.writeRegister(GS_REG_TEX1_1, tex1Reg); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, kRgbaq); + gs.writeRegister(GS_REG_ST, packSt(0.0f, 0.0f)); + gs.writeRegister(GS_REG_XYZ2, 0ull); + gs.writeRegister(GS_REG_ST, packSt(1.0f, 0.0f)); + gs.writeRegister(GS_REG_XYZ2, (64ull << 0) | (0ull << 16)); + gs.writeRegister(GS_REG_ST, packSt(0.0f, 0.0f)); + gs.writeRegister(GS_REG_XYZ2, (0ull << 0) | (64ull << 16)); + + return readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 1u); + }; + + constexpr uint64_t kTex1Linear = + (1ull << 5) | + (1ull << 6); + + const uint32_t nearestPixel = renderSamplePixel(0ull); + const uint32_t linearPixel = renderSamplePixel(kTex1Linear); + + t.Equals(nearestPixel, 0x80000000u, + "point sampling should keep the sampled STQ triangle pixel on texel 0"); + + const uint8_t linearR = static_cast(linearPixel & 0xFFu); + const uint8_t linearA = static_cast((linearPixel >> 24) & 0xFFu); + t.IsTrue(linearR > 0x10u && linearR < 0x70u, + "linear filtering should blend the STQ triangle sample between black and white T4 texels"); + t.Equals(linearA, static_cast(0x80u), + "linear filtering should preserve the shared opaque alpha from the CLUT entries"); }); tc.Run("GS alpha test AFAIL framebuffer-only still writes the pixel", [](TestCase &t) @@ -843,6 +2958,198 @@ void register_ps2_gs_tests() "AFAIL=RGB_ONLY should update RGB while preserving destination alpha"); }); + tc.Run("GS triangle fan subpixel quad fills rows without interior holes", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kScissor = + (0ull << 0) | + (31ull << 16) | + (0ull << 32) | + (31ull << 48); + constexpr uint64_t kPrim = + static_cast(GS_PRIM_TRIFAN); + constexpr uint64_t kRgbaq = + 0xFFull | + (0xFFull << 8) | + (0xFFull << 16) | + (0x80ull << 24) | + (0x3F800000ull << 32); // q = 1.0f + auto makeXyzf = [](uint16_t x, uint16_t y) -> uint64_t + { + return static_cast(x) | + (static_cast(y) << 16); + }; + + gs.writeRegister(GS_REG_FRAME_1, kFrame); + gs.writeRegister(GS_REG_SCISSOR_1, kScissor); + gs.writeRegister(GS_REG_XYOFFSET_1, 0ull); + gs.writeRegister(GS_REG_TEST_1, 0ull); + gs.writeRegister(GS_REG_PRIM, kPrim); + gs.writeRegister(GS_REG_RGBAQ, kRgbaq); + gs.writeRegister(GS_REG_XYZF2, makeXyzf(102u, 102u)); + gs.writeRegister(GS_REG_XYZF2, makeXyzf(420u, 102u)); + gs.writeRegister(GS_REG_XYZF2, makeXyzf(420u, 420u)); + gs.writeRegister(GS_REG_XYZF2, makeXyzf(102u, 420u)); + + bool sawFilledRow = false; + for (uint32_t y = 6u; y <= 26u; ++y) + { + int first = -1; + int last = -1; + for (uint32_t x = 6u; x <= 26u; ++x) + { + const size_t offset = (static_cast(y) * 64u + static_cast(x)) * 4u; + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data() + offset, sizeof(pixel)); + if ((pixel & 0x00FFFFFFu) != 0u) + { + if (first < 0) + { + first = static_cast(x); + } + last = static_cast(x); + } + } + + if (first < 0 || last < 0) + { + continue; + } + + sawFilledRow = true; + for (int x = first; x <= last; ++x) + { + const size_t offset = (static_cast(y) * 64u + static_cast(x)) * 4u; + uint32_t pixel = 0u; + std::memcpy(&pixel, vram.data() + offset, sizeof(pixel)); + if ((pixel & 0x00FFFFFFu) == 0u) + { + t.Fail("triangle fan quad should not leave interior holes within a covered row"); + break; + } + } + } + + t.IsTrue(sawFilledRow, + "triangle fan quad should light at least one framebuffer row"); + }); + + tc.Run("sceGsExecLoadImage and sceGsExecStoreImage roundtrip and free guest packets", [](TestCase &t) + { + PS2Runtime runtime; + t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed"); + uint8_t *const rdram = runtime.memory().getRDRAM(); + constexpr uint32_t kImageAddr = 0x4000u; + constexpr uint32_t kSrcAddr = 0x5000u; + constexpr uint32_t kDstAddr = 0x6000u; + + const GsImageMem image{0u, 0u, 2u, 2u, 0u, 1u, 0u}; + const uint8_t pixels[16] = { + 0x10u, 0x20u, 0x30u, 0x40u, + 0x50u, 0x60u, 0x70u, 0x80u, + 0x90u, 0xA0u, 0xB0u, 0xC0u, + 0xD0u, 0xE0u, 0xF0u, 0xFFu, + }; + + writeGsImageTest(rdram, kImageAddr, image); + std::memcpy(rdram + kSrcAddr, pixels, sizeof(pixels)); + + R5900Context loadCtx{}; + setRegU32(loadCtx, 4, kImageAddr); + setRegU32(loadCtx, 5, kSrcAddr); + ps2_stubs::sceGsExecLoadImage(rdram, &loadCtx, &runtime); + t.Equals(static_cast(getRegU32Test(loadCtx, 2)), 0, + "sceGsExecLoadImage should succeed for a simple CT32 upload"); + uint64_t loadTag = 0u; + std::memcpy(&loadTag, rdram + runtime.guestHeapBase(), sizeof(loadTag)); + t.Equals(loadTag, 0x1000000000008004ull, + "sceGsExecLoadImage should populate the packed A+D GIF tag in guest RAM"); + uint64_t loadReg1 = 0u; + uint64_t loadReg2 = 0u; + uint64_t loadReg3 = 0u; + uint64_t loadReg4 = 0u; + std::memcpy(&loadReg1, rdram + runtime.guestHeapBase() + 24u, sizeof(loadReg1)); + std::memcpy(&loadReg2, rdram + runtime.guestHeapBase() + 40u, sizeof(loadReg2)); + std::memcpy(&loadReg3, rdram + runtime.guestHeapBase() + 56u, sizeof(loadReg3)); + std::memcpy(&loadReg4, rdram + runtime.guestHeapBase() + 72u, sizeof(loadReg4)); + t.Equals(loadReg1, 0x50ull, "sceGsExecLoadImage should encode BITBLTBUF as A+D register 0x50"); + t.Equals(loadReg2, 0x51ull, "sceGsExecLoadImage should encode TRXPOS as A+D register 0x51"); + t.Equals(loadReg3, 0x52ull, "sceGsExecLoadImage should encode TRXREG as A+D register 0x52"); + t.Equals(loadReg4, 0x53ull, "sceGsExecLoadImage should encode TRXDIR as A+D register 0x53"); + expectGuestHeapReusable(t, runtime, + "sceGsExecLoadImage should free its temporary GIF packet"); + + R5900Context storeCtx{}; + setRegU32(storeCtx, 4, kImageAddr); + setRegU32(storeCtx, 5, kDstAddr); + ps2_stubs::sceGsExecStoreImage(rdram, &storeCtx, &runtime); + t.Equals(static_cast(getRegU32Test(storeCtx, 2)), 0, + "sceGsExecStoreImage should succeed for a matching CT32 readback"); + uint64_t storeTag = 0u; + std::memcpy(&storeTag, rdram + runtime.guestHeapBase(), sizeof(storeTag)); + t.Equals(storeTag, 0x1000000000008004ull, + "sceGsExecStoreImage should populate the packed A+D GIF tag in guest RAM"); + uint64_t storeReg1 = 0u; + uint64_t storeReg2 = 0u; + uint64_t storeReg3 = 0u; + uint64_t storeReg4 = 0u; + std::memcpy(&storeReg1, rdram + runtime.guestHeapBase() + 24u, sizeof(storeReg1)); + std::memcpy(&storeReg2, rdram + runtime.guestHeapBase() + 40u, sizeof(storeReg2)); + std::memcpy(&storeReg3, rdram + runtime.guestHeapBase() + 56u, sizeof(storeReg3)); + std::memcpy(&storeReg4, rdram + runtime.guestHeapBase() + 72u, sizeof(storeReg4)); + t.Equals(storeReg1, 0x50ull, "sceGsExecStoreImage should encode BITBLTBUF as A+D register 0x50"); + t.Equals(storeReg2, 0x51ull, "sceGsExecStoreImage should encode TRXPOS as A+D register 0x51"); + t.Equals(storeReg3, 0x52ull, "sceGsExecStoreImage should encode TRXREG as A+D register 0x52"); + t.Equals(storeReg4, 0x53ull, "sceGsExecStoreImage should encode TRXDIR as A+D register 0x53"); + expectGuestHeapReusable(t, runtime, + "sceGsExecStoreImage should free its temporary GIF packet"); + + bool roundtripOk = true; + size_t mismatchIndex = 0u; + for (size_t i = 0; i < sizeof(pixels); ++i) + { + if (rdram[kDstAddr + i] != pixels[i]) + { + roundtripOk = false; + mismatchIndex = i; + break; + } + } + if (!roundtripOk) + { + t.Fail("sceGsExecLoadImage/sceGsExecStoreImage should roundtrip CT32 pixel data " + "(first mismatch at byte " + std::to_string(mismatchIndex) + + ", got " + std::to_string(rdram[kDstAddr + mismatchIndex]) + + ", expected " + std::to_string(pixels[mismatchIndex]) + ")"); + } + }); + + tc.Run("sceGsResetGraph frees its temporary GIF packet", [](TestCase &t) + { + PS2Runtime runtime; + t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed"); + + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + setRegU32(ctx, 4, 0u); + setRegU32(ctx, 5, 1u); + setRegU32(ctx, 6, 2u); + setRegU32(ctx, 7, 1u); + ps2_stubs::sceGsResetGraph(rdram.data(), &ctx, &runtime); + + t.Equals(static_cast(getRegU32Test(ctx, 2)), 0, + "sceGsResetGraph should succeed in reset mode"); + expectGuestHeapReusable(t, runtime, + "sceGsResetGraph should free its temporary GIF packet"); + }); + tc.Run("sceGsSyncV waits on VBlank and reports interlaced field parity", [](TestCase &t) { notifyRuntimeStop(); diff --git a/ps2xTest/src/ps2_memory_tests.cpp b/ps2xTest/src/ps2_memory_tests.cpp index 91ce9f62..8edcfc9f 100644 --- a/ps2xTest/src/ps2_memory_tests.cpp +++ b/ps2xTest/src/ps2_memory_tests.cpp @@ -1,8 +1,12 @@ #include "MiniTest.h" -#include "ps2_memory.h" -#include "ps2_gs_gpu.h" -#include "ps2_vu1.h" +#include "runtime/ps2_memory.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_gs_psmct32.h" +#include "runtime/ps2_vu1.h" +#include "ps2_runtime.h" #include "ps2_runtime_macros.h" +#include "Stubs/DMA.h" +#include "Stubs/GS.h" #include #include @@ -18,6 +22,11 @@ namespace static_cast(imm); } + void setRegU32(R5900Context &ctx, int reg, uint32_t value) + { + ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); + } + void appendU32(std::vector &dst, uint32_t value) { const size_t pos = dst.size(); @@ -900,6 +909,127 @@ void register_ps2_memory_tests() t.Equals(mem.readIORegister(kVif1Ch + 0x20u), 0u, "VIF1 QWC should be cleared after drain"); }); + tc.Run("VIF1 DMA chain preserves compact tag high bytes for DIRECT packets", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + constexpr uint32_t kVif1Ch = 0x10009000u; + constexpr uint32_t kTag = 0x00025000u; + + uint8_t *rdram = mem.getRDRAM(); + std::memset(rdram + kTag, 0, 32u); + + const uint64_t endTag = makeDmaTag(1u, 7u, 0u, false); + std::memcpy(rdram + kTag, &endTag, sizeof(endTag)); + + // Compact VIF1 packet helpers place the DIRECT command in the tag's upper 64 bits. + const uint32_t directCmd = makeVifCmd(0x50u, 0u, 1u); + std::memcpy(rdram + kTag + 12u, &directCmd, sizeof(directCmd)); + for (uint32_t i = 0; i < 16u; ++i) + { + rdram[kTag + 16u + i] = static_cast(0x70u + i); + } + + std::vector> captured; + mem.setGifPacketCallback([&](const uint8_t *data, uint32_t sizeBytes) + { + captured.emplace_back(data, data + sizeBytes); + }); + + t.IsTrue(mem.writeIORegister(kVif1Ch + 0x30u, kTag), "write VIF1 TADR should succeed"); + t.IsTrue(mem.writeIORegister(kVif1Ch + 0x00u, 0x104u), "write VIF1 CHCR STR|CHAIN should succeed"); + + mem.processPendingTransfers(); + + t.Equals(captured.size(), static_cast(1u), "compact VIF1 chain should emit one GIF packet"); + t.Equals(captured[0].size(), static_cast(16u), "compact VIF1 DIRECT packet should be 1 QW"); + + bool payloadOk = true; + for (uint32_t i = 0; i < 16u; ++i) + { + if (captured[0][i] != static_cast(0x70u + i)) + { + payloadOk = false; + break; + } + } + t.IsTrue(payloadOk, "compact VIF1 chain payload should reach the GIF callback"); + t.IsTrue((mem.readIORegister(kVif1Ch + 0x00u) & 0x100u) == 0u, + "compact VIF1 chain should clear the STR bit after drain"); + }); + + tc.Run("VIF1 packet builders keep chain qwc live before terminate", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + constexpr uint32_t kVif1Ch = 0x10009000u; + constexpr uint32_t kStateAddr = 0x00027000u; + constexpr uint32_t kBaseAddr = 0x00027100u; + + uint8_t *rdram = mem.getRDRAM(); + std::memset(rdram + kStateAddr, 0, 0x200u); + + R5900Context ctx{}; + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, kBaseAddr); + ps2_stubs::sceVif1PkInit(rdram, &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, 0u); + ps2_stubs::sceVif1PkCnt(rdram, &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, 0u); + ps2_stubs::sceVif1PkOpenDirectCode(rdram, &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, 4u); // reserve one qword of DIRECT payload + ps2_stubs::sceVif1PkReserve(rdram, &ctx, nullptr); + const uint32_t payloadAddr = ::getRegU32(&ctx, 2); + for (uint32_t i = 0; i < 16u; ++i) + { + rdram[payloadAddr + i] = static_cast(0x30u + i); + } + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + ps2_stubs::sceVif1PkCloseDirectCode(rdram, &ctx, nullptr); + + uint32_t dmaTagWord = 0u; + std::memcpy(&dmaTagWord, rdram + kBaseAddr, sizeof(dmaTagWord)); + t.Equals(dmaTagWord & 0xFFFFu, 1u, "live packet head qwc should reflect one qword before terminate"); + + std::vector> captured; + mem.setGifPacketCallback([&](const uint8_t *data, uint32_t sizeBytes) + { + captured.emplace_back(data, data + sizeBytes); + }); + + t.IsTrue(mem.writeIORegister(kVif1Ch + 0x30u, kBaseAddr), "write VIF1 TADR should succeed"); + t.IsTrue(mem.writeIORegister(kVif1Ch + 0x00u, 0x104u), "write VIF1 CHCR STR|CHAIN should succeed"); + + mem.processPendingTransfers(); + + t.Equals(captured.size(), static_cast(1u), "live VIF1 packet should emit one GIF packet"); + t.Equals(captured[0].size(), static_cast(16u), "live VIF1 packet should emit one qword"); + + bool payloadOk = true; + for (uint32_t i = 0; i < 16u; ++i) + { + if (captured[0][i] != static_cast(0x30u + i)) + { + payloadOk = false; + break; + } + } + t.IsTrue(payloadOk, "live VIF1 packet payload should reach the GIF callback"); + }); + tc.Run("GIF DMA chain CALL sources payload from TADR+16", [](TestCase &t) { PS2Memory mem; @@ -1139,6 +1269,38 @@ void register_ps2_memory_tests() } }); + tc.Run("sceDmaReset re-enables DMAC DMAE", [](TestCase &t) + { + PS2Runtime runtime; + t.IsTrue(runtime.memory().initialize(), "PS2Memory initialize should succeed"); + + constexpr uint32_t kDctrl = 0x1000E000u; + constexpr uint32_t kDpcr = 0x1000E020u; + constexpr uint32_t kDsqwc = 0x1000E030u; + constexpr uint32_t kDrbor = 0x1000E050u; + constexpr uint32_t kDrbsr = 0x1000E040u; + constexpr uint32_t kDstadr = 0x1000E060u; + + PS2Memory &mem = runtime.memory(); + t.IsTrue(mem.writeIORegister(kDctrl, 0u), "clearing D_CTRL should succeed"); + t.IsTrue(mem.writeIORegister(kDpcr, 0x12345678u), "writing D_PCR should succeed"); + t.IsTrue(mem.writeIORegister(kDsqwc, 0x11220044u), "writing D_SQWC should succeed"); + t.IsTrue(mem.writeIORegister(kDrbor, 0x2000u), "writing D_RBOR should succeed"); + t.IsTrue(mem.writeIORegister(kDrbsr, 0x3FFFu), "writing D_RBSR should succeed"); + t.IsTrue(mem.writeIORegister(kDstadr, 0x4567u), "writing D_STADR should succeed"); + + R5900Context ctx{}; + ps2_stubs::sceDmaReset(mem.getRDRAM(), &ctx, &runtime); + + t.Equals(static_cast(::getRegU32(&ctx, 2)), 0, "sceDmaReset should return 0"); + t.Equals(mem.readIORegister(kDctrl), 1u, "sceDmaReset should leave D_CTRL DMAE enabled"); + t.Equals(mem.readIORegister(kDpcr), 0u, "sceDmaReset should clear D_PCR"); + t.Equals(mem.readIORegister(kDsqwc), 0u, "sceDmaReset should clear D_SQWC"); + t.Equals(mem.readIORegister(kDrbor), 0u, "sceDmaReset should clear D_RBOR"); + t.Equals(mem.readIORegister(kDrbsr), 0u, "sceDmaReset should clear D_RBSR"); + t.Equals(mem.readIORegister(kDstadr), 0u, "sceDmaReset should clear D_STADR"); + }); + tc.Run("VU1 XGKICK wraps packet payload across VU1 memory boundary", [](TestCase &t) { PS2Memory mem; @@ -1257,17 +1419,74 @@ void register_ps2_memory_tests() const uint8_t *vramOut = mem.getGSVRAM(); bool imageOk = true; - for (uint32_t i = 0; i < 16u; ++i) + for (uint32_t x = 0; x < 4u && imageOk; ++x) { - if (vramOut[i] != static_cast(0x70u + i)) + const uint32_t off = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u); + for (uint32_t c = 0; c < 4u; ++c) { - imageOk = false; - break; + if (vramOut[off + c] != static_cast(0x70u + x * 4u + c)) + { + imageOk = false; + break; + } } } t.IsTrue(imageOk, "VIF1 DIRECT image should update GS VRAM through GIF path2"); }); + tc.Run("VIF1 DIRECT image tag can continue with raw image qwords", [](TestCase &t) + { + PS2Memory mem; + t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed"); + + GS gs; + gs.init(mem.getGSVRAM(), static_cast(PS2_GS_VRAM_SIZE), &mem.gs()); + GifArbiter arbiter([&](const uint8_t *data, uint32_t sizeBytes) + { + gs.processGIFPacket(data, sizeBytes); + }); + mem.setGifArbiter(&arbiter); + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(0u) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(0u) << 56); + gs.writeRegister(GS_REG_BITBLTBUF, bitblt); + gs.writeRegister(GS_REG_TRXPOS, 0ull); + gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32)); + gs.writeRegister(GS_REG_TRXDIR, 0ull); + + std::vector packet; + appendU32(packet, makeVifCmd(0x50u, 0u, 1u)); // DIRECT 1 QW payload: GIF IMAGE tag only. + appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packet, 0ull); + for (uint32_t i = 0; i < 16u; ++i) + { + packet.push_back(static_cast(0xA0u + i)); + } + + mem.processVIF1Data(packet.data(), static_cast(packet.size())); + + const uint8_t *vramOut = mem.getGSVRAM(); + bool imageOk = true; + for (uint32_t x = 0; x < 4u && imageOk; ++x) + { + const uint32_t off = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u); + for (uint32_t c = 0; c < 4u; ++c) + { + if (vramOut[off + c] != static_cast(0xA0u + x * 4u + c)) + { + imageOk = false; + break; + } + } + } + t.IsTrue(imageOk, "raw qwords after a DIRECT image tag should continue the PATH2 image upload"); + }); + tc.Run("VIF MSCAL callback can execute XGKICK and update GS VRAM", [](TestCase &t) { PS2Memory mem; @@ -1331,12 +1550,16 @@ void register_ps2_memory_tests() const uint8_t *vramOut = mem.getGSVRAM(); bool imageOk = true; - for (uint32_t i = 0; i < 16u; ++i) + for (uint32_t x = 0; x < 4u && imageOk; ++x) { - if (vramOut[i] != static_cast(0x90u + i)) + const uint32_t off = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u); + for (uint32_t c = 0; c < 4u; ++c) { - imageOk = false; - break; + if (vramOut[off + c] != static_cast(0x90u + x * 4u + c)) + { + imageOk = false; + break; + } } } t.IsTrue(imageOk, "MSCAL-triggered XGKICK should route PATH1 packet into GS VRAM"); diff --git a/ps2xTest/src/ps2_recompiler_tests.cpp b/ps2xTest/src/ps2_recompiler_tests.cpp index bbaaacd4..de6c29bc 100644 --- a/ps2xTest/src/ps2_recompiler_tests.cpp +++ b/ps2xTest/src/ps2_recompiler_tests.cpp @@ -4,6 +4,7 @@ #include "ps2recomp/elf_parser.h" #include "ps2recomp/instructions.h" #include "ps2recomp/types.h" +#include "ps2_runtime_calls.h" #include #include #include @@ -158,6 +159,43 @@ void register_ps2_recompiler_tests() { MiniTest::Case("PS2Recompiler", [](TestCase &tc) { + tc.Run("game helpers are not classified as runtime stubs", [](TestCase &t) { + t.IsFalse(ps2_runtime_calls::isStubName("Pad_init"), + "Pad_init should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("Pad_set"), + "Pad_set should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("pdInitPeripheral"), + "pdInitPeripheral should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("pdGetPeripheral"), + "pdGetPeripheral should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("InitThread"), + "InitThread should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("syFree"), + "syFree should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("syMallocInit"), + "syMallocInit should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("syHwInit"), + "syHwInit should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("syHwInit2"), + "syHwInit2 should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("syRtcInit"), + "syRtcInit should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("sdDrvInit"), + "sdDrvInit should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("sdSndStopAll"), + "sdSndStopAll should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("sdSysFinish"), + "sdSysFinish should be recompiled as game code"); + t.IsFalse(ps2_runtime_calls::isStubName("iopGetArea"), + "iopGetArea should be recompiled as game code"); + t.IsTrue(ps2_runtime_calls::isStubName("builtin_set_imask"), + "builtin_set_imask should remain a runtime helper"); + t.IsTrue(ps2_runtime_calls::isStubName("getpid"), + "getpid should remain a runtime helper"); + t.IsTrue(ps2_runtime_calls::isStubName("scePadRead"), + "scePadRead should remain a runtime pad stub"); + }); + tc.Run("additional entries split at nearest discovered boundary", [](TestCase &t) { std::vector
sections = { {".text", 0x1000u, 0x3000u, 0u, true, false, false, true, nullptr} @@ -187,8 +225,8 @@ void register_ps2_recompiler_tests() size_t discovered = PS2Recompiler::DiscoverAdditionalEntryPoints( functions, decodedFunctions, sections); - t.Equals(discovered, static_cast(2), - "expected two additional entries to be discovered"); + t.Equals(discovered, static_cast(3), + "expected two mid-function targets plus the JAL return entry to be discovered"); auto findByStart = [&](uint32_t start) -> const Function* { auto it = std::find_if(functions.begin(), functions.end(), @@ -202,8 +240,10 @@ void register_ps2_recompiler_tests() const Function *entry1008 = findByStart(0x1008u); const Function *entry100C = findByStart(0x100Cu); + const Function *entry2008 = findByStart(0x2008u); t.IsNotNull(entry1008, "entry at 0x1008 should exist"); t.IsNotNull(entry100C, "entry at 0x100C should exist"); + t.IsNotNull(entry2008, "JAL return address entry at 0x2008 should exist"); if (entry1008 && entry100C) { t.Equals(entry1008->end, 0x100Cu, @@ -211,11 +251,18 @@ void register_ps2_recompiler_tests() t.Equals(entry100C->end, 0x1018u, "entry 0x100C should end at containing function end"); } + if (entry2008) + { + t.Equals(entry2008->end, 0x2010u, + "return entry 0x2008 should slice through the caller tail"); + } auto decoded1008It = decodedFunctions.find(0x1008u); auto decoded100CIt = decodedFunctions.find(0x100Cu); + auto decoded2008It = decodedFunctions.find(0x2008u); t.IsTrue(decoded1008It != decodedFunctions.end(), "decoded slice for 0x1008 should exist"); t.IsTrue(decoded100CIt != decodedFunctions.end(), "decoded slice for 0x100C should exist"); + t.IsTrue(decoded2008It != decodedFunctions.end(), "decoded slice for 0x2008 should exist"); if (decoded1008It != decodedFunctions.end()) { t.Equals(decoded1008It->second.size(), static_cast(1), @@ -231,6 +278,16 @@ void register_ps2_recompiler_tests() t.Equals(decoded100CIt->second.front().address, 0x100Cu, "entry 0x100C slice should begin at 0x100C"); } + if (decoded2008It != decodedFunctions.end()) + { + t.Equals(decoded2008It->second.size(), static_cast(2), + "return entry 0x2008 slice should keep the jump and its delay slot"); + if (!decoded2008It->second.empty()) + { + t.Equals(decoded2008It->second.front().address, 0x2008u, + "return entry 0x2008 slice should begin at the JAL fallthrough"); + } + } }); tc.Run("entry reslice trims earlier entries after late discovery", [](TestCase &t) { @@ -311,7 +368,7 @@ void register_ps2_recompiler_tests() } }); - tc.Run("same-function JAL targets get entry wrappers but J targets stay labels", [](TestCase &t) { + tc.Run("same-function JAL return addresses get entry wrappers but targets stay labels", [](TestCase &t) { std::vector
sections = { {".text", 0x1000u, 0x40u, 0u, true, false, false, true, nullptr} }; @@ -335,8 +392,11 @@ void register_ps2_recompiler_tests() functions, decodedFunctions, sections); t.Equals(discovered, static_cast(1), - "same-function JAL should create one entry while plain J stays internal"); + "same-function JAL should create only the resume entry while plain J stays internal"); + const bool hasResumeEntry = std::any_of( + functions.begin(), functions.end(), + [](const Function &fn) { return fn.start == 0x1008u; }); const bool hasCallEntry = std::any_of( functions.begin(), functions.end(), [](const Function &fn) { return fn.start == 0x100Cu; }); @@ -344,10 +404,142 @@ void register_ps2_recompiler_tests() functions.begin(), functions.end(), [](const Function &fn) { return fn.start == 0x1014u && fn.name.rfind("entry_", 0) == 0; }); - t.IsTrue(hasCallEntry, "same-function JAL target should be promoted to an entry wrapper"); + t.IsTrue(hasResumeEntry, "same-function JAL return address should be promoted to a resumable entry"); + t.IsFalse(hasCallEntry, "same-function JAL target should remain an internal label"); t.IsFalse(hasJumpEntry, "same-function J target should remain an internal label only"); }); + tc.Run("JAL return addresses get resumable entry wrappers", [](TestCase &t) { + std::vector
sections = { + {".text", 0x1000u, 0x2000u, 0u, true, false, false, true, nullptr} + }; + + std::vector functions = { + makeFunction("caller", 0x1000u, 0x1018u), + makeFunction("callee", 0x2000u, 0x2008u) + }; + + std::unordered_map> decodedFunctions; + decodedFunctions[0x1000u] = { + makeAbsJump(0x1000u, 0x2000u, OPCODE_JAL), + makeNopLike(0x1004u), + makeNopLike(0x1008u), + makeNopLike(0x100Cu), + makeJrRa(0x1010u), + makeNopLike(0x1014u) + }; + decodedFunctions[0x2000u] = { + makeJrRa(0x2000u), + makeNopLike(0x2004u) + }; + + size_t discovered = PS2Recompiler::DiscoverAdditionalEntryPoints( + functions, decodedFunctions, sections); + t.Equals(discovered, static_cast(1), + "external JAL should create one resumable entry at the caller return address"); + + auto entryIt = std::find_if(functions.begin(), functions.end(), + [](const Function &fn) { return fn.start == 0x1008u; }); + t.IsTrue(entryIt != functions.end(), "return address 0x1008 should be promoted to an entry wrapper"); + if (entryIt != functions.end()) + { + t.Equals(entryIt->end, 0x1018u, + "return-address entry should slice through the remainder of the caller"); + } + + auto decodedEntryIt = decodedFunctions.find(0x1008u); + t.IsTrue(decodedEntryIt != decodedFunctions.end(), + "decoded entry slice for the caller return address should exist"); + if (decodedEntryIt != decodedFunctions.end()) + { + t.Equals(decodedEntryIt->second.size(), static_cast(4), + "return-address entry slice should keep the caller tail"); + if (!decodedEntryIt->second.empty()) + { + t.Equals(decodedEntryIt->second.front().address, 0x1008u, + "return-address entry slice should begin at the JAL fallthrough"); + } + } + }); + + tc.Run("JAL to an already-known function still discovers the return entry", [](TestCase &t) { + std::vector
sections = { + {".text", 0x1000u, 0x2000u, 0u, true, false, false, true, nullptr} + }; + + std::vector functions = { + makeFunction("caller", 0x1000u, 0x1020u), + makeFunction("callee", 0x1100u, 0x1108u) + }; + + std::unordered_map> decodedFunctions; + decodedFunctions[0x1000u] = { + makeNopLike(0x1000u), + makeNopLike(0x1004u), + makeAbsJump(0x1008u, 0x1100u, OPCODE_JAL), + makeNopLike(0x100Cu), + makeNopLike(0x1010u), + makeNopLike(0x1014u), + makeJrRa(0x1018u), + makeNopLike(0x101Cu) + }; + decodedFunctions[0x1100u] = { + makeJrRa(0x1100u), + makeNopLike(0x1104u) + }; + + size_t discovered = PS2Recompiler::DiscoverAdditionalEntryPoints( + functions, decodedFunctions, sections); + t.Equals(discovered, static_cast(1), + "return entry should still be discovered even when the JAL target is already registered"); + + auto entryIt = std::find_if(functions.begin(), functions.end(), + [](const Function &fn) { return fn.start == 0x1010u; }); + t.IsTrue(entryIt != functions.end(), + "return address 0x1010 should be emitted as a resumable entry"); + if (entryIt != functions.end()) + { + t.Equals(entryIt->end, 0x1020u, + "return entry should cover the remaining caller tail"); + } + }); + + tc.Run("discovery ignores synthetic entry wrappers", [](TestCase &t) { + std::vector
sections = { + {".text", 0x1000u, 0x2000u, 0u, true, false, false, true, nullptr} + }; + + std::vector functions = { + makeFunction("entry_1008", 0x1008u, 0x1020u), + makeFunction("callee", 0x1100u, 0x1108u) + }; + + std::unordered_map> decodedFunctions; + decodedFunctions[0x1008u] = { + makeAbsJump(0x1008u, 0x1100u, OPCODE_JAL), + makeNopLike(0x100Cu), + makeNopLike(0x1010u), + makeNopLike(0x1014u), + makeJrRa(0x1018u), + makeNopLike(0x101Cu) + }; + decodedFunctions[0x1100u] = { + makeJrRa(0x1100u), + makeNopLike(0x1104u) + }; + + size_t discovered = PS2Recompiler::DiscoverAdditionalEntryPoints( + functions, decodedFunctions, sections); + t.Equals(discovered, static_cast(0), + "synthetic entry wrappers should not recursively produce more entries"); + + const bool hasRecursiveResumeEntry = std::any_of( + functions.begin(), functions.end(), + [](const Function &fn) { return fn.start == 0x1010u; }); + t.IsFalse(hasRecursiveResumeEntry, + "discovery should not promote a return entry out of an existing entry wrapper"); + }); + tc.Run("entry reslice handles entries without containing function", [](TestCase &t) { std::vector functions = { makeFunction("entry_1008", 0x1008u, 0x1018u), @@ -659,6 +851,21 @@ void register_ps2_recompiler_tests() std::filesystem::remove(mapPath, removeError); }); + tc.Run("runtime call resolution includes Veronica compatibility aliases", [](TestCase &t) { + t.Equals(ps2_runtime_calls::resolveSyscallName("ReleaseAlarm"), std::string_view{"ReleaseAlarm"}, + "ReleaseAlarm should resolve as a syscall name"); + t.Equals(ps2_runtime_calls::resolveSyscallName("_ReleaseAlarm"), std::string_view{"ReleaseAlarm"}, + "underscore ReleaseAlarm alias should resolve to ReleaseAlarm"); + t.Equals(ps2_runtime_calls::resolveSyscallName("EnableCache"), std::string_view{"EnableCache"}, + "EnableCache should resolve as a syscall name"); + t.Equals(ps2_runtime_calls::resolveSyscallName("DisableCache"), std::string_view{"DisableCache"}, + "DisableCache should resolve as a syscall name"); + t.Equals(ps2_runtime_calls::resolveStubName("isceSifSetDma"), std::string_view{"isceSifSetDma"}, + "isceSifSetDma should resolve as a stub name"); + t.Equals(ps2_runtime_calls::resolveStubName("isceSifSetDChain"), std::string_view{"isceSifSetDChain"}, + "isceSifSetDChain should resolve as a stub name"); + }); + tc.Run("respect max length for .cpp filenames", [](TestCase& t) { t.IsTrue(PS2Recompiler::ClampFilenameLength("ReallyLongFunctionNameReallyLongFunctionNameReallyLongFunctionName_0x12345678",".cpp",50).length() <= 50,"Function name must be max 50 characters"); diff --git a/ps2xTest/src/ps2_runtime_expansion_tests.cpp b/ps2xTest/src/ps2_runtime_expansion_tests.cpp index 6632d668..7941743a 100644 --- a/ps2xTest/src/ps2_runtime_expansion_tests.cpp +++ b/ps2xTest/src/ps2_runtime_expansion_tests.cpp @@ -4,11 +4,16 @@ #include "ps2recomp/r5900_decoder.h" #include "ps2recomp/types.h" #include "ps2_runtime.h" -#include "ps2_memory.h" +#include "runtime/ps2_memory.h" #include "ps2_syscalls.h" #include "ps2_stubs.h" -#include "ps2_gs_gpu.h" +#include "runtime/ps2_gs_gpu.h" +#include "runtime/ps2_gs_psmct32.h" #include "ps2_runtime_macros.h" +#include "Stubs/MPEG.h" +#include "Stubs/Audio.h" +#include "Stubs/GS.h" +#include "Stubs/VU.h" #include #include @@ -78,8 +83,7 @@ namespace uint32_t frameOffsetBytes(uint32_t x, uint32_t y, uint32_t fbw) { - const uint32_t stride = fbw * 64u * 4u; // CT32 - return y * stride + x * 4u; + return GSPSMCT32::addrPSMCT32(0u, (fbw != 0u) ? fbw : 1u, x, y); } void testRuntimeWorkerLoop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -104,9 +108,9 @@ namespace std::atomic gSerializedGuestActive{0}; std::atomic gSerializedGuestMaxActive{0}; - std::atomic gCooperativeYieldEntryCount{0}; - std::atomic gCooperativeYieldAllowFirstYield{false}; - std::atomic gCooperativeYieldPeerRan{false}; + std::atomic gPreemptionPolicyEntryCount{0}; + std::atomic gPreemptionPolicyAllowFirstProbe{false}; + std::atomic gPreemptionPolicyPeerRan{false}; void testSerializedGuestStep(uint8_t *, R5900Context *ctx, PS2Runtime *) { @@ -129,32 +133,33 @@ namespace } } - void testCooperativeGuestYieldStep(uint8_t *, R5900Context *ctx, PS2Runtime *runtime) + void testPreemptionPolicyStep(uint8_t *, R5900Context *ctx, PS2Runtime *runtime) { if (!ctx || !runtime) { return; } - const int32_t entryIndex = gCooperativeYieldEntryCount.fetch_add(1, std::memory_order_acq_rel) + 1; + const int32_t entryIndex = gPreemptionPolicyEntryCount.fetch_add(1, std::memory_order_acq_rel) + 1; if (entryIndex == 1) { - while (!gCooperativeYieldAllowFirstYield.load(std::memory_order_acquire)) + while (!gPreemptionPolicyAllowFirstProbe.load(std::memory_order_acquire)) { std::this_thread::yield(); } - for (int attempt = 0; attempt < 32 && - !gCooperativeYieldPeerRan.load(std::memory_order_acquire); + bool shouldPreempt = false; + for (int attempt = 0; attempt < 256 && + !shouldPreempt; ++attempt) { - runtime->cooperativeGuestYield(); + shouldPreempt = runtime->shouldPreemptGuestExecution(); } - setRegU32(*ctx, 2, gCooperativeYieldPeerRan.load(std::memory_order_acquire) ? 1u : 0u); + setRegU32(*ctx, 2, shouldPreempt ? 1u : 0u); } else { - gCooperativeYieldPeerRan.store(true, std::memory_order_release); + gPreemptionPolicyPeerRan.store(true, std::memory_order_release); setRegU32(*ctx, 2, 2u); } @@ -211,6 +216,7 @@ namespace gAsyncCallbackObservedGp.store(::getRegU32(ctx, 28), std::memory_order_release); ctx->pc = 0u; } + } void register_ps2_runtime_expansion_tests() @@ -293,19 +299,19 @@ void register_ps2_runtime_expansion_tests() "dispatchLoop should not execute guest code concurrently on one runtime"); }); - tc.Run("cooperative guest yield lets another guest thread run without leaving the current function", [](TestCase &t) + tc.Run("guest preemption policy requests a dispatcher handoff when another guest thread contends", [](TestCase &t) { PS2Runtime runtime; std::vector rdram(PS2_RAM_SIZE, 0u); constexpr uint32_t kFirstEntry = 0x190000u; constexpr uint32_t kSecondEntry = 0x1A0000u; - gCooperativeYieldEntryCount.store(0, std::memory_order_release); - gCooperativeYieldAllowFirstYield.store(false, std::memory_order_release); - gCooperativeYieldPeerRan.store(false, std::memory_order_release); + gPreemptionPolicyEntryCount.store(0, std::memory_order_release); + gPreemptionPolicyAllowFirstProbe.store(false, std::memory_order_release); + gPreemptionPolicyPeerRan.store(false, std::memory_order_release); - runtime.registerFunction(kFirstEntry, &testCooperativeGuestYieldStep); - runtime.registerFunction(kSecondEntry, &testCooperativeGuestYieldStep); + runtime.registerFunction(kFirstEntry, &testPreemptionPolicyStep); + runtime.registerFunction(kSecondEntry, &testPreemptionPolicyStep); R5900Context firstCtx{}; R5900Context secondCtx{}; @@ -319,7 +325,7 @@ void register_ps2_runtime_expansion_tests() const bool firstEntered = waitUntil([&]() { - return gCooperativeYieldEntryCount.load(std::memory_order_acquire) >= 1; + return gPreemptionPolicyEntryCount.load(std::memory_order_acquire) >= 1; }, std::chrono::milliseconds(100)); std::thread secondWorker([&]() @@ -332,7 +338,7 @@ void register_ps2_runtime_expansion_tests() return runtime.guestExecutionWaiterCountForTesting() > 0u; }, std::chrono::milliseconds(100)); - gCooperativeYieldAllowFirstYield.store(true, std::memory_order_release); + gPreemptionPolicyAllowFirstProbe.store(true, std::memory_order_release); if (firstWorker.joinable()) { @@ -343,12 +349,12 @@ void register_ps2_runtime_expansion_tests() secondWorker.join(); } - t.IsTrue(firstEntered, "first guest worker should enter before yielding"); - t.IsTrue(secondContending, "second guest worker should contend for guest execution before the first yields"); - t.IsTrue(gCooperativeYieldPeerRan.load(std::memory_order_acquire), - "second guest worker should run while the first cooperatively yields"); + t.IsTrue(firstEntered, "first guest worker should enter before probing for preemption"); + t.IsTrue(secondContending, "second guest worker should contend for guest execution before the first returns"); + t.IsTrue(gPreemptionPolicyPeerRan.load(std::memory_order_acquire), + "second guest worker should run after the first returns to the dispatcher"); t.Equals(getRegU32(&firstCtx, 2), 1u, - "first guest worker should observe that a peer ran before it resumed"); + "first guest worker should observe that the runtime requested preemption under contention"); }); tc.Run("vblank intc handlers can preempt serialized guest execution", [](TestCase &t) @@ -459,6 +465,171 @@ void register_ps2_runtime_expansion_tests() notifyRuntimeStop(); }); + tc.Run("MPEG init and callback stubs return success instead of TODO errors", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0u); + ps2_stubs::resetMpegStubState(); + + R5900Context initCtx{}; + ps2_stubs::sceMpegInit(rdram.data(), &initCtx, nullptr); + t.Equals(getRegS32(initCtx, 2), 0, + "sceMpegInit should succeed so games can continue through movie setup"); + + R5900Context addCtx0{}; + setRegU32(addCtx0, 4, 0x00123000u); + setRegU32(addCtx0, 5, 1u); + setRegU32(addCtx0, 6, 0x00124000u); + setRegU32(addCtx0, 7, 0u); + ps2_stubs::sceMpegAddCallback(rdram.data(), &addCtx0, nullptr); + t.Equals(getRegS32(addCtx0, 2), 1, + "first sceMpegAddCallback should hand back a non-error callback handle"); + + R5900Context addCtx1{}; + setRegU32(addCtx1, 4, 0x00123000u); + setRegU32(addCtx1, 5, 2u); + setRegU32(addCtx1, 6, 0x00124010u); + setRegU32(addCtx1, 7, 0u); + ps2_stubs::sceMpegAddCallback(rdram.data(), &addCtx1, nullptr); + t.Equals(getRegS32(addCtx1, 2), 2, + "subsequent sceMpegAddCallback calls should keep succeeding"); + + R5900Context reinitCtx{}; + ps2_stubs::sceMpegInit(rdram.data(), &reinitCtx, nullptr); + + R5900Context addAfterReinit{}; + setRegU32(addAfterReinit, 4, 0x00123000u); + setRegU32(addAfterReinit, 5, 3u); + setRegU32(addAfterReinit, 6, 0x00124020u); + setRegU32(addAfterReinit, 7, 0u); + ps2_stubs::sceMpegAddCallback(rdram.data(), &addAfterReinit, nullptr); + t.Equals(getRegS32(addAfterReinit, 2), 1, + "sceMpegInit should reset MPEG callback bookkeeping between runs"); + }); + + tc.Run("movie startup MPEG and audio stubs return safe progress values", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0u); + ps2_stubs::clearMpegCompatLayout(); + ps2_stubs::resetMpegStubState(); + ps2_stubs::resetAudioStubState(); + + R5900Context firstIsEndCtx{}; + setRegU32(firstIsEndCtx, 4, 0x00123000u); + ps2_stubs::sceMpegIsEnd(rdram.data(), &firstIsEndCtx, nullptr); + t.Equals(getRegS32(firstIsEndCtx, 2), 0, + "sceMpegIsEnd should allow one synthetic frame before reporting end"); + + R5900Context demuxCtx{}; + setRegU32(demuxCtx, 4, 0x00123000u); + setRegU32(demuxCtx, 5, 0x00400000u); + setRegU32(demuxCtx, 6, 0x00004000u); + setRegU32(demuxCtx, 7, 0x00410000u); + ps2_stubs::sceMpegDemuxPssRing(rdram.data(), &demuxCtx, nullptr); + t.Equals(getRegS32(demuxCtx, 2), 0x4000, + "sceMpegDemuxPssRing should consume the provided input instead of trapping"); + + R5900Context getPictureCtx{}; + setRegU32(getPictureCtx, 4, 0x00123000u); + setRegU32(getPictureCtx, 5, 0x00124000u); + setRegU32(getPictureCtx, 6, 440u); + ps2_stubs::sceMpegGetPicture(rdram.data(), &getPictureCtx, nullptr); + t.Equals(Ps2FastRead32(rdram.data(), 0x00123000u + 0x00u), 320u, + "sceMpegGetPicture should seed a safe movie width"); + t.Equals(Ps2FastRead32(rdram.data(), 0x00123000u + 0x04u), 240u, + "sceMpegGetPicture should seed a safe movie height"); + t.Equals(Ps2FastRead32(rdram.data(), 0x00123000u + 0x08u), 0u, + "first synthetic picture should preserve frameCount==0 for guest setup"); + + R5900Context secondIsEndCtx{}; + setRegU32(secondIsEndCtx, 4, 0x00123000u); + ps2_stubs::sceMpegIsEnd(rdram.data(), &secondIsEndCtx, nullptr); + t.Equals(getRegS32(secondIsEndCtx, 2), 0, + "sceMpegIsEnd should keep the decode thread alive and let the guest stop playback"); + + R5900Context remoteInitCtx{}; + ps2_stubs::sceSdRemoteInit(rdram.data(), &remoteInitCtx, nullptr); + t.Equals(getRegS32(remoteInitCtx, 2), 0, + "sceSdRemoteInit should succeed so Veronica can set up movie audio"); + + R5900Context blockTransCtx{}; + const uint32_t blockTransSp = 0x00100000u; + setRegU32(blockTransCtx, 29, blockTransSp); + setRegU32(blockTransCtx, 4, 1u); + setRegU32(blockTransCtx, 5, 0x80E0u); + setRegU32(blockTransCtx, 6, 1u); + setRegU32(blockTransCtx, 7, 2u); + std::memcpy(rdram.data() + blockTransSp + 0x10u, "\x40\x23\x01\x00", 4u); + std::memcpy(rdram.data() + blockTransSp + 0x14u, "\x00\x30\x00\x00", 4u); + std::memcpy(rdram.data() + blockTransSp + 0x18u, "\x40\x27\x01\x00", 4u); + ps2_stubs::sceSdRemote(rdram.data(), &blockTransCtx, nullptr); + t.Equals(getRegU32(&blockTransCtx, 2), 0x00012340u, + "sceSdRemote block transfer should publish the current IOP ring position"); + + R5900Context statusCtx{}; + setRegU32(statusCtx, 29, blockTransSp); + setRegU32(statusCtx, 4, 1u); + setRegU32(statusCtx, 5, 0x80F0u); + setRegU32(statusCtx, 6, 1u); + setRegU32(statusCtx, 7, 0u); + std::memset(rdram.data() + blockTransSp + 0x10u, 0, 12u); + ps2_stubs::sceSdRemote(rdram.data(), &statusCtx, nullptr); + t.Equals(getRegU32(&statusCtx, 2), 0x00012340u, + "sceSdRemote status polling should reuse the last configured transfer base"); + + R5900Context setParamCtx{}; + setRegU32(setParamCtx, 29, blockTransSp); + setRegU32(setParamCtx, 4, 1u); + setRegU32(setParamCtx, 5, 0x8010u); + setRegU32(setParamCtx, 6, 0x0F81u); + setRegU32(setParamCtx, 7, 0u); + ps2_stubs::sceSdRemote(rdram.data(), &setParamCtx, nullptr); + t.Equals(getRegU32(&setParamCtx, 2), 0x00012340u, + "sceSdRemote set-param calls should not trap or disturb the movie audio state"); + }); + + tc.Run("IPU init skips missing optional helper instead of dispatching the default trap", [](TestCase &t) + { + PS2Runtime runtime; + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + ctx.pc = 0x0010B470u; + + ps2_stubs::sceIpuInit(rdram.data(), &ctx, &runtime); + + t.IsFalse(runtime.isStopRequested(), + "sceIpuInit should tolerate the missing optional SetD4 helper"); + t.Equals(runtime.memory().read32(0x10002010u), 0x40000000u, + "sceIpuInit should still program IPU_CTRL"); + t.Equals(runtime.memory().read32(0x10002000u), 0u, + "sceIpuInit should leave IPU_CMD reset after initialization"); + }); + + tc.Run("sprintf consumes EE varargs from a2 a3 t0 and preserves width formatting", [](TestCase &t) + { + PS2Runtime runtime; + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + + constexpr uint32_t kDestAddr = 0x00002000u; + constexpr uint32_t kFormatAddr = 0x00002100u; + constexpr char kFormat[] = "rm_%1d%02d%1d.rdx"; + + std::memcpy(rdram.data() + kFormatAddr, kFormat, sizeof(kFormat)); + setRegU32(ctx, 4, kDestAddr); + setRegU32(ctx, 5, kFormatAddr); + setRegU32(ctx, 6, 0u); // a2 + setRegU32(ctx, 7, 3u); // a3 + setRegU32(ctx, 8, 1u); // t0 + + ps2_stubs::sprintf(rdram.data(), &ctx, &runtime); + + const std::string rendered(reinterpret_cast(rdram.data() + kDestAddr)); + t.Equals(rendered, std::string("rm_0031.rdx"), + "sprintf should read the third variadic integer from t0 and honor %02d"); + t.Equals(getRegS32(ctx, 2), static_cast(rendered.size()), + "sprintf should return the rendered length"); + }); + tc.Run("multiply-add matrix writes rd only when R5900 requires it", [](TestCase &t) { R5900Decoder decoder; @@ -950,5 +1121,153 @@ void register_ps2_runtime_expansion_tests() runtime.requestStop(); notifyRuntimeStop(); }); + + tc.Run("sceVu0ApplyMatrix uses libvux matrix math with the imported EE ABI", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + + constexpr uint32_t kOutAddr = 0x00100000u; + constexpr uint32_t kMatrixAddr = 0x00100040u; + constexpr uint32_t kSrcAddr = 0x00100080u; + + const float matrix[16] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f, + }; + const float src[4] = {1.0f, 2.0f, 3.0f, 1.0f}; + std::memcpy(rdram.data() + kMatrixAddr, matrix, sizeof(matrix)); + std::memcpy(rdram.data() + kSrcAddr, src, sizeof(src)); + + setRegU32(ctx, 4, kOutAddr); + setRegU32(ctx, 5, kMatrixAddr); + setRegU32(ctx, 6, kSrcAddr); + + ps2_stubs::sceVu0ApplyMatrix(rdram.data(), &ctx, nullptr); + + float out[4]{}; + std::memcpy(out, rdram.data() + kOutAddr, sizeof(out)); + t.Equals(out[0], 51.0f, "sceVu0ApplyMatrix should compute X with libvux layout"); + t.Equals(out[1], 58.0f, "sceVu0ApplyMatrix should compute Y with libvux layout"); + t.Equals(out[2], 65.0f, "sceVu0ApplyMatrix should compute Z with libvux layout"); + t.Equals(out[3], 72.0f, "sceVu0ApplyMatrix should compute W with libvux layout"); + t.Equals(getRegS32(ctx, 2), 0, "sceVu0ApplyMatrix should report success"); + }); + + tc.Run("sceVu0TransposeMatrix transposes a 4x4 matrix with dst/src ABI", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + + constexpr uint32_t kDstAddr = 0x00100100u; + constexpr uint32_t kSrcAddr = 0x00100140u; + const float src[16] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f, + }; + std::memcpy(rdram.data() + kSrcAddr, src, sizeof(src)); + + setRegU32(ctx, 4, kDstAddr); + setRegU32(ctx, 5, kSrcAddr); + + ps2_stubs::sceVu0TransposeMatrix(rdram.data(), &ctx, nullptr); + + float out[16]{}; + std::memcpy(out, rdram.data() + kDstAddr, sizeof(out)); + t.Equals(out[0], 1.0f, "transpose should preserve [0][0]"); + t.Equals(out[1], 5.0f, "transpose should swap row 0 col 1"); + t.Equals(out[2], 9.0f, "transpose should swap row 0 col 2"); + t.Equals(out[3], 13.0f, "transpose should swap row 0 col 3"); + t.Equals(out[4], 2.0f, "transpose should swap row 1 col 0"); + t.Equals(out[5], 6.0f, "transpose should preserve [1][1]"); + t.Equals(out[10], 11.0f, "transpose should preserve [2][2]"); + t.Equals(out[12], 4.0f, "transpose should swap row 3 col 0"); + t.Equals(out[15], 16.0f, "transpose should preserve [3][3]"); + t.Equals(getRegS32(ctx, 2), 0, "sceVu0TransposeMatrix should report success"); + }); + + tc.Run("sceVif1PkReset preserves the packet base pointer and clears open tag state", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + + constexpr uint32_t kStateAddr = 0x00100200u; + constexpr uint32_t kBaseAddr = 0x00101000u; + + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, kBaseAddr); + ps2_stubs::sceVif1PkInit(rdram.data(), &ctx, nullptr); + + const uint32_t dirtyCurrent = kBaseAddr + 0x40u; + const uint32_t dirtyPending = 0x12345678u; + const uint32_t dirtyDirectOpen = 0x00ABCDEFu; + const uint32_t dirtyGifOpen = 0x00112233u; + std::memcpy(rdram.data() + kStateAddr + 0u, &dirtyCurrent, sizeof(dirtyCurrent)); + std::memcpy(rdram.data() + kStateAddr + 8u, &dirtyPending, sizeof(dirtyPending)); + std::memcpy(rdram.data() + kStateAddr + 12u, &dirtyDirectOpen, sizeof(dirtyDirectOpen)); + std::memcpy(rdram.data() + kStateAddr + 20u, &dirtyGifOpen, sizeof(dirtyGifOpen)); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + ps2_stubs::sceVif1PkReset(rdram.data(), &ctx, nullptr); + + uint32_t current = 0u; + uint32_t base = 0u; + uint32_t pending = 0u; + uint32_t directOpen = 0u; + uint32_t gifOpen = 0u; + std::memcpy(¤t, rdram.data() + kStateAddr + 0u, sizeof(current)); + std::memcpy(&base, rdram.data() + kStateAddr + 4u, sizeof(base)); + std::memcpy(&pending, rdram.data() + kStateAddr + 8u, sizeof(pending)); + std::memcpy(&directOpen, rdram.data() + kStateAddr + 12u, sizeof(directOpen)); + std::memcpy(&gifOpen, rdram.data() + kStateAddr + 20u, sizeof(gifOpen)); + + t.Equals(current, kBaseAddr, "sceVif1PkReset should restore current pointer to the packet base"); + t.Equals(base, kBaseAddr, "sceVif1PkReset should preserve the packet base pointer"); + t.Equals(pending, 0u, "sceVif1PkReset should clear pending count tracking"); + t.Equals(directOpen, 0u, "sceVif1PkReset should clear direct-code open state"); + t.Equals(gifOpen, 0u, "sceVif1PkReset should clear GIF-tag open state"); + t.Equals(::getRegU32(&ctx, 2), kBaseAddr, "sceVif1PkReset should return the packet base pointer"); + }); + + tc.Run("sceVif1PkCloseDirectCode encodes DIRECT length in qwords", [](TestCase &t) + { + std::vector rdram(PS2_RAM_SIZE, 0u); + R5900Context ctx{}; + + constexpr uint32_t kStateAddr = 0x00100400u; + constexpr uint32_t kBaseAddr = 0x00102000u; + + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, kBaseAddr); + ps2_stubs::sceVif1PkInit(rdram.data(), &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, 0u); + ps2_stubs::sceVif1PkCnt(rdram.data(), &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, 0u); + ps2_stubs::sceVif1PkOpenDirectCode(rdram.data(), &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + setRegU32(ctx, 5, 4u); // reserve one qword worth of GIF payload + ps2_stubs::sceVif1PkReserve(rdram.data(), &ctx, nullptr); + + std::memset(&ctx, 0, sizeof(ctx)); + setRegU32(ctx, 4, kStateAddr); + ps2_stubs::sceVif1PkCloseDirectCode(rdram.data(), &ctx, nullptr); + + uint32_t directCmd = 0u; + std::memcpy(&directCmd, rdram.data() + kBaseAddr + 12u, sizeof(directCmd)); + t.Equals(directCmd, 0x50000001u, "sceVif1PkCloseDirectCode should store a 1-QW DIRECT length"); + }); }); } diff --git a/ps2xTest/src/ps2_runtime_io_tests.cpp b/ps2xTest/src/ps2_runtime_io_tests.cpp index ecadc544..e57b8f6f 100644 --- a/ps2xTest/src/ps2_runtime_io_tests.cpp +++ b/ps2xTest/src/ps2_runtime_io_tests.cpp @@ -16,11 +16,40 @@ namespace // Guest memory address ranges for test data constexpr uint32_t GUEST_STRING_AREA_START = 0x1000; constexpr uint32_t GUEST_BUFFER_AREA_START = 0x2000; + constexpr uint32_t GUEST_STACK_AREA_START = 0x6000; + constexpr uint32_t GUEST_MC_SYNC_CMD_ADDR = GUEST_BUFFER_AREA_START + 0x1C00; + constexpr uint32_t GUEST_MC_SYNC_RESULT_ADDR = GUEST_BUFFER_AREA_START + 0x1C04; + constexpr uint32_t GUEST_MC_TABLE_ADDR = GUEST_BUFFER_AREA_START + 0x2000; // Common file I/O flag combinations constexpr uint32_t PS2_FIO_WRITE_CREATE_TRUNC = PS2_FIO_O_WRONLY | PS2_FIO_O_CREAT | PS2_FIO_O_TRUNC; + struct SceMcStDateTime + { + uint8_t resv2; + uint8_t sec; + uint8_t min; + uint8_t hour; + uint8_t day; + uint8_t month; + uint16_t year; + }; + + struct SceMcTblGetDir + { + SceMcStDateTime create; + SceMcStDateTime modify; + uint32_t fileSizeByte; + uint16_t attrFile; + uint16_t reserve1; + uint32_t reserve2; + uint32_t pdaAplNo; + char entryName[32]; + }; + + static_assert(sizeof(SceMcTblGetDir) == 64, "sceMcTblGetDir size mismatch"); + void setRegU32(R5900Context &ctx, int reg, uint32_t value) { ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); @@ -36,6 +65,51 @@ namespace std::memcpy(rdram + addr, value.c_str(), value.size() + 1); } + void writeGuestU32(uint8_t *rdram, uint32_t addr, uint32_t value) + { + std::memcpy(rdram + addr, &value, sizeof(value)); + } + + int32_t readGuestS32(const uint8_t *rdram, uint32_t addr) + { + int32_t value = 0; + std::memcpy(&value, rdram + addr, sizeof(value)); + return value; + } + + uint32_t readGuestU32(const uint8_t *rdram, uint32_t addr) + { + uint32_t value = 0; + std::memcpy(&value, rdram + addr, sizeof(value)); + return value; + } + + void clearContext(R5900Context &ctx) + { + std::memset(&ctx, 0, sizeof(ctx)); + } + + void writeStackArg(std::vector &rdram, R5900Context &ctx, uint32_t slotIndex, uint32_t value) + { + const uint32_t sp = ::getRegU32(&ctx, 29); + writeGuestU32(rdram.data(), sp + 16u + slotIndex * sizeof(uint32_t), value); + } + + int32_t syncMc(std::vector &rdram, int32_t *cmdOut = nullptr) + { + R5900Context syncCtx{}; + setRegU32(syncCtx, 4, 0u); + setRegU32(syncCtx, 5, GUEST_MC_SYNC_CMD_ADDR); + setRegU32(syncCtx, 6, GUEST_MC_SYNC_RESULT_ADDR); + ps2_stubs::sceMcSync(rdram.data(), &syncCtx, nullptr); + + if (cmdOut) + { + *cmdOut = readGuestS32(rdram.data(), GUEST_MC_SYNC_CMD_ADDR); + } + return readGuestS32(rdram.data(), GUEST_MC_SYNC_RESULT_ADDR); + } + struct TempPaths { std::filesystem::path base; @@ -276,6 +350,193 @@ void register_ps2_runtime_io_tests() "mc0: directory should NOT exist under cdRoot"); }); + tc.Run("sceMc open write read and close roundtrip through sync", [](TestCase &t) + { + TestContext test; + + const uint32_t dirAddr = GUEST_STRING_AREA_START + 0x400; + const uint32_t fileAddr = GUEST_STRING_AREA_START + 0x500; + const uint32_t writeBufAddr = GUEST_BUFFER_AREA_START + 0x300; + const uint32_t readBufAddr = GUEST_BUFFER_AREA_START + 0x500; + const std::string payload = "libmc roundtrip"; + + writeGuestString(test.rdram.data(), dirAddr, "/SAVEDATA"); + writeGuestString(test.rdram.data(), fileAddr, "/SAVEDATA/test.bin"); + std::memcpy(test.rdram.data() + writeBufAddr, payload.data(), payload.size()); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, dirAddr); + ps2_stubs::sceMcMkdir(test.rdram.data(), &test.ctx, nullptr); + t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcMkdir should dispatch successfully"); + + int32_t cmd = 0; + t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcMkdir should finish successfully"); + t.Equals(cmd, 0x0B, "sceMcSync should report MKDIR as the last command"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, fileAddr); + setRegU32(test.ctx, 7, PS2_FIO_O_RDWR | PS2_FIO_O_CREAT | PS2_FIO_O_TRUNC); + ps2_stubs::sceMcOpen(test.rdram.data(), &test.ctx, nullptr); + t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcOpen should dispatch successfully"); + + const int32_t fd = syncMc(test.rdram, &cmd); + t.IsTrue(fd > 0, "sceMcOpen should produce a positive descriptor in sceMcSync"); + t.Equals(cmd, 0x02, "sceMcSync should report OPEN as the last command"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, static_cast(fd)); + setRegU32(test.ctx, 5, writeBufAddr); + setRegU32(test.ctx, 6, static_cast(payload.size())); + ps2_stubs::sceMcWrite(test.rdram.data(), &test.ctx, nullptr); + t.Equals(syncMc(test.rdram, &cmd), static_cast(payload.size()), + "sceMcWrite should report the full byte count via sceMcSync"); + t.Equals(cmd, 0x06, "sceMcSync should report WRITE as the last command"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, static_cast(fd)); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, PS2_FIO_SEEK_SET); + ps2_stubs::sceMcSeek(test.rdram.data(), &test.ctx, nullptr); + t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcSeek should rewind to offset zero"); + t.Equals(cmd, 0x04, "sceMcSync should report SEEK as the last command"); + + std::memset(test.rdram.data() + readBufAddr, 0, payload.size()); + clearContext(test.ctx); + setRegU32(test.ctx, 4, static_cast(fd)); + setRegU32(test.ctx, 5, readBufAddr); + setRegU32(test.ctx, 6, static_cast(payload.size())); + ps2_stubs::sceMcRead(test.rdram.data(), &test.ctx, nullptr); + t.Equals(syncMc(test.rdram, &cmd), static_cast(payload.size()), + "sceMcRead should report the full byte count via sceMcSync"); + t.Equals(cmd, 0x05, "sceMcSync should report READ as the last command"); + + std::string readback(reinterpret_cast(test.rdram.data() + readBufAddr), payload.size()); + t.Equals(readback, payload, "sceMcRead should fill the guest buffer with the written payload"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, static_cast(fd)); + ps2_stubs::sceMcClose(test.rdram.data(), &test.ctx, nullptr); + t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcClose should finish successfully"); + t.Equals(cmd, 0x03, "sceMcSync should report CLOSE as the last command"); + + const std::filesystem::path hostPath = test.paths.mcRoot / "SAVEDATA" / "test.bin"; + t.IsTrue(std::filesystem::exists(hostPath), "sceMcOpen/sceMcWrite should create the host file under mcRoot"); + }); + + tc.Run("sceMcGetDir includes dot entries and file metadata", [](TestCase &t) + { + TestContext test; + + std::filesystem::create_directories(test.paths.mcRoot / "SAVEDATA"); + const std::string hostPayload = "abc123"; + { + std::ofstream out(test.paths.mcRoot / "SAVEDATA" / "game.dat", std::ios::binary); + out.write(hostPayload.data(), static_cast(hostPayload.size())); + } + + const uint32_t patternAddr = GUEST_STRING_AREA_START + 0x700; + writeGuestString(test.rdram.data(), patternAddr, "/SAVEDATA/*"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, patternAddr); + setRegU32(test.ctx, 7, 0u); + setRegU32(test.ctx, 29, GUEST_STACK_AREA_START); + writeStackArg(test.rdram, test.ctx, 0u, 8u); + writeStackArg(test.rdram, test.ctx, 1u, GUEST_MC_TABLE_ADDR); + + ps2_stubs::sceMcGetDir(test.rdram.data(), &test.ctx, nullptr); + + int32_t cmd = 0; + const int32_t entryCount = syncMc(test.rdram, &cmd); + t.Equals(cmd, 0x0D, "sceMcSync should report GETDIR as the last command"); + t.Equals(entryCount, 3, "sceMcGetDir should return '.', '..', and the matching file"); + + const auto *entries = reinterpret_cast(test.rdram.data() + GUEST_MC_TABLE_ADDR); + t.Equals(std::string(entries[0].entryName), std::string("."), "sceMcGetDir should return '.' first"); + t.Equals(std::string(entries[1].entryName), std::string(".."), "sceMcGetDir should return '..' second"); + t.Equals(std::string(entries[2].entryName), std::string("game.dat"), "sceMcGetDir should include the matching file entry"); + t.Equals(entries[2].fileSizeByte, static_cast(hostPayload.size()), + "sceMcGetDir should report the host file size"); + t.IsTrue((entries[2].attrFile & 0x0080u) != 0u, + "sceMcGetDir file entries should carry the closed-file attribute"); + }); + + tc.Run("sceMcGetInfo reports formatted and unformatted states", [](TestCase &t) + { + TestContext test; + + constexpr uint32_t typeAddr = GUEST_BUFFER_AREA_START + 0x900; + constexpr uint32_t freeAddr = GUEST_BUFFER_AREA_START + 0x904; + constexpr uint32_t formatAddr = GUEST_BUFFER_AREA_START + 0x908; + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, typeAddr); + setRegU32(test.ctx, 7, freeAddr); + setRegU32(test.ctx, 29, GUEST_STACK_AREA_START); + writeStackArg(test.rdram, test.ctx, 0u, formatAddr); + ps2_stubs::sceMcGetInfo(test.rdram.data(), &test.ctx, nullptr); + + int32_t cmd = 0; + t.Equals(syncMc(test.rdram, &cmd), 0, "formatted cards should report success through sceMcSync"); + t.Equals(cmd, 0x01, "sceMcSync should report GETINFO as the last command"); + t.Equals(readGuestS32(test.rdram.data(), typeAddr), 2, "sceMcGetInfo should report a PS2 memory card"); + t.Equals(readGuestS32(test.rdram.data(), formatAddr), 1, "sceMcGetInfo should report a formatted card"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + ps2_stubs::sceMcUnformat(test.rdram.data(), &test.ctx, nullptr); + t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcUnformat should complete successfully"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, typeAddr); + setRegU32(test.ctx, 7, freeAddr); + setRegU32(test.ctx, 29, GUEST_STACK_AREA_START); + writeStackArg(test.rdram, test.ctx, 0u, formatAddr); + ps2_stubs::sceMcGetInfo(test.rdram.data(), &test.ctx, nullptr); + + t.Equals(syncMc(test.rdram, &cmd), -2, "unformatted cards should report sceMcResNoFormat through sceMcSync"); + t.Equals(readGuestS32(test.rdram.data(), formatAddr), 0, "sceMcGetInfo should report an unformatted card after sceMcUnformat"); + }); + + tc.Run("sceMcEnd resets libmc state so sync reports no active command", [](TestCase &t) + { + TestContext test; + + constexpr uint32_t dirAddr = GUEST_STRING_AREA_START + 0xB00; + + clearContext(test.ctx); + ps2_stubs::sceMcInit(test.rdram.data(), &test.ctx, nullptr); + t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcInit should succeed"); + + writeGuestString(test.rdram.data(), dirAddr, "/SAVEDATA"); + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 0u); + setRegU32(test.ctx, 6, dirAddr); + ps2_stubs::sceMcMkdir(test.rdram.data(), &test.ctx, nullptr); + int32_t cmd = 0; + t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcMkdir should complete before teardown"); + t.Equals(cmd, 0x0B, "sceMcSync should report MKDIR before teardown"); + + clearContext(test.ctx); + ps2_stubs::sceMcEnd(test.rdram.data(), &test.ctx, nullptr); + t.Equals(getRegS32(&test.ctx, 2), 0, "sceMcEnd should succeed"); + + t.Equals(syncMc(test.rdram, &cmd), 0, "sceMcSync should report cleared result after sceMcEnd"); + t.Equals(cmd, 0, "sceMcEnd should clear the last active libmc command"); + }); + tc.Run("sceIoctl cmd1 updates wait flag state", [](TestCase &t) { TestContext test; @@ -296,5 +557,68 @@ void register_ps2_runtime_io_tests() std::memcpy(&state, test.rdram.data() + statusAddr, sizeof(state)); t.Equals(state, 0u, "sceIoctl cmd1 should clear wait state from busy to ready"); }); + + tc.Run("sceCdSearchFile resolves movie filenames with zero-padded host leaf", [](TestCase &t) + { + TestContext test; + + std::filesystem::create_directories(test.paths.cdRoot / "movie"); + { + std::ofstream out(test.paths.cdRoot / "movie" / "mv_016.pss", std::ios::binary); + const std::string payload = "pss"; + out.write(payload.data(), static_cast(payload.size())); + } + + constexpr uint32_t fileAddr = GUEST_BUFFER_AREA_START + 0x1A00; + constexpr uint32_t pathAddr = GUEST_STRING_AREA_START + 0xA00; + writeGuestString(test.rdram.data(), pathAddr, "\\MOVIE\\MV_16.PSS;1"); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, fileAddr); + setRegU32(test.ctx, 5, pathAddr); + ps2_stubs::sceCdSearchFile(test.rdram.data(), &test.ctx, nullptr); + + t.Equals(getRegS32(&test.ctx, 2), 1, "sceCdSearchFile should resolve the extracted movie file"); + t.Equals(readGuestU32(test.rdram.data(), fileAddr + 4), 3u, + "sceCdSearchFile should report the host file size"); + t.IsTrue(readGuestU32(test.rdram.data(), fileAddr + 0) >= 0x00100000u, + "sceCdSearchFile should assign a pseudo LSN for the resolved host file"); + }); + + tc.Run("sceCdRead reads from explicit cdImage path", [](TestCase &t) + { + TestContext test; + + constexpr uint32_t kSectorSize = 2048u; + constexpr uint32_t bufAddr = GUEST_BUFFER_AREA_START + 0x1C80; + const std::filesystem::path imagePath = test.paths.base / "disc.iso"; + { + std::vector sector(kSectorSize, 0); + const char payload[] = "cd-image"; + std::memcpy(sector.data(), payload, sizeof(payload) - 1); + + std::ofstream out(imagePath, std::ios::binary); + out.write(reinterpret_cast(sector.data()), + static_cast(sector.size())); + } + + PS2Runtime::IoPaths ioPaths; + ioPaths.elfDirectory = test.paths.cdRoot; + ioPaths.hostRoot = test.paths.cdRoot; + ioPaths.cdRoot = test.paths.cdRoot; + ioPaths.mcRoot = test.paths.mcRoot; + ioPaths.cdImage = imagePath; + PS2Runtime::setIoPaths(ioPaths); + + clearContext(test.ctx); + setRegU32(test.ctx, 4, 0u); + setRegU32(test.ctx, 5, 1u); + setRegU32(test.ctx, 6, bufAddr); + ps2_stubs::sceCdRead(test.rdram.data(), &test.ctx, nullptr); + + t.Equals(getRegS32(&test.ctx, 2), 1, "sceCdRead should succeed when cdImage is configured"); + t.Equals(std::memcmp(test.rdram.data() + bufAddr, "cd-image", 8), 0, + "sceCdRead should copy sector data from the configured image"); + }); }); } diff --git a/ps2xTest/src/ps2_runtime_kernel_tests.cpp b/ps2xTest/src/ps2_runtime_kernel_tests.cpp index a3d13c50..7dbe6725 100644 --- a/ps2xTest/src/ps2_runtime_kernel_tests.cpp +++ b/ps2xTest/src/ps2_runtime_kernel_tests.cpp @@ -3,9 +3,11 @@ #include "ps2_runtime_macros.h" #include "ps2_syscalls.h" +#include #include #include #include +#include #include using namespace ps2_syscalls; @@ -24,7 +26,13 @@ namespace constexpr int KE_SEMA_ZERO = -419; constexpr int KE_SEMA_OVF = -420; + constexpr int THS_SUSPEND = 0x08; + constexpr int THS_WAITSUSPEND = 0x0C; constexpr int THS_DORMANT = 0x10; + constexpr uint32_t TSW_EVENT = 3u; + + constexpr uint32_t K_EVENT_WAIT_READY_ADDR = 0x1800u; + constexpr uint32_t K_EVENT_WAIT_GATE_ADDR = 0x1804u; struct EeThreadStatus { @@ -78,6 +86,28 @@ namespace } } + uint32_t readGuestU32(const uint8_t *rdram, uint32_t addr) + { + uint32_t value = 0; + std::memcpy(&value, rdram + addr, sizeof(value)); + return value; + } + + template + bool waitUntil(Predicate pred, std::chrono::milliseconds timeout) + { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) + { + if (pred()) + { + return true; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return pred(); + } + bool callSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { return dispatchNumericSyscall(syscallNumber, rdram, ctx, runtime); @@ -138,6 +168,38 @@ namespace ctx->pc = ::getRegU32(ctx, 31); } + void waitEventAfterSuspendHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + if (!rdram || !ctx) + { + return; + } + + writeGuestU32(rdram, K_EVENT_WAIT_READY_ADDR, 1u); + while (readGuestU32(rdram, K_EVENT_WAIT_GATE_ADDR) == 0u) + { + if (runtime && runtime->isStopRequested()) + { + ctx->pc = 0u; + return; + } + std::this_thread::yield(); + } + + const uint32_t eid = ::getRegU32(ctx, 4); + setRegU32(*ctx, 4, eid); + setRegU32(*ctx, 5, 0x4u); + setRegU32(*ctx, 6, 1u); + setRegU32(*ctx, 7, 0u); + WaitEventFlag(rdram, ctx, runtime); + ctx->pc = 0u; + } + + void alarmNoopHandler(uint8_t *, R5900Context *ctx, PS2Runtime *) + { + ctx->pc = 0u; + } + struct TestEnv { std::vector rdram; @@ -364,6 +426,138 @@ void register_ps2_runtime_kernel_tests() t.Equals(getRegS32(env.ctx, 2), KE_OK, "DeleteSema should clean up legacy-decoded semaphore"); }); + tc.Run("WaitEventFlag preserves waitsuspend state when a suspended thread blocks", [](TestCase &t) + { + TestEnv env; + + constexpr uint32_t kEventParamAddr = 0x1600u; + constexpr uint32_t kWaitThreadEntry = 0x00260000u; + + const uint32_t eventParam[3] = { + 0u, + 0u, + 0u + }; + std::memcpy(env.rdram.data() + kEventParamAddr, eventParam, sizeof(eventParam)); + writeGuestU32(env.rdram.data(), K_EVENT_WAIT_READY_ADDR, 0u); + writeGuestU32(env.rdram.data(), K_EVENT_WAIT_GATE_ADDR, 0u); + + R5900Context createEventCtx{}; + setRegU32(createEventCtx, 4, kEventParamAddr); + CreateEventFlag(env.rdram.data(), &createEventCtx, &env.runtime); + const int32_t eid = getRegS32(createEventCtx, 2); + t.IsTrue(eid > 0, "CreateEventFlag should return a valid event id"); + + env.runtime.registerFunction(kWaitThreadEntry, &waitEventAfterSuspendHandler); + + const uint32_t threadParam[7] = { + 0u, + kWaitThreadEntry, + 0x00310000u, + 0x00000800u, + 0x00120000u, + 6u, + 0u + }; + + writeGuestWords(env.rdram.data(), K_PARAM_ADDR, threadParam, std::size(threadParam)); + setRegU32(env.ctx, 4, K_PARAM_ADDR); + CreateThread(env.rdram.data(), &env.ctx, &env.runtime); + const int32_t tid = getRegS32(env.ctx, 2); + t.IsTrue(tid >= 2, "CreateThread should return a valid worker thread id"); + + setRegU32(env.ctx, 4, static_cast(tid)); + setRegU32(env.ctx, 5, static_cast(eid)); + StartThread(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "StartThread should launch the event waiter"); + + const bool ready = waitUntil([&]() + { + return readGuestU32(env.rdram.data(), K_EVENT_WAIT_READY_ADDR) == 1u; + }, std::chrono::milliseconds(200)); + t.IsTrue(ready, "waiter thread should reach the suspend gate before blocking"); + + setRegU32(env.ctx, 4, static_cast(tid)); + SuspendThread(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SuspendThread should succeed for the running waiter"); + + writeGuestU32(env.rdram.data(), K_EVENT_WAIT_GATE_ADDR, 1u); + + const bool waiting = waitUntil([&]() + { + R5900Context statusCtx{}; + setRegU32(statusCtx, 4, static_cast(tid)); + setRegU32(statusCtx, 5, K_STATUS_ADDR); + ReferThreadStatus(env.rdram.data(), &statusCtx, &env.runtime); + if (getRegS32(statusCtx, 2) != KE_OK) + { + return false; + } + + EeThreadStatus status{}; + std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status)); + return status.waitType == TSW_EVENT; + }, std::chrono::milliseconds(200)); + t.IsTrue(waiting, "waiter thread should block on the event flag"); + + EeThreadStatus waitingStatus{}; + std::memcpy(&waitingStatus, env.rdram.data() + K_STATUS_ADDR, sizeof(waitingStatus)); + t.Equals(waitingStatus.status, THS_WAITSUSPEND, + "event-flag wait should report THS_WAITSUSPEND when the thread is already suspended"); + + R5900Context signalCtx{}; + setRegU32(signalCtx, 4, static_cast(eid)); + setRegU32(signalCtx, 5, 0x4u); + SetEventFlag(env.rdram.data(), &signalCtx, &env.runtime); + t.Equals(getRegS32(signalCtx, 2), KE_OK, "SetEventFlag should wake the waiting thread"); + + const bool suspended = waitUntil([&]() + { + R5900Context statusCtx{}; + setRegU32(statusCtx, 4, static_cast(tid)); + setRegU32(statusCtx, 5, K_STATUS_ADDR); + ReferThreadStatus(env.rdram.data(), &statusCtx, &env.runtime); + if (getRegS32(statusCtx, 2) != KE_OK) + { + return false; + } + + EeThreadStatus status{}; + std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status)); + return status.status == THS_SUSPEND && status.waitType == 0u; + }, std::chrono::milliseconds(200)); + t.IsTrue(suspended, "after wake, a still-suspended waiter should move to THS_SUSPEND"); + + setRegU32(env.ctx, 4, static_cast(tid)); + ResumeThread(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "ResumeThread should release the waiter after the event is set"); + + const bool dormant = waitUntil([&]() + { + R5900Context statusCtx{}; + setRegU32(statusCtx, 4, static_cast(tid)); + setRegU32(statusCtx, 5, K_STATUS_ADDR); + ReferThreadStatus(env.rdram.data(), &statusCtx, &env.runtime); + if (getRegS32(statusCtx, 2) != KE_OK) + { + return false; + } + + EeThreadStatus status{}; + std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status)); + return status.status == THS_DORMANT; + }, std::chrono::milliseconds(200)); + t.IsTrue(dormant, "waiter thread should return to dormant after the event is signaled and resumed"); + + setRegU32(env.ctx, 4, static_cast(eid)); + DeleteEventFlag(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "DeleteEventFlag should clean up the test event flag"); + + setRegU32(env.ctx, 4, static_cast(tid)); + DeleteThread(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "DeleteThread should clean up the waiter thread"); + }); + tc.Run("setup heap and allocator primitives track end-of-heap", [](TestCase &t) { TestEnv env; @@ -401,6 +595,36 @@ void register_ps2_runtime_kernel_tests() t.Equals(reused, heapBase, "guestFree should make the head block reusable"); }); + tc.Run("ReleaseAlarm aliases CancelAlarm and cache toggles succeed", [](TestCase &t) + { + TestEnv env; + + constexpr uint32_t kAlarmHandlerAddr = 0x00270000u; + env.runtime.registerFunction(kAlarmHandlerAddr, &alarmNoopHandler); + + setRegU32(env.ctx, 4, 0xFFFFu); + setRegU32(env.ctx, 5, kAlarmHandlerAddr); + setRegU32(env.ctx, 6, 0u); + SetAlarm(env.rdram.data(), &env.ctx, &env.runtime); + const int32_t alarmId = getRegS32(env.ctx, 2); + t.IsTrue(alarmId > 0, "SetAlarm should create a cancellable alarm"); + + setRegU32(env.ctx, 4, static_cast(alarmId)); + ReleaseAlarm(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "ReleaseAlarm should cancel active alarms"); + + setRegU32(env.ctx, 4, static_cast(alarmId)); + CancelAlarm(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_ERROR, + "CancelAlarm should report missing alarms after ReleaseAlarm consumes them"); + + EnableCache(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "EnableCache should succeed as a no-op"); + + DisableCache(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "DisableCache should succeed as a no-op"); + }); + tc.Run("setup heap and thread invalid ids use documented kernel errors", [](TestCase &t) { TestEnv env; diff --git a/ps2xTest/src/ps2_sif_dma_tests.cpp b/ps2xTest/src/ps2_sif_dma_tests.cpp index a1735904..7a573b7b 100644 --- a/ps2xTest/src/ps2_sif_dma_tests.cpp +++ b/ps2xTest/src/ps2_sif_dma_tests.cpp @@ -15,6 +15,8 @@ namespace ps2_stubs namespace { + constexpr int KE_OK = 0; + struct TestEnv { std::vector rdram; @@ -24,10 +26,26 @@ namespace TestEnv() : rdram(PS2_RAM_SIZE, 0u) { ps2_stubs::resetSifState(); + ps2_syscalls::resetSoundDriverRpcState(); + ps2_syscalls::clearSoundDriverCompatLayout(); + ps2_syscalls::clearDtxCompatLayout(); std::memset(&ctx, 0, sizeof(ctx)); } }; + void setRecvxDtxCompatLayout() + { + PS2DtxCompatLayout layout{}; + layout.rpcSid = 0x7D000000u; + layout.urpcObjBase = 0x01F18000u; + layout.urpcObjLimit = 0x01F1FF00u; + layout.urpcObjStride = 0x20u; + layout.urpcFnTableBase = 0x0034FED0u; + layout.urpcObjTableBase = 0x0034FFD0u; + layout.dispatcherFuncAddr = 0x002FABC0u; + ps2_syscalls::setDtxCompatLayout(layout); + } + #pragma pack(push, 1) struct Ps2SifDmaTransfer { @@ -79,6 +97,18 @@ namespace return value; } + void writeGuestS16(uint8_t *rdram, uint32_t addr, int16_t value) + { + std::memcpy(rdram + addr, &value, sizeof(value)); + } + + int16_t readGuestS16(const uint8_t *rdram, uint32_t addr) + { + int16_t value = 0; + std::memcpy(&value, rdram + addr, sizeof(value)); + return value; + } + uint32_t g_dmacHandlerWriteAddr = 0u; uint32_t g_dmacHandlerValue = 0u; uint32_t g_dmacHandlerLastCause = 0u; @@ -138,6 +168,40 @@ void register_ps2_sif_dma_tests() t.IsTrue(getRegS32(env.ctx, 2) < 0, "sceSifDmaStat should be negative when transfer is complete"); }); + tc.Run("isceSifSetDma and isceSifSetDChain alias the SIF DMA helpers", [](TestCase &t) + { + TestEnv env; + + constexpr uint32_t kDescAddr = 0x00020240u; + constexpr uint32_t kSrcAddr = 0x00020340u; + constexpr uint32_t kDstAddr = 0x00020440u; + + std::array payload{}; + for (size_t i = 0; i < payload.size(); ++i) + { + payload[i] = static_cast(0x50u + i); + } + std::memcpy(env.rdram.data() + kSrcAddr, payload.data(), payload.size()); + std::memset(env.rdram.data() + kDstAddr, 0, payload.size()); + + const Ps2SifDmaTransfer desc{ + kSrcAddr, + kDstAddr, + static_cast(payload.size()), + 0}; + std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc)); + + setRegU32(env.ctx, 4, kDescAddr); + setRegU32(env.ctx, 5, 1u); + ps2_stubs::isceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); + t.IsTrue(getRegS32(env.ctx, 2) > 0, "isceSifSetDma should report a successful transfer id"); + t.IsTrue(std::memcmp(env.rdram.data() + kDstAddr, payload.data(), payload.size()) == 0, + "isceSifSetDma should copy transfer payload like sceSifSetDma"); + + ps2_stubs::isceSifSetDChain(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), 0, "isceSifSetDChain should mirror sceSifSetDChain"); + }); + tc.Run("sceSifSetDma dispatches enabled DMAC handlers for cause 5", [](TestCase &t) { TestEnv env; @@ -192,6 +256,377 @@ void register_ps2_sif_dma_tests() t.Equals(g_dmacHandlerLastArg, kHandlerArg, "DMAC handler should receive registered argument"); }); + tc.Run("sceSifSetDma acknowledges DTX work-buffer transfers by advancing the EE footer ticket", [](TestCase &t) + { + TestEnv env; + setRecvxDtxCompatLayout(); + + constexpr uint32_t kClientAddr = 0x0002D000u; + constexpr uint32_t kDtxSid = 0x7D000000u; + constexpr uint32_t kSendAddr = 0x0002D100u; + constexpr uint32_t kRecvAddr = 0x0002D200u; + constexpr uint32_t kDescAddr = 0x0002D300u; + constexpr uint32_t kEeWorkAddr = 0x0002D400u; + constexpr uint32_t kIopWorkAddr = 0x0002D800u; + constexpr uint32_t kDtxId = 3u; + constexpr uint32_t kWorkLen = 0x100u; + constexpr uint32_t kFooterTicketAddr = kEeWorkAddr + kWorkLen - sizeof(uint32_t); + + ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, kDtxSid); + setRegU32(env.ctx, 6, 0u); + ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for the DTX sid"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, kDtxId); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWorkAddr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWorkAddr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); + writeGuestU32(env.rdram.data(), kRecvAddr + 0x00u, 0u); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 2u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc should create the DTX transport"); + t.IsTrue(readGuestU32(env.rdram.data(), kRecvAddr) != 0u, "DTX create should return a remote handle"); + + std::memset(env.rdram.data() + kEeWorkAddr, 0x44, kWorkLen); + std::memset(env.rdram.data() + kIopWorkAddr, 0x00, kWorkLen); + writeGuestU32(env.rdram.data(), kFooterTicketAddr, 1u); + + const Ps2SifDmaTransfer desc{ + kEeWorkAddr, + kIopWorkAddr, + static_cast(kWorkLen), + 0}; + std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc)); + + setRegU32(env.ctx, 4, kDescAddr); + setRegU32(env.ctx, 5, 1u); + ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); + t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the DTX transfer"); + + t.Equals(readGuestU32(env.rdram.data(), kFooterTicketAddr), 2u, + "sceSifSetDma should advance the EE footer ticket so DTX clears wait_flag"); + }); + + tc.Run("sceSifSetDma applies SJX DTX payloads into the emulated SJRMT data ring", [](TestCase &t) + { + TestEnv env; + setRecvxDtxCompatLayout(); + + constexpr uint32_t kClientAddr = 0x0002E000u; + constexpr uint32_t kDtxSid = 0x7D000000u; + constexpr uint32_t kRecvAddr = 0x0002E100u; + constexpr uint32_t kSendAddr = 0x0002E200u; + constexpr uint32_t kDescAddr = 0x0002E300u; + constexpr uint32_t kEeWorkAddr = 0x0002E400u; + constexpr uint32_t kIopWorkAddr = 0x0002E800u; + constexpr uint32_t kRingAddr = 0x0002EC00u; + constexpr uint32_t kChunkDataAddr = 0x0002ED00u; + constexpr uint32_t kWorkLen = 0x100u; + constexpr uint32_t kChunkLen = 8u; + + ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, kDtxSid); + setRegU32(env.ctx, 6, 0u); + ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should bind the DTX sid"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kRingAddr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kWorkLen); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x422u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 12u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t sjrmtHandle = readGuestU32(env.rdram.data(), kRecvAddr); + t.IsTrue(sjrmtHandle != 0u, "SJRMT_UNI_CREATE should return a handle"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, sjrmtHandle); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, 1u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0x12345678u); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x400u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t sjxHandle = readGuestU32(env.rdram.data(), kRecvAddr); + t.IsTrue(sjxHandle != 0u, "SJX_CREATE should return a handle"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWorkAddr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWorkAddr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 2u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed"); + + std::memset(env.rdram.data() + kEeWorkAddr, 0, kWorkLen); + std::memset(env.rdram.data() + kIopWorkAddr, 0, kWorkLen); + std::memset(env.rdram.data() + kRingAddr, 0, kWorkLen); + for (uint32_t i = 0; i < kChunkLen; ++i) + { + env.rdram[kChunkDataAddr + i] = static_cast(0xA0u + i); + } + + writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x00u, 1u); + env.rdram[kEeWorkAddr + 0x10u] = 0u; + env.rdram[kEeWorkAddr + 0x11u] = 1u; + std::memcpy(env.rdram.data() + kEeWorkAddr + 0x12u, "\0\0", 2u); + writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x14u, sjxHandle); + writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x18u, kChunkDataAddr); + writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x1Cu, kChunkLen); + writeGuestU32(env.rdram.data(), kEeWorkAddr + kWorkLen - sizeof(uint32_t), 1u); + + const Ps2SifDmaTransfer desc{ + kEeWorkAddr, + kIopWorkAddr, + static_cast(kWorkLen), + 0}; + std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc)); + + setRegU32(env.ctx, 4, kDescAddr); + setRegU32(env.ctx, 5, 1u); + ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); + t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the SJX transport"); + t.Equals(env.rdram[kEeWorkAddr + 0x11u], static_cast(0u), + "SJX DMA ack should rewrite the response line to room so EE recycles the chunk"); + t.Equals(readGuestU32(env.rdram.data(), kEeWorkAddr + kWorkLen - sizeof(uint32_t)), 2u, + "SJX DMA ack should still advance the EE footer ticket"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 1u); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x429u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 8u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), kChunkLen, + "SJX DMA should make SJRMT report available data"); + t.IsTrue(std::memcmp(env.rdram.data() + kRingAddr, env.rdram.data() + kChunkDataAddr, kChunkLen) == 0, + "SJX DMA should copy the chunk payload into the emulated SJRMT ring"); + }); + + tc.Run("sceSifSetDma lets active PS2RNA playback drain emulated SJRMT data", [](TestCase &t) + { + TestEnv env; + setRecvxDtxCompatLayout(); + + constexpr uint32_t kClientAddr = 0x0002F000u; + constexpr uint32_t kDtxSid = 0x7D000000u; + constexpr uint32_t kRecvAddr = 0x0002F100u; + constexpr uint32_t kSendAddr = 0x0002F200u; + constexpr uint32_t kDesc0Addr = 0x0002F300u; + constexpr uint32_t kDesc1Addr = 0x0002F320u; + constexpr uint32_t kEeWork0Addr = 0x0002F400u; + constexpr uint32_t kIopWork0Addr = 0x0002F800u; + constexpr uint32_t kEeWork1Addr = 0x0002FC00u; + constexpr uint32_t kIopWork1Addr = 0x00030000u; + constexpr uint32_t kRingAddr = 0x00030400u; + constexpr uint32_t kChunkDataAddr = 0x00030500u; + constexpr uint32_t kWorkLen = 0x100u; + constexpr uint32_t kChunkLen = 8u; + + ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, kDtxSid); + setRegU32(env.ctx, 6, 0u); + ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should bind the DTX sid"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kRingAddr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kWorkLen); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x422u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 12u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t sjrmtHandle = readGuestU32(env.rdram.data(), kRecvAddr); + t.IsTrue(sjrmtHandle != 0u, "SJRMT_UNI_CREATE should return a handle"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, sjrmtHandle); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, 1u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0xCAFEBABEu); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x400u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t sjxHandle = readGuestU32(env.rdram.data(), kRecvAddr); + t.IsTrue(sjxHandle != 0u, "SJX_CREATE should return a handle"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 0u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, sjrmtHandle); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0u); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x408u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t ps2RnaHandle = readGuestU32(env.rdram.data(), kRecvAddr); + t.IsTrue(ps2RnaHandle != 0u, "PS2RNA_CREATE should return a handle"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWork0Addr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWork0Addr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 2u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed for SJX transport"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWork1Addr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWork1Addr); + writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 2u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 16u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed for PS2RNA transport"); + + std::memset(env.rdram.data() + kEeWork0Addr, 0, kWorkLen); + std::memset(env.rdram.data() + kIopWork0Addr, 0, kWorkLen); + std::memset(env.rdram.data() + kEeWork1Addr, 0, kWorkLen); + std::memset(env.rdram.data() + kIopWork1Addr, 0, kWorkLen); + std::memset(env.rdram.data() + kRingAddr, 0, kWorkLen); + for (uint32_t i = 0; i < kChunkLen; ++i) + { + env.rdram[kChunkDataAddr + i] = static_cast(0xB0u + i); + } + + writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x00u, 1u); + writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x10u, 2u); + writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x14u, ps2RnaHandle); + writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x18u, 1u); + writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x1Cu, 0u); + writeGuestU32(env.rdram.data(), kEeWork1Addr + kWorkLen - sizeof(uint32_t), 1u); + + const Ps2SifDmaTransfer desc1{ + kEeWork1Addr, + kIopWork1Addr, + static_cast(kWorkLen), + 0}; + std::memcpy(env.rdram.data() + kDesc1Addr, &desc1, sizeof(desc1)); + + setRegU32(env.ctx, 4, kDesc1Addr); + setRegU32(env.ctx, 5, 1u); + ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); + t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the PS2RNA control transport"); + t.Equals(readGuestU32(env.rdram.data(), kEeWork1Addr + kWorkLen - sizeof(uint32_t)), 2u, + "PS2RNA control DMA should advance the EE footer ticket"); + + writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x00u, 1u); + env.rdram[kEeWork0Addr + 0x10u] = 0u; + env.rdram[kEeWork0Addr + 0x11u] = 1u; + std::memcpy(env.rdram.data() + kEeWork0Addr + 0x12u, "\0\0", 2u); + writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x14u, sjxHandle); + writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x18u, kChunkDataAddr); + writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x1Cu, kChunkLen); + writeGuestU32(env.rdram.data(), kEeWork0Addr + kWorkLen - sizeof(uint32_t), 1u); + + const Ps2SifDmaTransfer desc0{ + kEeWork0Addr, + kIopWork0Addr, + static_cast(kWorkLen), + 0}; + std::memcpy(env.rdram.data() + kDesc0Addr, &desc0, sizeof(desc0)); + + setRegU32(env.ctx, 4, kDesc0Addr); + setRegU32(env.ctx, 5, 1u); + ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); + t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the SJX transport"); + t.Equals(env.rdram[kEeWork0Addr + 0x11u], static_cast(0u), + "SJX DMA ack should still rewrite the response line to room"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 1u); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x429u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 8u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), 0u, + "active PS2RNA playback should drain remote SJRMT data instead of leaving it queued forever"); + + writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); + writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 0u); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x429u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, kSendAddr); + setRegU32(env.ctx, 8, 8u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), kWorkLen, + "drained PS2RNA playback should return remote SJRMT room to full capacity"); + }); + tc.Run("resetSifState seeds boot-ready SIF registers", [](TestCase &t) { TestEnv env; @@ -321,6 +756,142 @@ void register_ps2_sif_dma_tests() t.Equals(static_cast(rd.size), kSize, "receive metadata size should be populated"); }); + tc.Run("sceSifGetOtherData preserves live sound-status sums when compat backfill is enabled", [](TestCase &t) + { + TestEnv env; + + constexpr uint32_t kRdAddr = 0x00023300u; + constexpr uint32_t kDstAddr = 0x00023400u; + constexpr uint32_t kSize = 0x42u; + constexpr uint32_t kPrimarySeCheckAddr = 0x01E0EF10u; + constexpr uint32_t kPrimaryMidiCheckAddr = 0x01E0EF20u; + constexpr uint32_t kMidiSumOffset = 0x1Eu; + constexpr uint32_t kSeSumOffset = 0x26u; + constexpr uint32_t kBank = 1u; + + PS2SoundDriverCompatLayout compat{}; + compat.primarySeCheckAddr = kPrimarySeCheckAddr; + compat.primaryMidiCheckAddr = kPrimaryMidiCheckAddr; + ps2_syscalls::setSoundDriverCompatLayout(compat); + + constexpr uint32_t kClientAddr = 0x00023500u; + constexpr uint32_t kRecvAddr = 0x00023600u; + constexpr uint32_t kSid = 1u; + + ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, kSid); + setRegU32(env.ctx, 6, 0u); + ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sound-driver sid"); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x12u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, 0u); + setRegU32(env.ctx, 8, 0u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t kSrcAddr = readGuestU32(env.rdram.data(), kRecvAddr); + + std::memset(env.rdram.data() + kDstAddr, 0, kSize); + std::memset(env.rdram.data() + kRdAddr, 0, sizeof(SifRpcReceiveData)); + + writeGuestS16(env.rdram.data(), kSrcAddr + kSeSumOffset + (kBank * 2u), static_cast(0x1357)); + writeGuestS16(env.rdram.data(), kSrcAddr + kMidiSumOffset + (kBank * 2u), static_cast(0x2468)); + + writeGuestS16(env.rdram.data(), kPrimarySeCheckAddr + (kBank * 2u), static_cast(0x7B7B)); + writeGuestS16(env.rdram.data(), kPrimaryMidiCheckAddr + (kBank * 2u), static_cast(0x6A6A)); + + setRegU32(env.ctx, 4, kRdAddr); + setRegU32(env.ctx, 5, kSrcAddr); + setRegU32(env.ctx, 6, kDstAddr); + setRegU32(env.ctx, 7, kSize); + ps2_stubs::sceSifGetOtherData(env.rdram.data(), &env.ctx, &env.runtime); + + t.Equals(getRegS32(env.ctx, 2), 0, + "sceSifGetOtherData should succeed for sound-status transfer"); + t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kBank * 2u)), + static_cast(0x1357), + "live se_sum for the active bank should not be clobbered by compat check arrays"); + t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kBank * 2u)), + static_cast(0x2468), + "live midi_sum for the active bank should not be clobbered by compat check arrays"); + }); + + tc.Run("sceSifGetOtherData backfills zero sound-status sums for later banks", [](TestCase &t) + { + TestEnv env; + + constexpr uint32_t kRdAddr = 0x00023700u; + constexpr uint32_t kDstAddr = 0x00023800u; + constexpr uint32_t kSize = 0x42u; + constexpr uint32_t kPrimarySeCheckAddr = 0x01E0EF10u; + constexpr uint32_t kPrimaryMidiCheckAddr = 0x01E0EF20u; + constexpr uint32_t kMidiSumOffset = 0x1Eu; + constexpr uint32_t kSeSumOffset = 0x26u; + constexpr uint32_t kLiveBank = 0u; + constexpr uint32_t kPendingBank = 1u; + + PS2SoundDriverCompatLayout compat{}; + compat.primarySeCheckAddr = kPrimarySeCheckAddr; + compat.primaryMidiCheckAddr = kPrimaryMidiCheckAddr; + ps2_syscalls::setSoundDriverCompatLayout(compat); + + constexpr uint32_t kClientAddr = 0x00023900u; + constexpr uint32_t kRecvAddr = 0x00023A00u; + + ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 1u); + setRegU32(env.ctx, 6, 0u); + ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sound-driver sid"); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, 0x12u); + setRegU32(env.ctx, 6, 0u); + setRegU32(env.ctx, 7, 0u); + setRegU32(env.ctx, 8, 0u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 4u); + setRegU32(env.ctx, 11, 0u); + ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t kSrcAddr = readGuestU32(env.rdram.data(), kRecvAddr); + + std::memset(env.rdram.data() + kDstAddr, 0, kSize); + std::memset(env.rdram.data() + kRdAddr, 0, sizeof(SifRpcReceiveData)); + + writeGuestS16(env.rdram.data(), kSrcAddr + kSeSumOffset + (kLiveBank * 2u), static_cast(0x1111)); + writeGuestS16(env.rdram.data(), kSrcAddr + kMidiSumOffset + (kLiveBank * 2u), static_cast(0x2222)); + + writeGuestS16(env.rdram.data(), kPrimarySeCheckAddr + (kPendingBank * 2u), static_cast(0x3333)); + writeGuestS16(env.rdram.data(), kPrimaryMidiCheckAddr + (kPendingBank * 2u), static_cast(0x4444)); + + setRegU32(env.ctx, 4, kRdAddr); + setRegU32(env.ctx, 5, kSrcAddr); + setRegU32(env.ctx, 6, kDstAddr); + setRegU32(env.ctx, 7, kSize); + ps2_stubs::sceSifGetOtherData(env.rdram.data(), &env.ctx, &env.runtime); + + t.Equals(getRegS32(env.ctx, 2), 0, + "sceSifGetOtherData should succeed for later-bank sound-status transfer"); + t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kLiveBank * 2u)), + static_cast(0x1111), + "existing live se_sum values should remain intact"); + t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kLiveBank * 2u)), + static_cast(0x2222), + "existing live midi_sum values should remain intact"); + t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kPendingBank * 2u)), + static_cast(0x3333), + "zero se_sum slots should backfill from compat tables for later banks"); + t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kPendingBank * 2u)), + static_cast(0x4444), + "zero midi_sum slots should backfill from compat tables for later banks"); + }); + tc.Run("sceSifGetOtherData rejects unsupported guest segments", [](TestCase &t) { TestEnv env; diff --git a/ps2xTest/src/ps2_sif_rpc_tests.cpp b/ps2xTest/src/ps2_sif_rpc_tests.cpp index 3aede2f4..612bcbb6 100644 --- a/ps2xTest/src/ps2_sif_rpc_tests.cpp +++ b/ps2xTest/src/ps2_sif_rpc_tests.cpp @@ -89,10 +89,26 @@ namespace TestEnv() : rdram(PS2_RAM_SIZE, 0) { ps2_stubs::resetSifState(); + ps2_syscalls::resetSoundDriverRpcState(); + ps2_syscalls::clearSoundDriverCompatLayout(); + ps2_syscalls::clearDtxCompatLayout(); std::memset(&ctx, 0, sizeof(ctx)); } }; + void setRecvxDtxCompatLayout() + { + PS2DtxCompatLayout layout{}; + layout.rpcSid = 0x7D000000u; + layout.urpcObjBase = 0x01F18000u; + layout.urpcObjLimit = 0x01F1FF00u; + layout.urpcObjStride = 0x20u; + layout.urpcFnTableBase = 0x0034FED0u; + layout.urpcObjTableBase = 0x0034FFD0u; + layout.dispatcherFuncAddr = 0x002FABC0u; + ps2_syscalls::setDtxCompatLayout(layout); + } + void setRegU32(R5900Context &ctx, int reg, uint32_t value) { ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); @@ -309,14 +325,14 @@ void register_ps2_sif_rpc_tests() t.Equals(getRegU32Result(env.ctx, 2), 0u, "removing the same queue twice should return 0"); }); - tc.Run("sid1 nowait RPC 0x12/0x13 returns expected pointers and signals sema", [](TestCase &t) + tc.Run("snddrv state RPC returns stable buffers and signals sema", [](TestCase &t) { TestEnv env; constexpr uint32_t kClientAddr = 0x00028000u; constexpr uint32_t kSemaParamAddr = 0x00028100u; constexpr uint32_t kRecvAddr = 0x00028200u; - constexpr uint32_t kSid = 1u; + constexpr uint32_t kSid = IOP_SID_SNDDRV_STATE; SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); @@ -339,11 +355,7 @@ void register_ps2_sif_rpc_tests() setRegU32(env.ctx, 5, kSid); setRegU32(env.ctx, 6, 0u); SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sid 1"); - - SifRpcClientData client = readGuestStruct(env.rdram.data(), kClientAddr); - client.hdr.sema_id = semaId; - writeGuestStruct(env.rdram.data(), kClientAddr, client); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for snddrv state sid"); setRegU32(env.ctx, 4, static_cast(semaId)); PollSema(env.rdram.data(), &env.ctx, &env.runtime); @@ -351,7 +363,7 @@ void register_ps2_sif_rpc_tests() std::memset(env.rdram.data() + kRecvAddr, 0, 16u); setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x12u); + setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_STATUS_ADDR); setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); setRegU32(env.ctx, 7, 0u); setRegU32(env.ctx, 8, 0u); @@ -359,10 +371,11 @@ void register_ps2_sif_rpc_tests() setRegU32(env.ctx, 10, 16u); setRegU32(env.ctx, 11, 0u); setRegU32(env.ctx, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, 0u); + writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc(0x12) should succeed"); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr), 0x00012000u, "rpc 0x12 should return SND_STATUS pointer"); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc(sound status) should succeed"); + const uint32_t statusAddr = readGuestStruct(env.rdram.data(), kRecvAddr); + t.IsTrue(statusAddr != 0u, "rpc 0x12 should return a nonzero sound-status pointer"); setRegU32(env.ctx, 4, static_cast(semaId)); PollSema(env.rdram.data(), &env.ctx, &env.runtime); @@ -370,20 +383,105 @@ void register_ps2_sif_rpc_tests() std::memset(env.rdram.data() + kRecvAddr, 0, 16u); setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x13u); + setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_ADDR_TABLE); setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); setRegU32(env.ctx, 7, 0u); setRegU32(env.ctx, 8, 0u); setRegU32(env.ctx, 9, kRecvAddr); setRegU32(env.ctx, 10, 16u); setRegU32(env.ctx, 11, 0u); + writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc(0x13) should succeed"); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr), 0x00012100u, "rpc 0x13 should return address-table pointer"); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc(sound addr table) should succeed"); + const uint32_t addrTableAddr = readGuestStruct(env.rdram.data(), kRecvAddr); + t.IsTrue(addrTableAddr != 0u, "rpc 0x13 should return a nonzero address-table pointer"); + t.IsTrue(addrTableAddr != statusAddr, "sound-status and address-table pointers should be distinct"); setRegU32(env.ctx, 4, static_cast(semaId)); PollSema(env.rdram.data(), &env.ctx, &env.runtime); t.Equals(getRegS32(env.ctx, 2), KE_OK, "each nowait rpc should signal completion sema"); + + std::memset(env.rdram.data() + kRecvAddr, 0, 16u); + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_STATUS_ADDR); + setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); + setRegU32(env.ctx, 7, 0u); + setRegU32(env.ctx, 8, 0u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 16u); + setRegU32(env.ctx, 11, 0u); + writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); + SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr), statusAddr, + "sound-status pointer should remain stable across repeated rpc 0x12 calls"); + }); + + tc.Run("snddrv state RPC returns low guest sound-driver addresses", [](TestCase &t) + { + TestEnv env; + + constexpr uint32_t kClientAddr = 0x00028300u; + constexpr uint32_t kSemaParamAddr = 0x00028400u; + constexpr uint32_t kRecvAddr = 0x00028500u; + constexpr uint32_t kSid = IOP_SID_SNDDRV_STATE; + + SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); + + const uint32_t semaParam[6] = {0u, 1u, 0u, 0u, 0u, 0u}; + std::memcpy(env.rdram.data() + kSemaParamAddr, semaParam, sizeof(semaParam)); + + setRegU32(env.ctx, 4, kSemaParamAddr); + CreateSema(env.rdram.data(), &env.ctx, &env.runtime); + const int32_t semaId = getRegS32(env.ctx, 2); + t.IsTrue(semaId > 0, "CreateSema should return a positive semaphore id"); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, kSid); + setRegU32(env.ctx, 6, 0u); + SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); + t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for snddrv state sid"); + + SifRpcClientData client = readGuestStruct(env.rdram.data(), kClientAddr); + client.hdr.sema_id = semaId; + writeGuestStruct(env.rdram.data(), kClientAddr, client); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_STATUS_ADDR); + setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); + setRegU32(env.ctx, 7, 0u); + setRegU32(env.ctx, 8, 0u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 16u); + setRegU32(env.ctx, 11, 0u); + setRegU32(env.ctx, 29, K_STACK_ADDR); + writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); + SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t statusAddr = readGuestStruct(env.rdram.data(), kRecvAddr); + t.IsTrue(statusAddr > 0u && statusAddr < 0x00200000u, + "rpc 0x12 should return a low guest address like an IOP pointer"); + + setRegU32(env.ctx, 4, kClientAddr); + setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_ADDR_TABLE); + setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); + setRegU32(env.ctx, 7, 0u); + setRegU32(env.ctx, 8, 0u); + setRegU32(env.ctx, 9, kRecvAddr); + setRegU32(env.ctx, 10, 16u); + setRegU32(env.ctx, 11, 0u); + SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); + const uint32_t addrTableAddr = readGuestStruct(env.rdram.data(), kRecvAddr); + t.IsTrue(addrTableAddr > 0u && addrTableAddr < 0x00200000u, + "rpc 0x13 should return a low guest address like an IOP pointer"); + + const uint32_t hdBaseAddr = readGuestStruct(env.rdram.data(), addrTableAddr + 0u); + const uint32_t sqBaseAddr = readGuestStruct(env.rdram.data(), addrTableAddr + 4u); + const uint32_t dataBaseAddr = readGuestStruct(env.rdram.data(), addrTableAddr + 8u); + t.IsTrue(hdBaseAddr > 0u && hdBaseAddr < 0x00200000u, + "sound-driver hd base should stay in low guest address space"); + t.IsTrue(sqBaseAddr > hdBaseAddr && sqBaseAddr < 0x00200000u, + "sound-driver sq base should be a later low guest address"); + t.IsTrue(dataBaseAddr > sqBaseAddr && dataBaseAddr < 0x00200000u, + "sound-driver data base should be a later low guest address"); }); tc.Run("SifCallRpc falls back to stack ABI when register pack is implausible", [](TestCase &t) @@ -465,6 +563,7 @@ void register_ps2_sif_rpc_tests() tc.Run("SifCallRpc prefers stack ABI for DTX URPC when both packs look plausible", [](TestCase &t) { TestEnv env; + setRecvxDtxCompatLayout(); constexpr uint32_t kClientAddr = 0x0002B000u; constexpr uint32_t kDtxSid = 0x7D000000u;