From c51a7192b207b0b1d96e806e0a237b7ba3595577 Mon Sep 17 00:00:00 2001 From: simonbronk Date: Fri, 16 Oct 2026 08:58:06 +0200 Subject: [PATCH 001/176] fix(relinker): accept omitted empty array sizes in strict filtering (cherry picked from commit 13b6650cf9f3b8042ed10ff6a83f8e9bf1c00a5c) --- .../UnusedNidFilter/StrictNidFilter.cpp | 4 +- .../relinker/tests/test_init_array_roots.py | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 3eede671ebc8..d1d937a7ba13 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -98,8 +98,8 @@ class StrictImage { for (const auto& [addressTag, sizeTag] : {std::pair{25, 27}, std::pair{26, 28}, std::pair{32, 33}}) { const auto array = value(addressTag, 0x60000000 + addressTag); const auto size = value(sizeTag, 0x60000000 + sizeTag); - if (array.has_value() != size.has_value()) throw RelinkerException("Strict filter: incomplete constructor array metadata"); - if (!array) continue; + if (size && !array) throw RelinkerException("Strict filter: incomplete constructor array metadata"); + if (!size || *size == 0) continue; if (*size % 8 != 0) throw RelinkerException("Strict filter: invalid constructor array size", *array); for (std::uint64_t offset = 0; offset < *size; offset += 8) { const auto slot = *array + offset; diff --git a/core/relinker/relinker/tests/test_init_array_roots.py b/core/relinker/relinker/tests/test_init_array_roots.py index a886cc17ed54..bc3348e1e4c3 100644 --- a/core/relinker/relinker/tests/test_init_array_roots.py +++ b/core/relinker/relinker/tests/test_init_array_roots.py @@ -1,4 +1,5 @@ from pathlib import Path +import os import struct import subprocess import sys @@ -6,6 +7,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) from test_jmprel_table_bounds import fixture, DT_RELA, DT_RELASZ +from test_guest_symbol_names import DYNAMIC as STRICT_DYNAMIC, importing_executable DT_INIT_ARRAY = 25 @@ -72,6 +74,52 @@ def filtered(relinker, work, name, elf): return lines[0] +def strict_array_metadata(relinker, work): + families = ((25, 27), (26, 28), (32, 33), (0x60000019, 0x6000001B), + (0x6000001A, 0x6000001C), (0x60000020, 0x60000021)) + slot = 0x4E00 + outside = 0xFFFFFFFFFFFFFFF0 + for address_tag, size_tag in families: + cases = ( + ("missing-size", [(address_tag, outside)], None), + ("zero-size", [(address_tag, outside), (size_tag, 0)], None), + ("nonempty", [(address_tag, slot), (size_tag, 8)], None), + ("size-only-zero", [(size_tag, 0)], "incomplete constructor array metadata"), + ("size-only-nonzero", [(size_tag, 8)], "incomplete constructor array metadata"), + ("partial-entry", [(address_tag, slot), (size_tag, 7)], "invalid constructor array size"), + ("unmapped", [(address_tag, outside), (size_tag, 8)], "virtual range is not file-backed"), + ) + for name, extra, error in cases: + elf = importing_executable([]) + dynamic_size, = struct.unpack_from(" 0; filtered=0" in result.stdout, result.stdout + live_regions = 2 if name == "nonempty" else 1 + assert f"Function graph: {live_regions}/" in result.stdout, result.stdout + if windows and os.name == "nt": + assert subprocess.run([str(output)], capture_output=True, timeout=20).returncode == 42 + + def main(): relinker = Path(sys.argv[1]).resolve() with tempfile.TemporaryDirectory(prefix="anyps5-init-array-") as directory: @@ -105,6 +153,7 @@ def main(): image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, LOAD_END + 0x100, [FUNCTION], load_size=LOAD_END)) assert result.returncode == 2 and "array is not file-backed" in result.stderr and not output.exists(), ( result.returncode, result.stdout, result.stderr) + strict_array_metadata(relinker, work) print("Init array root tests passed") From efe4c01d3fa391504cdf04be91eff898a9d2ce9a Mon Sep 17 00:00:00 2001 From: David Date: Thu, 15 Oct 2026 09:57:56 +0400 Subject: [PATCH 002/176] fix(relinker): trap guest GS accesses in macOS output instead of refusing the module On x86-64 macOS, including under Rosetta, the host owns GS (it holds the pthread TSD base, which the macOS TLS stubs read), so a guest instruction that reads or writes through GS cannot be translated, and the TLS scan refused the whole module. Prospero Win's (PPSA99995) Wine ntdll has six. Overwrite each in place with ud2 and NOP padding of its own length, as the Windows writer does in #6102, and refuse one that is also an AMD-only trampoline site. (cherry picked from commit 55c6254ee67f523dc0168700f31ed84c68902b15) --- .../elfpatcher/src/macos/MacOsImage.cpp | 28 +++++++++++++++++++ .../relinker/tests/test_macos_tls_overlap.py | 17 ++++++----- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 635d4f93b85d..81fc38d4db65 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -194,6 +195,11 @@ void AppendPathCommand(std::vector& out, const std::uint32_t cmd, out.resize(start + size, 0); } +struct GsTrap { + std::uint32_t Rva; + std::size_t Length; +}; + struct TlsAccess { std::uint32_t Rva; std::size_t Length; @@ -212,6 +218,7 @@ struct TlsScan { const Domain::ProgramHeader* Tls = nullptr; std::vector Accesses; std::set BranchTargets; + std::vector GsTraps; }; TlsScan ScanTls(const std::vector& source, const std::vector& headers, const Windows::WindowsLoadImage& image) { @@ -237,6 +244,10 @@ TlsScan ScanTls(const std::vector& source, const std::vector= 0 && target <= std::numeric_limits::max()) scan.BranchTargets.insert(static_cast(target)); } if (info.SegmentPrefix == 0 || (insidePrevious && info.SegmentPrefix != 0x64)) continue; + if (info.SegmentPrefix == 0x65) { + scan.GsTraps.push_back({rva, info.Length}); + continue; + } const auto position = info.OpcodeOffset; bool hasOperandSizePrefix = false; bool supportedPrefixes = info.SegmentPrefix == 0x64; @@ -380,6 +391,23 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { segments.push_back({"__TEXT", 0, LoadRva, VmProtRead | VmProtExecute, {}, {}}); const auto guestSegments = segments.size(); auto sections = image.BuildSections(); + for (const auto& trap : tls.GsTraps) { + if (input.Trampolines != nullptr) { + for (const auto& site : *input.Trampolines) { + const auto siteRva = image.GetRva(site.Address, site.Length); + if (siteRva < trap.Rva + trap.Length && trap.Rva < siteRva + site.Length) + throw Domain::RelinkerException("AMD-only instruction is also a guest GS access", siteRva); + } + } + const auto section = std::find_if(sections.begin(), sections.end(), [&](const auto& candidate) { + return trap.Rva >= candidate.Rva && trap.Rva - candidate.Rva < candidate.Data.size(); + }); + if (section == sections.end()) throw Domain::RelinkerException("Guest GS instruction is outside the image", trap.Rva); + const auto offset = trap.Rva - section->Rva; + if (trap.Length > section->Data.size() - offset) throw Domain::RelinkerException("Guest GS instruction crosses a section boundary", trap.Rva); + std::fill_n(section->Data.begin() + offset, trap.Length, 0x90); + std::copy(std::begin(kStubOpUd2), std::end(kStubOpUd2), section->Data.begin() + offset); + } if (input.Trampolines != nullptr && !input.Trampolines->empty()) { for (const auto& access : tls.Accesses) { for (const auto& site : *input.Trampolines) { diff --git a/core/relinker/relinker/tests/test_macos_tls_overlap.py b/core/relinker/relinker/tests/test_macos_tls_overlap.py index 0eaeb6396da6..cadbda39c3eb 100644 --- a/core/relinker/relinker/tests/test_macos_tls_overlap.py +++ b/core/relinker/relinker/tests/test_macos_tls_overlap.py @@ -30,19 +30,22 @@ def check_split_function(relinker, directory): raise SystemExit("function-begins-inside-instruction: the instruction around the GS-prefixed decoding changed") -def check_gs_access_refused(relinker, directory): - image = make_image("register", "unwind", body=bytes.fromhex("65 48 8b 04 25 28 00 00 00") + b"\xc3") - result, output = convert(relinker, directory, "gs-access", image) - message = result.stdout + result.stderr - if result.returncode != 2 or "Unsupported macOS guest TLS instruction" not in message or output.exists(): - raise SystemExit(f"gs-access: a GS access at an instruction start was not refused: {message}") +def check_gs_access_trapped(relinker, directory): + instruction = bytes.fromhex("65 48 8b 04 25 28 00 00 00") + result, output = convert(relinker, directory, "gs-access", make_image("register", "unwind", body=instruction + b"\xc3")) + if result.returncode != 0: + raise SystemExit(f"gs-access: relinker failed: {result.stdout}{result.stderr}") + data = output.read_bytes() + code = b"".join(data[lo:hi] for lo, hi in MachO(data).executable_ranges()) + if b"\x0f\x0b" + b"\x90" * (len(instruction) - 2) + b"\xc3" not in code: + raise SystemExit("gs-access: a GS access at an instruction start was not trapped") def main(): relinker = Path(sys.argv[1]).resolve() with tempfile.TemporaryDirectory(prefix="anyps5-macos-tls-overlap-") as directory: check_split_function(relinker, directory) - check_gs_access_refused(relinker, directory) + check_gs_access_trapped(relinker, directory) print("macOS TLS overlap checks passed") From 7645b17377366123a34f4642378ec4ff73919665 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Thu, 15 Oct 2026 12:28:05 -0400 Subject: [PATCH 003/176] fix(build): keep bundled SPIRV validator tables valid at exit (cherry picked from commit 4187f0b72c31b472dc1103b24f819b5d6f603d8c) --- core/shader/recompiler/CMakeLists.txt | 8 +++ core/shader/recompiler/PatchSpirvTools.cmake | 46 ++++++++++++++ .../recompiler/tests/SpirvValidatorExit.cpp | 62 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 core/shader/recompiler/tests/SpirvValidatorExit.cpp diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 6f2f8a6380b9..18868421fa21 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -317,4 +317,12 @@ function(add_shader_recompiler target runtimeTarget) target_link_libraries(${target} PRIVATE SPIRV-Tools-opt SPIRV-Tools-static) endif() target_compile_definitions(${target} PUBLIC ANYPS5_ENABLE_SPIRV_TOOLS=$) + if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS AND NOT ANYPS5_USE_SYSTEM_DEPS AND NOT TARGET spirv_validator_exit_tests) + add_test_executable(spirv_validator_exit_tests "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/SpirvValidatorExit.cpp") + target_link_libraries(spirv_validator_exit_tests PRIVATE SPIRV-Tools-static) + set_target_properties(spirv_validator_exit_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(spirv_validator_exit_tests) + add_test(NAME spirv_validator_exit COMMAND spirv_validator_exit_tests) + set_tests_properties(spirv_validator_exit PROPERTIES TIMEOUT 30) + endif() endfunction() diff --git a/core/shader/recompiler/PatchSpirvTools.cmake b/core/shader/recompiler/PatchSpirvTools.cmake index 69cb4bcfc79b..4acc117b3bd8 100644 --- a/core/shader/recompiler/PatchSpirvTools.cmake +++ b/core/shader/recompiler/PatchSpirvTools.cmake @@ -120,3 +120,49 @@ if(NOT spirvToolsReplacements EQUAL 1) message(FATAL_ERROR "SPIRV-Tools optimizer sources have changed; review the local memory fix") endif() set_property(TARGET SPIRV-Tools-opt PROPERTY SOURCES "${spirvToolsPatchedSources}") + +get_target_property(spirvToolsValidatorSourceDir SPIRV-Tools-static SOURCE_DIR) +set(spirvToolsValidatorSource "${spirvToolsValidatorSourceDir}/val/validate_id.cpp") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${spirvToolsValidatorSource}") +file(READ "${spirvToolsValidatorSource}" spirvToolsValidator) +string(FIND "${spirvToolsValidator}" "#include " spirvToolsValidatorInclude) +if(spirvToolsValidatorInclude EQUAL -1) + message(FATAL_ERROR "SPIRV-Tools validator includes have changed; review the local lifetime fix") +endif() +string(REPLACE "#include " "#include \n#include " spirvToolsValidator "${spirvToolsValidator}") +foreach(table IN ITEMS allow deny) + set(oldDeclaration "static std::unordered_set instruction_${table}_set{") + set(newDeclaration "static constexpr spv::Op instruction_${table}_set[]{") + set(oldLookup "instruction_${table}_set.count(opcode)") + set(newLookup "(std::find(std::begin(instruction_${table}_set),\n std::end(instruction_${table}_set), opcode) !=\n std::end(instruction_${table}_set))") + string(FIND "${spirvToolsValidator}" "${oldDeclaration}" declarationPosition) + string(FIND "${spirvToolsValidator}" "${oldLookup}" lookupPosition) + if(declarationPosition EQUAL -1 OR lookupPosition EQUAL -1) + message(FATAL_ERROR "SPIRV-Tools validator type-operand tables have changed; review the local lifetime fix") + endif() + string(REPLACE "${oldDeclaration}" "${newDeclaration}" spirvToolsValidator "${spirvToolsValidator}") + string(REPLACE "${oldLookup}" "${newLookup}" spirvToolsValidator "${spirvToolsValidator}") +endforeach() +set(spirvToolsPatchedValidator "${spirvToolsPatchedDir}/validate_id.cpp") +file(CONFIGURE OUTPUT "${spirvToolsPatchedValidator}" CONTENT "${spirvToolsValidator}" @ONLY) +foreach(validatorTarget IN ITEMS SPIRV-Tools-static SPIRV-Tools-shared) + if(NOT TARGET ${validatorTarget}) + continue() + endif() + get_target_property(validatorSources ${validatorTarget} SOURCES) + set(patchedValidatorSources) + set(validatorReplacements 0) + foreach(source IN LISTS validatorSources) + get_filename_component(sourcePath "${source}" ABSOLUTE BASE_DIR "${spirvToolsValidatorSourceDir}") + if(sourcePath STREQUAL spirvToolsValidatorSource) + list(APPEND patchedValidatorSources "${spirvToolsPatchedValidator}") + math(EXPR validatorReplacements "${validatorReplacements} + 1") + else() + list(APPEND patchedValidatorSources "${source}") + endif() + endforeach() + if(NOT validatorReplacements EQUAL 1) + message(FATAL_ERROR "SPIRV-Tools validator sources have changed; review the local lifetime fix") + endif() + set_property(TARGET ${validatorTarget} PROPERTY SOURCES "${patchedValidatorSources}") +endforeach() diff --git a/core/shader/recompiler/tests/SpirvValidatorExit.cpp b/core/shader/recompiler/tests/SpirvValidatorExit.cpp new file mode 100644 index 000000000000..2bab5af5c967 --- /dev/null +++ b/core/shader/recompiler/tests/SpirvValidatorExit.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::array, 3> modules; + +bool Validate(const std::vector& words) { + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + return tools.Validate(words); +} + +bool Check(const char* phase) { + const bool function = Validate(modules[0]); + const bool phi = Validate(modules[1]); + const bool invalidRejected = !Validate(modules[2]); + std::fprintf(stderr, "%s: function=%d phi=%d invalidRejected=%d\n", phase, function, phi, invalidRejected); + return function && phi && invalidRejected; +} + +void ValidateAtExit() { + if (!Check("exit validation")) std::_Exit(EXIT_FAILURE); +} + +} + +int main() { + if (std::atexit(ValidateAtExit) != 0) return EXIT_FAILURE; + const std::string prefix = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%fn = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%one = OpConstant %uint 1 +%two = OpConstant %uint 2 +)"; + const std::string function = "%main = OpFunction %void None %fn\n%entry = OpLabel\n"; + const std::string tail = "OpReturn\nOpFunctionEnd\n"; + const std::string phi = R"(OpSelectionMerge %merge None +OpBranchConditional %true %left %right +%left = OpLabel +OpBranch %merge +%right = OpLabel +OpBranch %merge +%merge = OpLabel +%value = OpPhi %uint %one %left %two %right +)"; + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + if (!tools.Assemble(prefix + function + tail, &modules[0]) || + !tools.Assemble(prefix + function + phi + tail, &modules[1]) || + !tools.Assemble(prefix + "%main = OpFunction %void None %void\n%entry = OpLabel\n" + tail, &modules[2])) return EXIT_FAILURE; + return Check("warm validation") ? EXIT_SUCCESS : EXIT_FAILURE; +} From 1c7a156cd9e002f874d814f90568b7a467fc9034 Mon Sep 17 00:00:00 2001 From: Kovalev Vladislav Date: Tue, 13 Oct 2026 07:30:21 +0600 Subject: [PATCH 004/176] fix(relinker): accept section-relative guest module relocations (cherry picked from commit 5f564173209aec77c82051ada9399066941d1eb8) --- core/relinker/CMakeLists.txt | 3 + .../relinker/src/guest/GuestImageReader.cpp | 4 +- .../test_guest_relative_section_symbols.py | 83 +++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 core/relinker/relinker/tests/test_guest_relative_section_symbols.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 74f12ba877de..36caaaf14152 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -400,6 +400,9 @@ if(BUILD_TESTING) add_test(NAME guest_none_relocations COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_none_relocations.py $) set_tests_properties(guest_none_relocations PROPERTIES TIMEOUT 20) + add_test(NAME guest_relative_section_symbols COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_relative_section_symbols.py $) + set_tests_properties(guest_relative_section_symbols PROPERTIES TIMEOUT 20) add_test(NAME optional_plt COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index cffd2b4f297a..48e4d6ebac88 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -256,7 +256,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector image.Symbols.push_back({{}, 0, 0, 0, 0, 0}); continue; } - if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name); + if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && info != 3 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name); if (section != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) { const auto [found, inserted] = exports.emplace(name, image.Symbols.size()); if (name.empty() || (!inserted && (!windows || !IdenticalOrdinaryFunctionExport(image.Symbols[found->second], symbol)))) fail("Duplicate or empty export after stripping #: " + name); @@ -326,7 +326,9 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const auto& symbol = image.Symbols[info >> 32]; if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation"); } + const auto position = output.size(); output.insert(output.end(), bytes.begin() + offset + index, bytes.begin() + offset + index + 24); + if (relocation == 8) Io::WriteU64(output, position + 8, 8); } }; if (tags.contains(7) || tags.contains(0x6100002f)) { diff --git a/core/relinker/relinker/tests/test_guest_relative_section_symbols.py b/core/relinker/relinker/tests/test_guest_relative_section_symbols.py new file mode 100644 index 000000000000..1d940e1311e9 --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_relative_section_symbols.py @@ -0,0 +1,83 @@ +from pathlib import Path +import os +import platform +import struct +import subprocess +import sys +import tempfile + +from test_guest_intel_trampolines import main_fixture +from test_guest_none_relocations import FUNCTION, TARGET, UNCHANGED, change_tag, check_output, module + + +def fixture(sce=False, index=1, lifecycle=False, info=3, value=0): + image = module([(0, 0xffffffff00000000, -1), (TARGET, (index << 32) | 8, FUNCTION), + (TARGET + 8, 8, FUNCTION)], sce=sce, lifecycle=lifecycle) + if index == 1: + image[0x22B0:0x22C8] = image[0x2298:0x22B0] + count = max(3, index + 1) + struct.pack_into(" Date: Fri, 16 Oct 2026 01:14:33 +1300 Subject: [PATCH 005/176] feat(libc): implement the __std vectorized algorithm helpers __std_find_trivial(_unsized), __std_count_trivial, __std_min/max/minmax_element, __std_reverse_trivially_swappable, __std_reverse_copy_trivially_copyable and __std_swap_ranges_trivially_swappable_noalias for 1-, 2-, 4- and 8-byte elements, matching the libc.prx bundled with PPSA08427 including the rescan of the first aligned block in the unsized find. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8tinbP1MUpUaDsbNWnGnu (cherry picked from commit 6103a3e0b9d1c103ddbe9488f5ea7c93779169ad) --- core/libs/prx/libc/CMakeLists.txt | 10 ++ core/libs/prx/libc/src/VectorAlgorithms.cpp | 161 ++++++++++++++++++++ core/libs/tests/GuestVectorAlgorithms.cpp | 96 ++++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 268 insertions(+) create mode 100644 core/libs/prx/libc/src/VectorAlgorithms.cpp create mode 100644 core/libs/tests/GuestVectorAlgorithms.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 2c0fe28f97ec..1c443db72622 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -38,6 +38,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Multibyte.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RuneLocale.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VectorAlgorithms.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatBuiltins.cpp @@ -416,6 +417,15 @@ if(BUILD_TESTING) set_tests_properties(guest_hostname PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_vector_algorithms_tests ${LIBS_INCLUDE_DIR}/tests/GuestVectorAlgorithms.cpp) + target_include_directories(guest_vector_algorithms_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_vector_algorithms_tests PRIVATE libc) + configure_windows_unwind(guest_vector_algorithms_tests) + add_test(NAME guest_vector_algorithms COMMAND guest_vector_algorithms_tests) + if(WIN32) + set_tests_properties(guest_vector_algorithms PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_memrchr_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemrchr.cpp) target_include_directories(guest_memrchr_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_memrchr_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/VectorAlgorithms.cpp b/core/libs/prx/libc/src/VectorAlgorithms.cpp new file mode 100644 index 000000000000..169c0a23d8b9 --- /dev/null +++ b/core/libs/prx/libc/src/VectorAlgorithms.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +struct MinMaxPointers { + const void* minimum; + const void* maximum; +}; + +template +TValue Read(const unsigned char* address) { + TValue value; + std::memcpy(&value, address, sizeof(value)); + return value; +} + +template +const void* Find(const void* first, const void* last, TValue value) { + auto* current = static_cast(first); + auto* end = static_cast(last); + for (; current != end; current += sizeof(TValue)) { + if (Read(current) == value) return current; + } + return end; +} + +constexpr std::uintptr_t UnsizedBlock = 32; + +template +const void* FindUnsized(const void* first, TValue value) { + auto* start = static_cast(first); + auto* block = start - (reinterpret_cast(start) & (UnsizedBlock - 1)); + for (auto* current = start; current != block + UnsizedBlock; current += sizeof(TValue)) { + if (Read(current) == value) return current; + } + auto* current = block; + while (Read(current) != value) current += sizeof(TValue); + return current; +} + +template +std::size_t Count(const void* first, const void* last, TValue value) { + auto* current = static_cast(first); + auto* end = static_cast(last); + std::size_t count = 0; + for (; current != end; current += sizeof(TValue)) { + if (Read(current) == value) ++count; + } + return count; +} + +template +MinMaxPointers MinMax(const void* first, const void* last, bool wantMinimum, bool wantMaximum) { + auto* current = static_cast(first); + auto* end = static_cast(last); + MinMaxPointers result{first, first}; + if (current == end) return result; + TValue minimum = Read(current); + TValue maximum = minimum; + for (current += sizeof(TValue); current != end; current += sizeof(TValue)) { + const TValue value = Read(current); + if (wantMinimum && value < minimum) { + minimum = value; + result.minimum = current; + } + if (wantMaximum && (wantMinimum ? !(value < maximum) : maximum < value)) { + maximum = value; + result.maximum = current; + } + } + return result; +} + +template +MinMaxPointers MinMaxBySign(const void* first, const void* last, bool isSigned, bool wantMinimum, bool wantMaximum) { + using TSigned = std::make_signed_t; + return isSigned ? MinMax(first, last, wantMinimum, wantMaximum) : MinMax(first, last, wantMinimum, wantMaximum); +} + +template +void Reverse(void* first, void* last) { + auto* low = static_cast(first); + auto* high = static_cast(last); + unsigned char buffer[Size]; + while (high - low >= static_cast(2 * Size)) { + high -= Size; + std::memcpy(buffer, low, Size); + std::memcpy(low, high, Size); + std::memcpy(high, buffer, Size); + low += Size; + } +} + +template +void ReverseCopy(const void* first, const void* last, void* destination) { + auto* begin = static_cast(first); + auto* end = static_cast(last); + auto* out = static_cast(destination); + while (end != begin) { + end -= Size; + std::memcpy(out, end, Size); + out += Size; + } +} + +} + +extern "C" { + +const void* APS5_VABI __std_find_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void* first, std::uint8_t value) { return FindUnsized(first, value); } +const void* APS5_VABI __std_find_trivial_unsized_2_nid_postfix(const void* first, std::uint16_t value) { return FindUnsized(first, value); } +const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void* first, std::uint32_t value) { return FindUnsized(first, value); } +const void* APS5_VABI __std_find_trivial_unsized_8_nid_postfix(const void* first, std::uint64_t value) { return FindUnsized(first, value); } +std::size_t APS5_VABI __std_count_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value) { return Count(first, last, value); } +std::size_t APS5_VABI __std_count_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value) { return Count(first, last, value); } +std::size_t APS5_VABI __std_count_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value) { return Count(first, last, value); } +std::size_t APS5_VABI __std_count_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value) { return Count(first, last, value); } + +const void* APS5_VABI __std_min_element_1_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_min_element_2_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_min_element_4_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_min_element_8_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_max_element_1_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +const void* APS5_VABI __std_max_element_2_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +const void* APS5_VABI __std_max_element_4_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +const void* APS5_VABI __std_max_element_8_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +MinMaxPointers APS5_VABI __std_minmax_element_1_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } +MinMaxPointers APS5_VABI __std_minmax_element_2_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } +MinMaxPointers APS5_VABI __std_minmax_element_4_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } +MinMaxPointers APS5_VABI __std_minmax_element_8_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } + +void APS5_VABI __std_reverse_trivially_swappable_1_nid_postfix(void* first, void* last) { Reverse<1>(first, last); } +void APS5_VABI __std_reverse_trivially_swappable_2_nid_postfix(void* first, void* last) { Reverse<2>(first, last); } +void APS5_VABI __std_reverse_trivially_swappable_4_nid_postfix(void* first, void* last) { Reverse<4>(first, last); } +void APS5_VABI __std_reverse_trivially_swappable_8_nid_postfix(void* first, void* last) { Reverse<8>(first, last); } +void APS5_VABI __std_reverse_copy_trivially_copyable_1_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<1>(first, last, destination); } +void APS5_VABI __std_reverse_copy_trivially_copyable_2_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<2>(first, last, destination); } +void APS5_VABI __std_reverse_copy_trivially_copyable_4_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<4>(first, last, destination); } +void APS5_VABI __std_reverse_copy_trivially_copyable_8_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<8>(first, last, destination); } + +void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void* first1, void* last1, void* first2) { + auto* left = static_cast(first1); + auto* right = static_cast(first2); + const std::size_t length = static_cast(static_cast(last1) - left); + for (std::size_t i = 0; i < length; ++i) { + const unsigned char byte = left[i]; + left[i] = right[i]; + right[i] = byte; + } +} + +} diff --git a/core/libs/tests/GuestVectorAlgorithms.cpp b/core/libs/tests/GuestVectorAlgorithms.cpp new file mode 100644 index 000000000000..c3b858bac6da --- /dev/null +++ b/core/libs/tests/GuestVectorAlgorithms.cpp @@ -0,0 +1,96 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +struct MinMaxPointers { + const void* minimum; + const void* maximum; +}; + +extern "C" { +const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void*, const void*, std::uint16_t); +const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void*, std::uint8_t); +const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void*, std::uint32_t); +std::size_t APS5_VABI __std_count_trivial_8_nid_postfix(const void*, const void*, std::uint64_t); +const void* APS5_VABI __std_min_element_1_nid_postfix(const void*, const void*, bool); +const void* APS5_VABI __std_max_element_4_nid_postfix(const void*, const void*, bool); +MinMaxPointers APS5_VABI __std_minmax_element_2_nid_postfix(const void*, const void*, bool); +MinMaxPointers APS5_VABI __std_minmax_element_8_nid_postfix(const void*, const void*, bool); +void APS5_VABI __std_reverse_trivially_swappable_2_nid_postfix(void*, void*); +void APS5_VABI __std_reverse_copy_trivially_copyable_8_nid_postfix(const void*, const void*, void*); +void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void*, void*, void*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void CheckFindAndCount() { + const std::uint16_t halves[] = {5, 7, 9, 7}; + Require(__std_find_trivial_2_nid_postfix(halves, halves + 4, 7) == halves + 1); + Require(__std_find_trivial_2_nid_postfix(halves, halves + 4, 8) == halves + 4); + Require(__std_find_trivial_2_nid_postfix(halves, halves, 5) == halves); + const std::uint64_t wides[] = {1, 2, 1, 1}; + Require(__std_count_trivial_8_nid_postfix(wides, wides + 4, 1) == 3 && __std_count_trivial_8_nid_postfix(wides, wides, 1) == 0); + alignas(32) std::uint32_t words[16] = {}; + words[3] = 42; + words[12] = 42; + Require(__std_find_trivial_unsized_4_nid_postfix(words + 8, 42) == words + 12); +} + +static void CheckUnsizedRescan() { + alignas(32) unsigned char bytes[96] = {}; + bytes[3] = 'x'; + bytes[70] = 'x'; + Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 3); + bytes[20] = 'x'; + Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 20); + bytes[3] = 0; + bytes[20] = 0; + Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 70); +} + +static void CheckMinMax() { + const unsigned char bytes[] = {3, 0x80, 1, 0x80, 1, 0xff}; + Require(__std_min_element_1_nid_postfix(bytes, bytes + 6, false) == bytes + 2); + Require(__std_min_element_1_nid_postfix(bytes, bytes + 6, true) == bytes + 1); + Require(__std_min_element_1_nid_postfix(bytes, bytes, true) == bytes); + const std::uint32_t words[] = {7, 0xffffffffu, 9, 0xffffffffu, 9}; + Require(__std_max_element_4_nid_postfix(words, words + 5, false) == words + 1); + Require(__std_max_element_4_nid_postfix(words, words + 5, true) == words + 2); + const std::int16_t halves[] = {4, -3, 8, -3, 8, 2}; + const MinMaxPointers signedHalves = __std_minmax_element_2_nid_postfix(halves, halves + 6, true); + Require(signedHalves.minimum == halves + 1 && signedHalves.maximum == halves + 4); + const MinMaxPointers unsignedHalves = __std_minmax_element_2_nid_postfix(halves, halves + 6, false); + Require(unsignedHalves.minimum == halves + 5 && unsignedHalves.maximum == halves + 3); + const std::uint64_t wides[] = {5}; + const MinMaxPointers single = __std_minmax_element_8_nid_postfix(wides, wides + 1, false); + Require(single.minimum == wides && single.maximum == wides); + const MinMaxPointers empty = __std_minmax_element_8_nid_postfix(wides, wides, false); + Require(empty.minimum == wides && empty.maximum == wides); +} + +static void CheckReverseAndSwap() { + std::uint16_t halves[] = {1, 2, 3, 4, 5}; + __std_reverse_trivially_swappable_2_nid_postfix(halves, halves + 5); + Require(halves[0] == 5 && halves[1] == 4 && halves[2] == 3 && halves[3] == 2 && halves[4] == 1); + const std::uint64_t wides[] = {1, 2, 3}; + std::uint64_t reversed[4] = {9, 9, 9, 9}; + __std_reverse_copy_trivially_copyable_8_nid_postfix(wides, wides + 3, reversed); + Require(reversed[0] == 3 && reversed[1] == 2 && reversed[2] == 1 && reversed[3] == 9); + unsigned char left[37], right[37]; + for (int i = 0; i < 37; ++i) { + left[i] = static_cast(i); + right[i] = static_cast(100 + i); + } + __std_swap_ranges_trivially_swappable_noalias_nid_postfix(left, left + 37, right); + for (int i = 0; i < 37; ++i) Require(left[i] == 100 + i && right[i] == i); +} + +int main() { + CheckFindAndCount(); + CheckUnsizedRescan(); + CheckMinMax(); + CheckReverseAndSwap(); + return 0; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 6798ff76443f..ea34395fa2c1 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -7,6 +7,7 @@ - [_Mtx_trylock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `k6pGNMwJB08`) - returns busy for a non-recursive mutex already owned by the caller, assumed to match `_Mtx_lock`'s ownership handling; exact console behavior is unverified. - [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified. +- [__std_find_trivial_unsized_N](../../../core/libs/prx/libc/src/VectorAlgorithms.cpp) (libc) - matches the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs): when nothing matches between `first` and the end of its 32-byte-aligned block, the console rescans that whole block without masking and so can return a match up to 31 bytes before `first` - [_Thrd_sleep](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `jfRI3snge3o`) follows the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`xtime.c`) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, retrying on `EINTR`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. - [_Mtx_init_with_name](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `tgioGpKtmbE`) forwards to `_Mtx_init` and ignores the mutex name parameter. - [_Cnd_timedwait](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `McaImWKXong`) follows the console's `_Cnd_timedwait` (disassembled from a title's `libc.prx`), where xtime is an absolute deadline converted against the current time: it returns `_Thrd_timedout` (2) on expiration and `_Thrd_success` (0) on notification. The console returns 4 for other internal pthread errors while this implementation throws. From ec5f7d7e7b6c1f255beb4ad3db0761fc98632470 Mon Sep 17 00:00:00 2001 From: hypermist Date: Fri, 16 Oct 2026 01:37:51 +1300 Subject: [PATCH 006/176] feat(libc): add exact long double and BSD math exports copysignl, fdiml, fmodl, remainderl, remquol, logbl, ilogbl, rintl, roundl, lroundl, lrintl, llrintl, sqrtl, modfl, nanl, scalblnl, fmal, rint(f), lrint(f), llrint(f), drem(f), scalbln(f), scalb(f), finite(f), __fpclassifyl, __isinfl, __isnanl, __isnormall, _LDtest, _Dclass, _FDclass, _LDclass and _LDsign, matching the libc.prx bundled with PPSA08427 bit for bit, including NaN propagation, errno and FreeBSD fmal special cases. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8tinbP1MUpUaDsbNWnGnu (cherry picked from commit 4f608a8c492d6ea5d2b6a03230b371e1557807d5) --- core/libs/prx/libc/CMakeLists.txt | 10 ++ core/libs/prx/libc/src/MathExact.cpp | 239 +++++++++++++++++++++++++++ core/libs/tests/GuestMathExact.cpp | 101 +++++++++++ docs/dev/debt/libc.md | 2 + 4 files changed, 352 insertions(+) create mode 100644 core/libs/prx/libc/src/MathExact.cpp create mode 100644 core/libs/tests/GuestMathExact.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 1c443db72622..1be99fdb8652 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -40,6 +40,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/VectorAlgorithms.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MathExact.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp @@ -321,6 +322,15 @@ if(BUILD_TESTING) set_tests_properties(guest_std_execute_once PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_math_exact_tests ${LIBS_INCLUDE_DIR}/tests/GuestMathExact.cpp) + target_include_directories(guest_math_exact_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_math_exact_tests PRIVATE libc) + configure_windows_unwind(guest_math_exact_tests) + add_test(NAME guest_math_exact COMMAND guest_math_exact_tests) + if(WIN32) + set_tests_properties(guest_math_exact PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_libc_threads_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcThreads.cpp) target_include_directories(guest_libc_threads_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_threads_tests PRIVATE libc libkernel) diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp new file mode 100644 index 000000000000..8f790c1a33db --- /dev/null +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +class ErrnoGuard { +public: + ErrnoGuard() : saved(errno) {} + ~ErrnoGuard() { errno = saved; } + ErrnoGuard(const ErrnoGuard&) = delete; + ErrnoGuard& operator=(const ErrnoGuard&) = delete; + +private: + int saved; +}; + +struct ExtendedBits { + std::uint64_t mantissa; + std::uint16_t signExponent; +}; + +ExtendedBits Bits(const long double& value) { + ExtendedBits bits; + unsigned char bytes[sizeof(long double)]; + std::memcpy(bytes, &value, sizeof(bytes)); + std::memcpy(&bits.mantissa, bytes, sizeof(bits.mantissa)); + std::memcpy(&bits.signExponent, bytes + 8, sizeof(bits.signExponent)); + return bits; +} + +constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + +short TestExtended(const ExtendedBits& bits) { + const int exponent = bits.signExponent & 0x7fff; + const std::uint64_t fraction = bits.mantissa & 0x7fffffffffffffffull; + if (exponent == 0x7fff) return fraction != 0 ? NotANumber : Infinite; + if (exponent != 0 || (bits.mantissa >> 63) != 0) return Finite; + return bits.mantissa != 0 ? Denormal : Zero; +} + +template +short TestFloat(TFloat value) { + switch (std::fpclassify(value)) { + case FP_NAN: + return NotANumber; + case FP_INFINITE: + return Infinite; + case FP_ZERO: + return Zero; + case FP_SUBNORMAL: + return Denormal; + default: + return Finite; + } +} + +} + +extern "C" { + +long double APS5_VABI copysignl_nid_postfix(long double x, long double y) { return std::copysign(x, y); } +long double APS5_VABI fdiml_nid_postfix(long double x, long double y) { + if (std::isnan(x)) return x; + if (std::isnan(y)) return y; + return x > y ? x - y : 0.0L; +} + +long double APS5_VABI fmodl_nid_postfix(long double x, long double y) { + if (!std::isfinite(x) || y == 0.0L || std::isnan(y)) { + errno = EDOM; + return (x * y) / (x * y); + } + ErrnoGuard guard; + return std::fmod(x, y); +} + +long double APS5_VABI remainderl_nid_postfix(long double x, long double y) { + return std::remainder(x, y); +} + +long double APS5_VABI remquol_nid_postfix(long double x, long double y, int* quotient) { + if (!std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == 0.0L)) errno = EDOM; + int bits = 0; + const long double result = std::remquo(x, y, &bits); + if (!std::isnan(result)) *quotient = bits < 0 ? -(-bits & 7) : (bits & 7); + return result; +} + +long double APS5_VABI logbl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::logb(x); +} + +int APS5_VABI ilogbl_nid_postfix(long double x) { + if ((Bits(x).signExponent & 0x7fff) == 0x7fff) return INT_MAX; + ErrnoGuard guard; + return std::ilogb(x); +} + +long double APS5_VABI rintl_nid_postfix(long double x) { return std::rint(x); } +long double APS5_VABI roundl_nid_postfix(long double x) { return std::isfinite(x) ? std::round(x) : x; } + +long APS5_VABI lroundl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::lround(x); +} + +long APS5_VABI lrintl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::lrint(x); +} + +long long APS5_VABI llrintl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::llrint(x); +} + +long double APS5_VABI sqrtl_nid_postfix(long double x) { + return std::sqrt(x); +} + +long double APS5_VABI modfl_nid_postfix(long double x, long double* integral) { + if (std::isnan(x)) { + *integral = x; + return x; + } + return std::modf(x, integral); +} +long double APS5_VABI nanl_nid_postfix(const char*) { return std::numeric_limits::quiet_NaN(); } + +long double APS5_VABI scalblnl_nid_postfix(long double x, long exponent) { + ErrnoGuard guard; + return std::scalbn(x, static_cast(exponent)); +} + +long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double z) { + if (x == 0.0L || y == 0.0L) return x * y + z; + if (z == 0.0L) return x * y; + if (!std::isfinite(x) || !std::isfinite(y)) return x * y + z; + if (!std::isfinite(z)) return z; + int xExponent = 0, yExponent = 0, zExponent = 0; + std::frexp(x, &xExponent); + std::frexp(y, &yExponent); + std::frexp(z, &zExponent); + if (xExponent + yExponent - zExponent > 2 * std::numeric_limits::digits) return x * y; + ErrnoGuard guard; + return std::fma(x, y, z); +} + +double APS5_VABI rint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::rint(x); } +float APS5_VABI rintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::rint(x); } + +long APS5_VABI lrint_nid_postfix(double x) { + ErrnoGuard guard; + return std::lrint(x); +} + +long APS5_VABI lrintf_nid_postfix(float x) { + ErrnoGuard guard; + return std::lrint(x); +} + +long long APS5_VABI llrint_nid_postfix(double x) { + ErrnoGuard guard; + return std::llrint(x); +} + +long long APS5_VABI llrintf_nid_postfix(float x) { + ErrnoGuard guard; + return std::llrint(x); +} + +double APS5_VABI drem_nid_postfix(double x, double y) { + return std::remainder(x, y); +} + +float APS5_VABI dremf_nid_postfix(float x, float y) { + return std::remainder(x, y); +} + +double APS5_VABI scalbln_nid_postfix(double x, long exponent) { + ErrnoGuard guard; + return std::scalbln(x, exponent); +} + +float APS5_VABI scalblnf_nid_postfix(float x, long exponent) { + ErrnoGuard guard; + return std::scalbln(x, exponent); +} + +double APS5_VABI scalb_nid_postfix(double x, double exponent) { + ErrnoGuard guard; + if (std::isnan(x) || std::isnan(exponent)) return x * exponent; + if (std::isinf(exponent)) return exponent > 0 ? x * exponent : x / -exponent; + if (std::rint(exponent) != exponent) return (exponent - exponent) / (exponent - exponent); + if (exponent > 65000.0) return std::scalbn(x, 65000); + if (exponent < -65000.0) return std::scalbn(x, -65000); + return std::scalbn(x, static_cast(exponent)); +} + +float APS5_VABI scalbf_nid_postfix(float x, float exponent) { + ErrnoGuard guard; + if (std::isnan(x) || std::isnan(exponent)) return x * exponent; + if (std::isinf(exponent)) return exponent > 0 ? x * exponent : x / -exponent; + if (std::rint(exponent) != exponent) return (exponent - exponent) / (exponent - exponent); + if (exponent > 65000.0f) return std::scalbn(x, 65000); + if (exponent < -65000.0f) return std::scalbn(x, -65000); + return std::scalbn(x, static_cast(exponent)); +} + +int APS5_VABI finite_nid_postfix(double x) { return std::isfinite(x) ? 1 : 0; } +int APS5_VABI finitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0; } + +int APS5_VABI __fpclassifyl_nid_postfix(long double x) { + const ExtendedBits bits = Bits(x); + if ((bits.signExponent & 0x7fff) != 0 || bits.mantissa == 0) return TestExtended(bits); + return Denormal; +} +int APS5_VABI __isinfl_nid_postfix(long double x) { return TestExtended(Bits(x)) == Infinite ? 1 : 0; } +int APS5_VABI __isnanl_nid_postfix(long double x) { return TestExtended(Bits(x)) == NotANumber ? 1 : 0; } + +int APS5_VABI __isnormall_nid_postfix(long double x) { + const int exponent = Bits(x).signExponent & 0x7fff; + return exponent != 0 && exponent != 0x7fff ? 1 : 0; +} + +short APS5_VABI _LDtest_nid_postfix(const long double* value) { return TestExtended(Bits(*value)); } +int APS5_VABI _Dclass_nid_postfix(double x) { return TestFloat(x); } +int APS5_VABI _FDclass_nid_postfix(float x) { return TestFloat(x); } +int APS5_VABI _LDclass_nid_postfix(long double x) { return TestExtended(Bits(x)); } +short APS5_VABI _LDsign_nid_postfix(long double x) { return static_cast(Bits(x).signExponent & 0x8000); } + +} diff --git a/core/libs/tests/GuestMathExact.cpp b/core/libs/tests/GuestMathExact.cpp new file mode 100644 index 000000000000..b15cca64958b --- /dev/null +++ b/core/libs/tests/GuestMathExact.cpp @@ -0,0 +1,101 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +long double APS5_VABI fdiml_nid_postfix(long double, long double); +long double APS5_VABI fmodl_nid_postfix(long double, long double); +long double APS5_VABI remquol_nid_postfix(long double, long double, int*); +int APS5_VABI ilogbl_nid_postfix(long double); +long double APS5_VABI roundl_nid_postfix(long double); +long double APS5_VABI modfl_nid_postfix(long double, long double*); +long double APS5_VABI nanl_nid_postfix(const char*); +long double APS5_VABI scalblnl_nid_postfix(long double, long); +long double APS5_VABI fmal_nid_postfix(long double, long double, long double); +double APS5_VABI scalb_nid_postfix(double, double); +double APS5_VABI drem_nid_postfix(double, double); +long APS5_VABI lrint_nid_postfix(double); +int APS5_VABI finite_nid_postfix(double); +int APS5_VABI __fpclassifyl_nid_postfix(long double); +int APS5_VABI __isnanl_nid_postfix(long double); +int APS5_VABI __isnormall_nid_postfix(long double); +short APS5_VABI _LDtest_nid_postfix(const long double*); +int APS5_VABI _Dclass_nid_postfix(double); +short APS5_VABI _LDsign_nid_postfix(long double); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static long double Extended(std::uint16_t signExponent, std::uint64_t mantissa) { + long double value = 0; + unsigned char bytes[sizeof(long double)] = {}; + std::memcpy(bytes, &mantissa, sizeof(mantissa)); + std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); + std::memcpy(&value, bytes, sizeof(bytes)); + return value; +} + +static bool SameBits(long double left, long double right) { return std::memcmp(&left, &right, 10) == 0; } + +static void CheckSignalingNanPassThrough() { + const long double signaling = Extended(0x7fff, 0x8000000000001234ull); + Require(SameBits(fdiml_nid_postfix(signaling, 1.0L), signaling)); + Require(SameBits(fdiml_nid_postfix(1.0L, signaling), signaling)); + Require(SameBits(roundl_nid_postfix(signaling), signaling)); + long double integral = 0; + Require(SameBits(modfl_nid_postfix(signaling, &integral), signaling) && SameBits(integral, signaling)); + Require(SameBits(fmal_nid_postfix(1.0L, 1.0L, signaling), signaling)); + Require(SameBits(nanl_nid_postfix("123"), Extended(0x7fff, 0xc000000000000000ull))); +} + +static void CheckErrno() { + errno = 0; + Require(std::isnan(fmodl_nid_postfix(NAN, 1.0L)) && errno == EDOM); + errno = 0; + Require(fmodl_nid_postfix(7.0L, 2.0L) == 1.0L && errno == 0); + int quotient = 0; + errno = 0; + Require(std::isnan(remquol_nid_postfix(1.0L, 0.0L, "ient)) && errno == EDOM); + errno = 0; + remquol_nid_postfix(NAN, 0.0L, "ient); + Require(errno == 0); + Require(remquol_nid_postfix(-7.0L, 2.0L, "ient) == 1.0L && quotient == -4); + errno = 0; + Require(std::isnan(scalb_nid_postfix(1.0, 2.5)) && errno == 0); + Require(scalb_nid_postfix(1.0, 3.0) == 8.0 && scalb_nid_postfix(4.0, -INFINITY) == 0.0); + errno = 0; + Require(std::isnan(drem_nid_postfix(1.0, 0.0)) && errno == EDOM); + Require(drem_nid_postfix(7.0, 2.0) == -1.0 && lrint_nid_postfix(2.5) == 2); +} + +static void CheckConsoleQuirks() { + Require(ilogbl_nid_postfix(NAN) == INT_MAX && ilogbl_nid_postfix(0.0L) == INT_MIN && ilogbl_nid_postfix(8.0L) == 3); + Require(scalblnl_nid_postfix(1.0L, 0x100000001L) == 2.0L && scalblnl_nid_postfix(1.0L, LONG_MAX) == 0.5L); + const long double product = Extended(0x4000, 0xe000000000000000ull) * Extended(0x4001, 0xda76b022816f6a94ull); + Require(SameBits(fmal_nid_postfix(Extended(0x4000, 0xe000000000000000ull), Extended(0x4001, 0xda76b022816f6a94ull), Extended(0x9b45, 0xd3b49eac84283c95ull)), product)); + Require(finite_nid_postfix(1.0) == 1 && finite_nid_postfix(INFINITY) == 0); +} + +static void CheckClassification() { + const long double pseudoDenormal = Extended(0x0000, 0x8000000000000000ull); + Require(_LDtest_nid_postfix(&pseudoDenormal) == -1 && __fpclassifyl_nid_postfix(pseudoDenormal) == -2); + const long double zero = 0.0L, infinity = INFINITY, nan = NAN, denormal = Extended(0, 1); + Require(_LDtest_nid_postfix(&zero) == 0 && _LDtest_nid_postfix(&infinity) == 1 && _LDtest_nid_postfix(&nan) == 2 && _LDtest_nid_postfix(&denormal) == -2); + Require(__fpclassifyl_nid_postfix(1.0L) == -1 && __fpclassifyl_nid_postfix(0.0L) == 0 && __fpclassifyl_nid_postfix(INFINITY) == 1); + Require(__isnanl_nid_postfix(NAN) == 1 && __isnanl_nid_postfix(INFINITY) == 0); + Require(__isnormall_nid_postfix(1.0L) == 1 && __isnormall_nid_postfix(denormal) == 0); + Require(_Dclass_nid_postfix(5e-324) == -2 && _Dclass_nid_postfix(-0.0) == 0); + Require(_LDsign_nid_postfix(-2.0L) == -32768 && _LDsign_nid_postfix(2.0L) == 0); +} + +int main() { + CheckSignalingNanPassThrough(); + CheckErrno(); + CheckConsoleQuirks(); + CheckClassification(); + return 0; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index ea34395fa2c1..add29f73d4b8 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -59,6 +59,8 @@ - [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported - [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target. - [strtod, strtof, strtold](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `NAN(n-char-sequence)` converts to the quiet NaN that `nan` and `nanf` return, negated after a minus sign, and ignores the sequence as they ignore their tag. This is inferred from C17 7.12.11.2p2, which makes `nan("n")` equivalent to `strtod("NAN(n)", NULL)`, and not observed on the console. No console `nanl` was observed, so `strtold` returns the x87 default quiet NaN (exponent `0x7fff`, significand `0xc000000000000000`) +- [remquol](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) leaves `*quo` unchanged when the result is NaN; the `libc.prx` bundled with PPSA08427 writes a value that depends on earlier x87 state (`remquol(1, 0)` stored 0 on a first call and 4 on later ones) +- [Long double math in MathExact.cpp](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) was compared with the `libc.prx` bundled with PPSA08427 only for canonical 80-bit encodings; unnormals, pseudo-denormals and pseudo-infinities passed to arithmetic functions (not the classifiers) follow the host x87 instead of the console - [nan and nanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep. - [seed48 and lcong48](../../../core/libs/prx/libc/src/Random.cpp) (libSceLibcInternal, implemented in libc) follow the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x6F460 and 0x6F420): the multiplier and addend are shared by the whole `rand48` family, including `erand48`, `nrand48` and `jrand48` on a caller's seed; `lcong48` sets them with the seed, and `srand48` and `seed48` restore 0x5DEECE66D and 0xB. Unlike FreeBSD, `seed48` returns a static copy of the new seed rather than the previous one. A null argument, which the console dereferences, throws - [lgamma](../../../core/libs/prx/libc/src/Math.cpp) (libc) returns the host's `lgamma` but does not store the sign of Γ(x) in the guest's `signgam`, as FreeBSD 11's [`e_lgamma.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/e_lgamma.c) does through `__ieee754_lgamma_r`, because no guest `signgam` is exported. PPSA99004 imports it. Console behaviour is unverified From bb5aabf752770422785ba861dd55ac11d3122cc7 Mon Sep 17 00:00:00 2001 From: Nixitov Date: Wed, 14 Oct 2026 01:55:28 +0200 Subject: [PATCH 007/176] feat(libkernel_sys): implement fchown Morrowind imports fchown and stops at it once rtprio_thread and the pthread concurrency controls are in. Accept the guest's own owner and group (0) or unchanged (-1), fail an invalid descriptor with EBADF, and throw for another owner or group, since ownership is not modelled. (cherry picked from commit 443df0551be1b668473068b547f6fbc51cf40e13) --- core/libs/prx/libkernel_sys/Export.cpp | 27 ++++++++++++++++++++++++++ core/libs/tests/GuestKernelSys.cpp | 10 ++++++++++ docs/dev/debt/libkernel_sys.md | 1 + 3 files changed, 38 insertions(+) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 3a4e3f494f29..1bb5f28b6ef9 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -42,9 +43,22 @@ extern "C" int* APS5_VABI __error_nid_postfix(); namespace { +#ifdef _WIN32 +extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); +static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {} +static int GuestFstat(int fd) { + const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreInvalidParameter); + struct _stat64 status{}; + const int result = ::_fstat64(fd, &status); + _set_thread_local_invalid_parameter_handler(previous); + return result; +} +#endif + constexpr int GuestEnoent = 2; constexpr int GuestEperm = 1; constexpr int GuestEio = 5; +constexpr int GuestEbadf = 9; constexpr int GuestEfault = 14; constexpr int GuestEbadf = 9; constexpr int GuestEnotdir = 20; @@ -567,6 +581,19 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) return 0; } +int APS5_VABI fchown_nid_postfix(int fd, std::uint32_t uid, std::uint32_t gid) { + constexpr std::uint32_t Unchanged = 0xFFFFFFFFu; + if (uid != 0 && uid != Unchanged) NotImplemented_nid_no_patch("fchown: an owner other than the guest user"); + if (gid != 0 && gid != Unchanged) NotImplemented_nid_no_patch("fchown: a group other than the guest group"); +#ifdef _WIN32 + if (GuestFstat(fd) != 0) return static_cast(Fail(GuestEbadf)); +#else + struct stat status{}; + if (::fstat(fd, &status) != 0) return static_cast(Fail(GuestEbadf)); +#endif + return 0; +} + int APS5_VABI unlink_nid_postfix(const char* path); int APS5_VABI rmdir_nid_postfix(const char* path); diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 89c9b9350fef..e59d59d32a72 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -45,6 +45,7 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int, const char*, char*, std::size int APS5_VABI mkdirat_nid_postfix(int, const char*, std::uint16_t); int APS5_VABI renameat_nid_postfix(int, const char*, int, const char*); int APS5_VABI fchmodat_nid_postfix(int, const char*, int, int); +int APS5_VABI fchown_nid_postfix(int, std::uint32_t, std::uint32_t); std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t); int* APS5_VABI __error_nid_postfix(); @@ -535,6 +536,15 @@ int main() { Require(fchmodat_nid_postfix(base, "loop/x", 0600, 0) == -1 && Error() == 62); Require(fchmodat_nid_postfix(base, longName.c_str(), 0600, 0) == -1 && Error() == 63); #endif + Require(fchown_nid_postfix(base, 0, 0) == 0); + Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0); + Require(fchown_nid_postfix(-1, 0, 0) == -1 && Error() == 9); + bool fchownThrew = false; + try { fchown_nid_postfix(base, 1, 0); } catch (const std::exception&) { fchownThrew = true; } + Require(fchownThrew); + fchownThrew = false; + try { fchown_nid_postfix(base, 0, 1); } catch (const std::exception&) { fchownThrew = true; } + Require(fchownThrew); Require(sceKernelClose(base) == 0); #ifndef _WIN32 if (created == 0) { diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index b45c18af4c19..4590e03b1476 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -13,3 +13,4 @@ - [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) +- [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it From 9513950bcc4d0b80d39dd40033987e9595d8ad19 Mon Sep 17 00:00:00 2001 From: capmare Date: Thu, 15 Oct 2026 16:00:56 +0200 Subject: [PATCH 008/176] feat(libSceFontFt): implement the font format support exports (cherry picked from commit 2be7b0769a75a7d18ad1f02be65fd4e30c5b1475) --- core/libs/prx/libSceFontFt/Export.cpp | 63 +++++++++++++++++---------- core/libs/tests/GuestFont.cpp | 12 +++++ docs/dev/debt/libSceFontFt.md | 5 +++ 3 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 docs/dev/debt/libSceFontFt.md diff --git a/core/libs/prx/libSceFontFt/Export.cpp b/core/libs/prx/libSceFontFt/Export.cpp index d34a3ca5597d..0dcd8bcf9039 100644 --- a/core/libs/prx/libSceFontFt/Export.cpp +++ b/core/libs/prx/libSceFontFt/Export.cpp @@ -1,11 +1,41 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_MODULE_H + #include "prx/libSceFontFt/include/FontFtDriver.hpp" #include "prx/libc/include/General.hpp" #pragma GCC visibility push(default) +namespace { + +FT_Library ProbeLibrary() { + static const FT_Library library = [] { + FT_Library created = nullptr; + if (FT_Init_FreeType(&created) != 0) return static_cast(nullptr); + return created; + }(); + return library; +} + +int SupportFormat(const char* function, std::initializer_list modules) { + const FT_Library library = ProbeLibrary(); + if (library == nullptr) throw std::runtime_error(std::string(function) + ": FreeType failed to initialize"); + for (const char* module : modules) { + if (FT_Get_Module(library, module) == nullptr) throw std::runtime_error(std::string(function) + ": FreeType module " + module + " is not built in"); + } + return 0; +} + +} + extern "C" { const Font::SysDriver* APS5_VABI sceFontSelectLibraryFt(int value) { @@ -32,13 +62,11 @@ int APS5_VABI sceFontFtSetAliasPath() { } int APS5_VABI sceFontFtSupportBdf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"bdf"}); } int APS5_VABI sceFontFtSupportCid() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"t1cid"}); } int APS5_VABI sceFontFtSupportFontFormats() { @@ -47,28 +75,23 @@ int APS5_VABI sceFontFtSupportFontFormats() { } int APS5_VABI sceFontFtSupportOpenType() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"cff", "truetype"}); } int APS5_VABI sceFontFtSupportOpenTypeOtf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"cff"}); } int APS5_VABI sceFontFtSupportOpenTypeTtf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"truetype"}); } int APS5_VABI sceFontFtSupportPcf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"pcf"}); } int APS5_VABI sceFontFtSupportPfr() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"pfr"}); } int APS5_VABI sceFontFtSupportSystemFonts() { @@ -77,8 +100,7 @@ int APS5_VABI sceFontFtSupportSystemFonts() { } int APS5_VABI sceFontFtSupportTrueType() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"truetype"}); } int APS5_VABI sceFontFtSupportTrueTypeGx() { @@ -87,18 +109,15 @@ int APS5_VABI sceFontFtSupportTrueTypeGx() { } int APS5_VABI sceFontFtSupportType1() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"type1"}); } int APS5_VABI sceFontFtSupportType42() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"type42"}); } int APS5_VABI sceFontFtSupportWinFonts() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"winfonts"}); } int APS5_VABI sceFontFtTermAliases() { diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index f65a313d4070..a43268ca121c 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -59,6 +59,17 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting*); FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting*, const FontWritingLetterStep**); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); +int APS5_VABI sceFontFtSupportBdf(); +int APS5_VABI sceFontFtSupportCid(); +int APS5_VABI sceFontFtSupportOpenType(); +int APS5_VABI sceFontFtSupportOpenTypeOtf(); +int APS5_VABI sceFontFtSupportOpenTypeTtf(); +int APS5_VABI sceFontFtSupportPcf(); +int APS5_VABI sceFontFtSupportPfr(); +int APS5_VABI sceFontFtSupportTrueType(); +int APS5_VABI sceFontFtSupportType1(); +int APS5_VABI sceFontFtSupportType42(); +int APS5_VABI sceFontFtSupportWinFonts(); int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter*, int*); FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter*, const FontTextCharacter*, FontTextCodes*); FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes*); @@ -229,6 +240,7 @@ int main() { Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); Require(sceFontSelectLibraryFt(0) != nullptr && sceFontSelectLibraryFt(1) == nullptr); Require(sceFontSelectRendererFt(0) != nullptr && sceFontSelectRendererFt(1) == nullptr); + Require(sceFontFtSupportBdf() == 0 && sceFontFtSupportCid() == 0 && sceFontFtSupportOpenType() == 0 && sceFontFtSupportOpenTypeOtf() == 0 && sceFontFtSupportOpenTypeTtf() == 0 && sceFontFtSupportPcf() == 0 && sceFontFtSupportPfr() == 0 && sceFontFtSupportTrueType() == 0 && sceFontFtSupportType1() == 0 && sceFontFtSupportType42() == 0 && sceFontFtSupportWinFonts() == 0); FontLibrary library = nullptr; Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); diff --git a/docs/dev/debt/libSceFontFt.md b/docs/dev/debt/libSceFontFt.md new file mode 100644 index 000000000000..618ebd832261 --- /dev/null +++ b/docs/dev/debt/libSceFontFt.md @@ -0,0 +1,5 @@ +# libSceFontFt technical debt + +### Unknown function info + +- [sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportTrueType, sceFontFtSupportType1, sceFontFtSupportType42 and sceFontFtSupportWinFonts](../../../core/libs/prx/libSceFontFt/Export.cpp) (libSceFontFt) are declared without parameters, signatures unknown; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/fontft.cpp), another emulator, declares them the same way and only logs. They are assumed to enable one font format in the FontFt library, which here always registers FreeType's default modules, so each call returns 0 when the host FreeType has the module (`bdf`, `t1cid`, `truetype`, `cff`, `pcf`, `pfr`, `type1`, `type42`, `winfonts`; OpenType needs `cff` and `truetype`) and throws when it does not. The mapping of Sony's format names to FreeType modules, the return value and the error codes of the console are unverified, and no title is known to call them. From 388925da985f1d947bb1223aff2017ba3ad847e8 Mon Sep 17 00:00:00 2001 From: Zenodeon Date: Thu, 15 Oct 2026 15:49:43 +0400 Subject: [PATCH 009/176] fix(agc): preserve registered array slices on CPU write-back (cherry picked from commit 540c3a10c670bdb9d06723edf6f0a7b8d938e77a) --- .../libSceAgcDriver/Graphics/src/Texture.cpp | 15 +++++- .../tests/execution/ArrayLayerTarget.cpp | 54 +++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 0461810da7f3..73dd489bfca8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -4441,7 +4441,8 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { overhang = covered >= end; } } - if (unregisteredDrop && !overhang && !RegisteredReadableCovers(descriptor.baseAddress, static_cast(guestBytes))) { + const bool registered = std::all_of(keep.begin(), keep.end(), [](const auto& range) { return RegisteredReadableCovers(range.first, static_cast(range.second - range.first)); }); + if (unregisteredDrop && !overhang && !registered) { static std::atomic reports{0}; if (reports.fetch_add(1) < 4) std::fprintf(stderr, "[gpu] storage image 0x%llx+0x%llx: memory no longer registered; %zu pending ranges dropped\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), keep.size()); unregisteredDropped.fetch_add(1, std::memory_order_relaxed); @@ -4457,7 +4458,17 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // stale unit (dropped, or kept for the CPU) still differs from them, so `original` vouches for // the content again only where it did before, or once every unit is stored. const bool wasValid = originalValid; - if (!wasValid) GuestMemory::ReadCommitted(descriptor.baseAddress, original); + if (!wasValid) { + if (registered) { + GuestMemory::ReadCommitted(descriptor.baseAddress, original); + } else { + for (const auto& [from, to] : keep) { + const auto first = std::max(descriptor.baseAddress, from); + const auto last = std::min(descriptor.baseAddress + guestBytes, to); + if (first < last) GuestMemory::ReadCommitted(first, std::span(original).subspan(static_cast(first - descriptor.baseAddress), static_cast(last - first))); + } + } + } DeviceBuffer linear(context, static_cast(sliceLinearBytes * arrayLayers), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); DeviceBuffer tiled(context, original.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); Buffer host(context, original.size(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp index 18dcef360cc5..f9e388295ebc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp @@ -81,7 +81,7 @@ std::string Hex(std::uint32_t value) { class GuestBlock { public: - GuestBlock() { + explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0) : registeredOffset(registeredOffset) { #ifdef _WIN32 block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, LayerBytes)); if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, SurfaceBytes, PAGE_READWRITE, SurfaceBytes); @@ -97,11 +97,11 @@ class GuestBlock { } #endif Require(block != nullptr, "array layer target: cannot allocate the guest block"); - GuestAllocations::Mutation().Add(block, SurfaceBytes, true, true, true); + GuestAllocations::Mutation().Add(block + registeredOffset, registeredBytes, true, true, true); } ~GuestBlock() { - GuestAllocations::Mutation().Remove(block); + if (registered) GuestAllocations::Mutation().Remove(block + registeredOffset); #ifdef _WIN32 GuestArena::GuestArenaReset_nid_postfix(block, SurfaceBytes); GuestArena::GuestArenaRelease_nid_postfix(block, SurfaceBytes); @@ -115,9 +115,15 @@ class GuestBlock { GuestBlock& operator=(const GuestBlock&) = delete; std::uint8_t* Data() { return block; } + void Unregister() { + GuestAllocations::Mutation().Remove(block + registeredOffset); + registered = false; + } private: std::uint8_t* block = nullptr; + std::size_t registeredOffset = 0; + bool registered = true; }; std::array SurfaceDescriptor(std::uint64_t address, std::uint32_t swizzleMode, std::uint32_t type, std::uint32_t lastLayer) { @@ -309,6 +315,45 @@ void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std: AgcDriver::Graphics::ClearCachedTextures(device.Device()); } +void UnregisteredUnusedLayer(AgcDriver::VulkanDevice& device, std::uint32_t slice) { + GuestBlock block(LayerBytes, slice * LayerBytes); + auto* texels = block.Data(); + const auto surface = AddressOf(texels); + const auto active = surface + slice * LayerBytes; + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, slice, TileR64KBX, false); + Require(StorageTexture::FindPending(surface, SurfaceBytes) != nullptr, "the registered slice was not pending before its CPU write-back"); + StorageTexture::FlushPending(active, LayerBytes, nullptr, "test"); + device.WaitIdle(); + const auto* first = reinterpret_cast(active); + const auto* last = reinterpret_cast(active + LayerBytes); + const auto unchanged = [](std::uint32_t value) { return value == Initial; }; + const auto drawn = std::count(first, last, std::bit_cast(Drawn)); + const bool untouched = std::all_of(reinterpret_cast(texels), first, unchanged) && std::all_of(last, reinterpret_cast(texels + SurfaceBytes), unchanged); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + Require(drawn == ScissorWidth * Side, "CPU write-back discarded the registered slice because an unused array slice was unregistered"); + Require(untouched, "CPU write-back modified an unregistered unused slice"); +} + +void RetiredArrayLayer(AgcDriver::VulkanDevice& device) { + GuestBlock block(LayerBytes); + auto* texels = block.Data(); + const auto surface = AddressOf(texels); + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, 0u, TileR64KBX, false); + Require(StorageTexture::FindPending(surface, SurfaceBytes) != nullptr, "the retired slice was not pending before it was unregistered"); + block.Unregister(); + StorageTexture::FlushPending(surface, LayerBytes, nullptr, "test"); + device.WaitIdle(); + const bool untouched = std::all_of(reinterpret_cast(texels), reinterpret_cast(texels + SurfaceBytes), [](std::uint32_t value) { return value == Initial; }); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + Require(untouched, "CPU write-back stored results over a retired allocation"); +} + } int main() { @@ -319,6 +364,9 @@ int main() { Run(*device, block.Data(), TileR64KBX); Run(*device, block.Data(), static_cast(AgcDriver::Graphics::ColorTileMode::D64KBX)); for (std::uint32_t slice = 1; slice < Layers; ++slice) Run(*device, block.Data(), TileR64KBX, true, slice); + UnregisteredUnusedLayer(*device, 0u); + UnregisteredUnusedLayer(*device, Layers - 1u); + RetiredArrayLayer(*device); std::puts("array layer target tests passed"); return 0; } catch (const std::exception& error) { From 27e7a1386f41f3effbe3b58d16ee1093bcfcf8ed Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Thu, 15 Oct 2026 07:42:13 +0000 Subject: [PATCH 010/176] fix(agc): stamp the watched blocks of a partly watched store MarkWritten and StoreOwnBytes return 0 for a range that is not wholly watched, and they stamped none of its blocks. The watched blocks such a store wrote kept their old stamps, so UnchangedSince and StoredOver over them still read as unchanged after a GPU write that crossed into an unwatched import. A vouched shader table column or a cached range then kept bytes that had been replaced. Both now stamp each block whose part of the store starts or ends in watched memory, as the wholly watched path stamps every block. They still return 0 and note the store as unstamped. This costs one or two coverage lookups per block, and only on this path. (cherry picked from commit c886c28d1784fcfca1327b4bad7d7de68f7fe731) --- .../Execution/src/GuestMemory.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 275901bb9ae8..cc3747747a14 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -883,6 +883,23 @@ struct WriteTracker { } } + void stampWatchedPart(std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration) { +#ifdef _WIN32 + address = std::max(address, base); + end = std::min(end, base + size); +#else + end = std::min(end, LeafCount * LeafBlocks * WriteBlockBytes); +#endif + if (address >= end) return; + for (auto block = blockOf(address); block <= blockOf(end - 1); ++block) { + const auto first = std::max(address, blockBegin(block)); + const auto stop = std::min(end, blockBegin(block) + WriteBlockBytes); + if (!covers(first, 1) && !covers(stop - 1, 1)) continue; + stamp(block, stampGeneration, StampKind::Driver); + noteDriverStore(block, address, end, stampGeneration, false); + } + } + void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration, bool complete) { const auto begin = blockBegin(block); const auto from = static_cast(std::max(address, begin) - begin); @@ -1390,6 +1407,7 @@ std::uint64_t storeOwn(std::uint64_t address, std::size_t bytes, const std::func ++tracker.generation; if (!tracker.watched || !tracker.covers(stored.first, static_cast(stored.second - stored.first))) { tracker.noteUnstampedStore(stored.first, stored.second); + if (tracker.watched) tracker.stampWatchedPart(stored.first, stored.second, tracker.generation); return 0; } for (auto block = tracker.blockOf(stored.first); block <= tracker.blockOf(stored.second - 1); ++block) { @@ -1438,6 +1456,7 @@ static std::uint64_t markWritten(std::uint64_t address, std::size_t bytes, bool ++tracker.generation; if (!tracker.watched || !tracker.covers(address, bytes)) { tracker.noteUnstampedStore(address, address + bytes); + if (tracker.watched) tracker.stampWatchedPart(address, address + bytes, tracker.generation); return 0; } #ifndef _WIN32 From a9d746c5cb819a40c8afdcd289ea717e3e54f3c1 Mon Sep 17 00:00:00 2001 From: thuanquang <60096168+thuanquang@users.noreply.github.com> Date: Mon, 12 Oct 2026 23:19:00 +0700 Subject: [PATCH 011/176] fix(relinker): list converted modules correctly for a relative output path The runtime layout printed ../app0/sce_module/... under app0/ when the output path contained . or .. components (./out.elf), because the module paths are normal and the output directory was not. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit f40acdb2c625f16472cf013283822478581a728e) --- core/relinker/main.cpp | 2 +- core/relinker/relinker/tests/test_guest_needed_modules.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 917a1f4f063a..f8fe75b960ff 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -313,7 +313,7 @@ int Run(const int argc, char* argv[]) { << Io::Utf8Path(Io::NativePath(absPath).filename()) << "\n" << "libs/\n *.prx\napp0/\n \n"; for (const auto& artifact : guestArtifacts) { - std::string relative = Io::Utf8Path(artifact.Path.lexically_relative(Io::NativePath(absPath).parent_path() / "app0")); + std::string relative = Io::Utf8Path(artifact.Path.lexically_relative(Io::NativePath(absPath).parent_path().lexically_normal() / "app0")); std::replace(relative.begin(), relative.end(), '\\', '/'); std::cout << " " << relative << '\n'; } diff --git a/core/relinker/relinker/tests/test_guest_needed_modules.py b/core/relinker/relinker/tests/test_guest_needed_modules.py index d15b96ac99a2..5af5c7c7ecc0 100644 --- a/core/relinker/relinker/tests/test_guest_needed_modules.py +++ b/core/relinker/relinker/tests/test_guest_needed_modules.py @@ -114,7 +114,12 @@ def convert(case, windows, needed=NEEDED, repeats=1): artifact = case / "app0" / "sce_module" / "needed.prx.guest.prx" assert artifact.read_bytes().startswith(b"MZ" if windows else b"\x7fELF"), artifact assert list((case / "app0").rglob("*.guest.prx")) == [artifact] - assert " sce_module/needed.prx.guest.prx\n" in result.stdout, result.stdout + layout = " sce_module/needed.prx.guest.prx\n" + assert layout in result.stdout, result.stdout + for spelling in (os.path.join(".", output.name), os.path.join("..", case.name, output.name)): + relative = subprocess.run([str(relinker), *(["--windows"] if windows else []), "input.elf", spelling], cwd=case, + capture_output=True, text=True, timeout=30) + assert relative.returncode == 0 and layout in relative.stdout, (spelling, relative.stdout, relative.stderr) if windows and os.name == "nt": run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30) assert run.returncode == 42, (run.returncode, run.stdout, run.stderr) From 71ff265a7412fdda8844df2c0c59a799c324f9cd Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Thu, 15 Oct 2026 07:42:13 +0000 Subject: [PATCH 012/176] test(agc): check the stamps of a store into partly watched memory A driver store and an own store across a watched block and an unwatched one must stamp the watched block and no other. On Linux, a store across the halves of a split block must stamp it, and a store into its unwatched half alone must not. (cherry picked from commit a0dc9d374f9bca34d8e4c39a6d73974bf22ba894) --- .../libSceAgcDriver/tests/WriteTracking.cpp | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp index 56bfe785d0a4..39498e9d8ed2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp @@ -290,6 +290,39 @@ void CheckNewestStamp() { Require(NewestStamp(base + 3 * Block - 8, Block + 8) > partial, "a CPU store into the watched part of a partly watched range did not advance its stamp"); } +void CheckPartlyWatchedStore() { + void* memory = AllocateWatched(3 * Block); + const auto base = reinterpret_cast(memory); + const auto stored = base + Block; + const auto unwatched = base + 2 * Block; + std::memset(memory, 0x11, 3 * Block); + BumpCollectEpoch(); + Unwatch(unwatched, Block); + const auto before = CollectWrites(base, 2 * Block); + Require(before != 0 && UnchangedSince(base, 2 * Block, before), "the watched blocks were not collected"); + Require(MarkWritten(unwatched - 64, 128) == 0, "a driver store into partly watched memory is trusted"); + Require(!UnchangedSince(stored, Block, before), "a driver store into partly watched memory left its watched block unchanged"); + Require(StoredOver(unwatched - 64, 64, before), "a driver store into partly watched memory is not seen over its watched bytes"); + Require(!StoredOver(stored, Block - 64, before), "a driver store into partly watched memory is seen over watched bytes it did not store"); + Require(UnchangedSince(base, Block, before), "a driver store into partly watched memory stamped a block it did not store into"); + const auto beforeOwn = TrackerGeneration(); + Require(StoreOwnBytes(unwatched - 64, 128, [&] { std::memset(reinterpret_cast(unwatched - 64), 0x22, 128); }) == 0, "an own store into partly watched memory is trusted"); + Require(!UnchangedSince(stored, Block, beforeOwn), "an own store into partly watched memory left its watched block unchanged"); + Require(UnchangedSince(base, Block, beforeOwn), "an own store into partly watched memory stamped a block it did not store into"); +#ifndef _WIN32 + void* split = AllocateWatched(Block); + const auto splitBase = reinterpret_cast(split); + const auto watchedHalf = splitBase + Block / 2; + std::memset(split, 0x11, Block); + Unwatch(splitBase, Block / 2); + const auto beforeSplit = CollectWrites(watchedHalf, Block / 2); + Require(beforeSplit != 0 && UnchangedSince(watchedHalf, Block / 2, beforeSplit), "the watched half of a split block was not collected"); + Require(MarkWritten(splitBase + 64, 128) == 0 && UnchangedSince(watchedHalf, Block / 2, beforeSplit), "a driver store into the unwatched half of a split block stamped its watched half"); + Require(MarkWritten(watchedHalf - 64, 128) == 0, "a driver store across the halves of a split block is trusted"); + Require(!UnchangedSince(watchedHalf, Block / 2, beforeSplit), "a driver store across the halves of a split block left its watched half unchanged"); +#endif +} + #ifdef _WIN32 void CheckPrivateMappingReuse() { void* memory = AllocateWatched(3 * Block); @@ -450,6 +483,7 @@ int main() { CheckOwnStore(); CheckUnwatch(); CheckNewestStamp(); + CheckPartlyWatchedStore(); #ifdef _WIN32 CheckPrivateMappingReuse(); #endif From 2561b5f38adf6a02529a40ddc0b8056f845bccf4 Mon Sep 17 00:00:00 2001 From: jweidenh <38249793+jweidenh@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:35:35 -0400 Subject: [PATCH 013/176] fix(relinker): accept executables without a RELA table (cherry picked from commit f40d22067e843c8bf6496250d3e10d9c677f2288) --- core/relinker/CMakeLists.txt | 2 + .../elfpatcher/general/ElfConstants.hpp | 6 + .../src/pipeline/RelinkerPipeline.cpp | 22 +++- .../relinker/tests/test_optional_rela.py | 123 ++++++++++++++++++ 4 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 core/relinker/relinker/tests/test_optional_rela.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 36caaaf14152..bafc25396d7d 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -405,6 +405,8 @@ if(BUILD_TESTING) set_tests_properties(guest_relative_section_symbols PROPERTIES TIMEOUT 20) add_test(NAME optional_plt COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) + add_test(NAME optional_rela COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_rela.py $) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) set(tlsCoverageGroups metadata padding register_loads displacement_bounds alu_execution register_address_loads moved_branch rejected) diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index b809478033f4..0dbc39b29fe7 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -91,6 +91,9 @@ inline constexpr std::int64_t DT_RELASZ = 8; inline constexpr std::int64_t DT_RELAENT = 9; inline constexpr std::int64_t DT_STRSZ = 10; inline constexpr std::int64_t DT_SYMENT = 11; +inline constexpr std::int64_t DT_REL = 17; +inline constexpr std::int64_t DT_RELSZ = 18; +inline constexpr std::int64_t DT_RELENT = 19; inline constexpr std::int64_t DT_PLTREL = 20; inline constexpr std::int64_t DT_JMPREL = 23; inline constexpr std::int64_t DT_INIT = 12; @@ -103,6 +106,9 @@ inline constexpr std::int64_t DT_RUNPATH = 29; inline constexpr std::int64_t DT_FLAGS = 30; inline constexpr std::int64_t DT_PREINIT_ARRAY = 32; inline constexpr std::int64_t DT_PREINIT_ARRAYSZ = 33; +inline constexpr std::int64_t DT_RELRSZ = 35; +inline constexpr std::int64_t DT_RELR = 36; +inline constexpr std::int64_t DT_RELRENT = 37; inline constexpr std::uint64_t DF_BIND_NOW = 0x8; inline constexpr std::uint64_t DF_SYMBOLIC = 0x2; diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index e799b5e0fb64..949978dc793f 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -172,11 +172,25 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf } const ByteCount dynJmpRelSize = gotSize; - const FileByteOffset dynRelaOffset = readAsOffset(DT_OS_RELA, DT_RELA, "DT_RELA"); - const ByteCount dynRelaSize = readAsSize(DT_OS_RELASZ, DT_RELASZ, "DT_RELASZ"); + if (hasTag(DT_REL) || hasTag(DT_RELSZ) || hasTag(DT_RELENT)) + throw RelinkerException("DT_REL relocations are not supported"); + if (hasTag(DT_RELR) || hasTag(DT_RELRSZ) || hasTag(DT_RELRENT)) + throw RelinkerException("DT_RELR relocations are not supported"); + + const bool hasRelaTable = hasTag(DT_OS_RELA) || hasTag(DT_RELA) + || hasTag(DT_OS_RELASZ) || hasTag(DT_RELASZ); + FileByteOffset dynRelaOffset = 0; + ByteCount dynRelaSize = 0; + if (hasRelaTable) { + dynRelaOffset = readAsOffset(DT_OS_RELA, DT_RELA, "DT_RELA"); + dynRelaSize = readAsSize(DT_OS_RELASZ, DT_RELASZ, "DT_RELASZ"); + } constexpr std::size_t relaEntSize = 24; - if (readAsSize(DT_OS_RELAENT, DT_RELAENT, "DT_RELAENT") != relaEntSize) - throw RelinkerException("Unsupported DT_RELAENT value"); + if (hasRelaTable || hasTag(DT_OS_RELAENT) || hasTag(DT_RELAENT)) { + const auto relocationEntrySize = readAsSize(DT_OS_RELAENT, DT_RELAENT, "DT_RELAENT"); + if (relocationEntrySize != relaEntSize) + throw RelinkerException("Unsupported DT_RELAENT value: " + std::to_string(relocationEntrySize) + "; expected 24 bytes"); + } if (dynRelaSize % relaEntSize != 0) throw RelinkerException("Invalid DT_RELASZ value"); diff --git a/core/relinker/relinker/tests/test_optional_rela.py b/core/relinker/relinker/tests/test_optional_rela.py new file mode 100644 index 000000000000..0f3345dcc4d9 --- /dev/null +++ b/core/relinker/relinker/tests/test_optional_rela.py @@ -0,0 +1,123 @@ +import json +import os +from pathlib import Path +import platform +import signal +import struct +import subprocess +import sys +import tempfile + +from test_dynamic_segment import fixture as base_fixture +from test_linux_dynamic_strings import dynamic_strings, file_offset +from test_linux_load_alignment import loads +from test_windows_import_modules import provider + + +SCE_TAGS = {2: 0x6100002D, 3: 0x61000027, 5: 0x61000035, 6: 0x61000039, + 7: 0x6100002F, 8: 0x61000031, 9: 0x61000033, 10: 0x61000037, + 11: 0x6100003B, 20: 0x6100002B, 23: 0x61000029} +TARGETS = (("linux", [], b"\x7fELF"), ("windows", ["--windows"], b"MZ"), + ("macos", ["--macos"], b"\xcf\xfa\xed\xfe")) + + +def fixture(sce, relocation_tags=(), plt=False, relative=False, extra_tags=()): + image = base_fixture() + strings = b"\0module.prx\0shared\0" if plt else b"\0" + image[0x600:0x600 + len(strings)] = strings + image[0x200:0x206] = bytes.fromhex("ff25fa000000" if plt or relative else "b82a000000c3") + tags = [(5, 0x600), (10, len(strings)), (6, 0x620), (11, 24), (4, 0x680), *relocation_tags] + struct.pack_into(" Date: Thu, 15 Oct 2026 10:41:30 -0400 Subject: [PATCH 014/176] test(relinker): page-align the optional RELA guest fixture (cherry picked from commit 73ab57d57ef8dab738fb42701670f504dbbc106a) --- .../relinker/tests/test_optional_rela.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/relinker/relinker/tests/test_optional_rela.py b/core/relinker/relinker/tests/test_optional_rela.py index 0f3345dcc4d9..be8870b3ecd5 100644 --- a/core/relinker/relinker/tests/test_optional_rela.py +++ b/core/relinker/relinker/tests/test_optional_rela.py @@ -21,6 +21,19 @@ ("macos", ["--macos"], b"\xcf\xfa\xed\xfe")) +def guest_provider(code=None): + image = provider(42, code=code) + aligned = bytearray(0x3000) + aligned[:0x400] = image[:0x400] + aligned[0x1000:0x1100] = image[0x400:0x500] + aligned[0x2000:0x2400] = image[0x600:0xA00] + for header, offset in ((64, 0x1000), (120, 0x2000), (176, 0x2000)): + struct.pack_into(" Date: Fri, 16 Oct 2026 11:43:27 +0200 Subject: [PATCH 015/176] fix(shader): flush f64 min and max inputs by float mode (cherry picked from commit 5356078ec1c3352c9feb296562f9c0bed00292c8) --- .../tests/execution/F64Arithmetic.cpp | 85 +++++++++++++++++-- .../Translation/TranslationContext.hpp | 2 +- .../Translation/src/Float64Instructions.cpp | 8 +- .../Translation/src/VectorInstructions.cpp | 4 +- docs/dev/debt/libSceAgcDriver.md | 2 +- 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp index 38ff1defd32e..520e49b4a901 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -154,15 +155,13 @@ void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, con Require(actual == expected, std::string(Ieee ? "f64 arithmetic, IEEE mode: lane " : "f64 arithmetic: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } -void Run(AgcDriver::VulkanDevice& device) { - for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); +void Run(AgcDriver::VulkanDevice& device, std::span code, std::optional mode) { Output.fill(0xdeadbeefu); std::vector userData(8, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); - const std::span code(Code); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; ShaderRecompiler::RecompileRequest request{ @@ -172,7 +171,7 @@ void Run(AgcDriver::VulkanDevice& device) { {0, 0, 0, 128} }; request.useCache = false; - if (Ieee) request.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}; + request.context.floatMode = mode; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); @@ -190,6 +189,80 @@ void Check() { } } +std::uint64_t MinMaxReference(std::uint64_t lhs, std::uint64_t rhs, bool maximum, bool ieee) { + constexpr std::uint64_t magnitude = 0x7fffffffffffffffull; + constexpr std::uint64_t infinity = 0x7ff0000000000000ull; + constexpr std::uint64_t quiet = 0x0008000000000000ull; + const bool lhsNan = (lhs & magnitude) > infinity; + const bool rhsNan = (rhs & magnitude) > infinity; + if (ieee && lhsNan && (lhs & quiet) == 0u) return lhs | quiet; + if (ieee && rhsNan && (rhs & quiet) == 0u) return rhs | quiet; + if (rhsNan) return lhs; + if (lhsNan) return rhs; + if ((lhs & magnitude) == 0u && (rhs & magnitude) == 0u) return maximum ? lhs & rhs : lhs | rhs; + return maximum ? (std::bit_cast(lhs) > std::bit_cast(rhs) ? lhs : rhs) : (std::bit_cast(lhs) < std::bit_cast(rhs) ? lhs : rhs); +} + +void CheckMinMaxDenormals(AgcDriver::VulkanDevice& device) { + constexpr std::array, Threads> rows{{ + {0x0000000000000001ull, 0x0000000000000000ull}, {0x0000000000000000ull, 0x0000000000000001ull}, + {0x8000000000000001ull, 0x0000000000000000ull}, {0x0000000000000000ull, 0x8000000000000001ull}, + {0x0000000000000001ull, 0x8000000000000001ull}, {0x8000000000000001ull, 0x0000000000000001ull}, + {0x000fffffffffffffull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0x000fffffffffffffull}, + {0x000fffffffffffffull, 0x0010000000000000ull}, {0x0010000000000000ull, 0x000fffffffffffffull}, + {0x800fffffffffffffull, 0x8010000000000000ull}, {0x8010000000000000ull, 0x800fffffffffffffull}, + {0x0000000000000001ull, 0x000fffffffffffffull}, {0x000fffffffffffffull, 0x0000000000000001ull}, + {0x8000000000000001ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0x8000000000000001ull}, + {0x7ff8000000000123ull, 0x0000000000000001ull}, {0x0000000000000001ull, 0x7ff8000000000123ull}, + {0xfff8000000000123ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0xfff8000000000123ull}, + {0x7ff0000000000123ull, 0x0000000000000001ull}, {0x0000000000000001ull, 0x7ff0000000000123ull}, + {0xfff0000000000123ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0xfff0000000000123ull}, + {0x7ff0000000000000ull, 0x000fffffffffffffull}, {0x000fffffffffffffull, 0x7ff0000000000000ull}, + {0xfff0000000000000ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0xfff0000000000000ull}, + {0x0000000000000000ull, 0x8000000000000000ull}, {0x8000000000000000ull, 0x0000000000000000ull}, + {0x0010000000000000ull, 0x8010000000000000ull}, {0x8010000000000000ull, 0x0010000000000000ull}, + }}; + constexpr std::array, 4> modifiers{{{0u, 0u}, {1u, 2u}, {2u, 1u}, {3u, 3u}}}; + for (const auto& modifier : modifiers) { + alignas(256) auto code = Code; + for (const auto word : {13u, 15u}) { + code[word] |= modifier[0] << 8u; + code[word + 1u] |= modifier[1] << 29u; + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t source = 0; source < 2u; ++source) { + Input[tid * Inputs + source * 2u] = static_cast(rows[tid][source]); + Input[tid * Inputs + source * 2u + 1u] = static_cast(rows[tid][source] >> 32u); + } + } + for (const int denormals : {-1, 0, 2, 3}) { + for (const bool ieee : {false, true}) { + if (denormals < 0 && ieee) continue; + std::optional mode; + if (denormals >= 0) mode = ShaderRecompiler::ShaderFloatMode{0x30u | (static_cast(denormals) << 6u), true, ieee, false}; + Run(device, code, mode); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto pair = rows[tid]; + for (std::uint32_t source = 0; source < 2u; ++source) { + if (((modifier[0] >> source) & 1u) != 0u) pair[source] &= 0x7fffffffffffffffull; + if (((modifier[1] >> source) & 1u) != 0u) pair[source] ^= 0x8000000000000000ull; + if ((denormals == 0 || denormals == 2) && (pair[source] & 0x7ff0000000000000ull) == 0u) pair[source] &= 0x8000000000000000ull; + } + for (std::uint32_t operation = 0; operation < 2u; ++operation) { + const auto expected = MinMaxReference(pair[0], pair[1], operation != 0u, ieee); + for (std::uint32_t word = 0; word < 2u; ++word) { + const auto actual = Output[tid * Results + 8u + operation * 2u + word]; + const auto expectedWord = static_cast(expected >> (word * 32u)); + Require(actual == expectedWord, "f64 min/max denormal mode " + std::to_string(denormals) + " IEEE " + std::to_string(ieee) + " abs " + std::to_string(modifier[0]) + " neg " + std::to_string(modifier[1]) + " lane " + std::to_string(tid) + " " + Names[8u + operation * 2u + word] + " is " + Hex(actual) + ", expected " + Hex(expectedWord)); + } + } + } + } + } + } + std::puts("f64 min/max denormal input cases passed"); +} + } int main() { @@ -202,9 +275,11 @@ int main() { } for (const bool ieee : {false, true}) { Ieee = ieee; - Run(*device); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Run(*device, Code, ieee ? std::optional{ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}} : std::nullopt); Check(); } + CheckMinMaxDenormals(*device); std::puts("f64 arithmetic tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 31164d9de82c..f061accf5f7e 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -240,7 +240,7 @@ class TranslationContext { bool vMullitF32(const RdnaInstruction& inst); void emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx); bool float64Operation(const RdnaInstruction& inst, IrOpcode opcode); - bool nonIeeeMinMaxF64(const RdnaInstruction& inst, IrOpcode opcode); + bool minMaxF64(const RdnaInstruction& inst, IrOpcode opcode); bool float64Unary(const RdnaInstruction& inst, IrOpcode opcode); bool vCvtF64F32(const RdnaInstruction& inst); void writeF64Result(const RdnaOperand& operand, IrValue& value); diff --git a/core/shader/recompiler/Translation/src/Float64Instructions.cpp b/core/shader/recompiler/Translation/src/Float64Instructions.cpp index b6f8f33333f3..27fc9bc24fe0 100644 --- a/core/shader/recompiler/Translation/src/Float64Instructions.cpp +++ b/core/shader/recompiler/Translation/src/Float64Instructions.cpp @@ -48,13 +48,17 @@ IrValue& TranslationContext::flushTinyF64Result(IrOpcode opcode, IrValue& lhs, I return ir.ConstructU64(ir.Select(tiny, ir.Constant(0u), ir.CompositeExtract(result, 0u)), ir.Select(tiny, sign, ir.CompositeExtract(result, 1u))); } -bool TranslationContext::nonIeeeMinMaxF64(const RdnaInstruction& inst, IrOpcode opcode) { - const std::array, 2> bits{readF64Bits(sourceAt(inst, 0u)), readF64Bits(sourceAt(inst, 1u))}; +bool TranslationContext::minMaxF64(const RdnaInstruction& inst, IrOpcode opcode) { + const std::array, 2> bits{flushF64Bits(readF64Bits(sourceAt(inst, 0u)), 0x40u), flushF64Bits(readF64Bits(sourceAt(inst, 1u)), 0x40u)}; const auto isNan = [&](const std::array& value) { IrValue& magnitude = ir.BitwiseAnd(value[1].Value(), ir.Constant(0x7fffffffu)); return &ir.LogicalOr(ir.UGreaterThan(magnitude, ir.Constant(0x7ff00000u)), ir.LogicalAnd(ir.IEqual(magnitude, ir.Constant(0x7ff00000u)), ir.INotEqual(value[0].Value(), ir.Constant(0u)))); }; IrValue& plain = ir.Emit(opcode, IrType::U64, {&ir.ConstructU64(bits[0][0].Value(), bits[0][1].Value()), &ir.ConstructU64(bits[1][0].Value(), bits[1][1].Value())}); + if (ieeeMode) { + writeF64Result(inst.destination, plain); + return true; + } IrValue* lhsNan = isNan(bits[0]); IrValue* rhsNan = isNan(bits[1]); std::array words{}; diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index f045f3635aad..9c254b77c078 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -955,9 +955,9 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VFmaF64: return float64Operation(inst, IrOpcode::FPFma64); case RdnaOpcode::VMinF64: - return ieeeMode ? float64Operation(inst, IrOpcode::FPMin64) : nonIeeeMinMaxF64(inst, IrOpcode::FPMin64); + return minMaxF64(inst, IrOpcode::FPMin64); case RdnaOpcode::VMaxF64: - return ieeeMode ? float64Operation(inst, IrOpcode::FPMax64) : nonIeeeMinMaxF64(inst, IrOpcode::FPMax64); + return minMaxF64(inst, IrOpcode::FPMax64); case RdnaOpcode::VLdexpF64: return float64Unary(inst, IrOpcode::FPLdexp64); case RdnaOpcode::VTruncF64: diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index ab62e0cbe0ec..a7549e3785f6 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -103,7 +103,7 @@ - [WRITE_DATA to thread-trace user-data registers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) updates the queue's UCONFIG values for register destinations `0xc340..0xc347`, including repeated writes to one address; these are not guest-memory labels. The range follows AMD's GFX10 `SQ_THREAD_TRACE_USERDATA_0..7` definitions in [the Linux register header](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/include/asic_reg/gc/gc_10_1_0_offset.h) and the packets emitted by PPSA21564's bundled AGC driver before its EOP flip. Other register destinations throw. GPU thread-trace capture and its user-data stream are not emitted; only the final register state is retained. Console trace behavior is unverified. - [WRITE_DATA with DST_SEL 3](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) writes guest memory like DST_SEL 5. AMD's GFX10 header names 3 the GDS, but Killzone: Liberation (PPSA11142) writes its frame fence with `sceAgcDcbWriteData(dst 6, cache policy 3, write confirm)`, which the SPRX-confirmed encoding ([OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/cb_builders.c)) turns into DST_SEL 3 with a 40-bit label address in the title's own memory that `RELEASE_MEM` and `WAIT_REG_MEM` then use; the console must write that label or the title would wait on it forever. Whether the console routes it through the GDS or a cache is unknown - [SET_*_REG_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write. -- The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. With f16/f64 denormals flushed the hardware also flushes denormal inputs of f64 min/max, which those lowerings still do not. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. +- The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. `v_min_f64` and `v_max_f64` flush subnormal inputs to signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3, and an unknown mode, preserve inputs. The fully flushed and preserved f64 behavior was [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/2036); split-mode selection follows [LLVM's denormal-mode enumeration](https://llvm.org/docs/AMDGPUUsage.html#floating-point-denorm-mode-enumeration-values). Other f64 arithmetic does not model these denormal controls. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. - A [WAIT_REG_MEM](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp) (libSceAgcDriver) that a compute queue passes on another queue's recorded label store, before the GPU has executed it, is landed before the queue's next dispatch, draw, COND_EXEC or predicated packet reads guest memory: the worker polls until memory satisfies the wait or no unfinished batch writes the label. The graphics queue (queue 0) is exempt, as landing its waits costs PPSA01341 about a quarter of its frame rate; a title that fills memory a graphics command buffer reads after it submits the buffer behind such a wait reads it early there. `APS5_LAND_WAITS_ALL_QUEUES=1` lands the graphics queue's waits too. - The [dispatches that follow a compute queue's wait](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp) (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (`APS5_GROUP_CAPTURE_ALL_QUEUES=1` includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory. - [PA_CL_CLIP_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. From 6ca5388c55c21dab5a3a8dffbe12b2f16561fc0f Mon Sep 17 00:00:00 2001 From: milescoviello Date: Fri, 16 Oct 2026 11:28:00 +0000 Subject: [PATCH 016/176] fix(shader): store through DIM 2D image_store to a cube T#'s BASE_ARRAY face A DIM 2D image_load through a cube T# already reads texel (x, y) of the T#'s BASE_ARRAY face (#4139, measured on RDNA1). A DIM 2D image_store to a cube T# had no matching runtime mode and threw "image descriptor is incompatible with the static runtime image interface". Crimson Desert writes a 16_16_16_16_FLOAT cube this way from a compute shader. A mip-less image_store to an image the program only stores to now takes the same face mode, writing texel (x, y) of the BASE_ARRAY face, the face the measured load reads. Stores were not measured on hardware; the debt entry says so. Formats that store through a conversion (SINT) keep throwing. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit dbf176f2ef1db88cda102ed17e7aab4408923363) --- docs/dev/debt/shader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index b2917b84776c..fb8a8ad8d43c 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -62,7 +62,7 @@ - [MIMG PCK2/PCK4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_pck2/pck4`, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from `x & ~(N - 1)` on row y, concatenated (element k at bit `k * texel bits`) and zero-extended. The dword is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314). - [3D image accesses bound to a 2D or 2D-array T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follow CouchRiv's [hardware oracle](../HW_ORACLE.md) measurement on RDNA2 (gfx1036, #2023) and adriadam10's (gfx1035, #2140), not the PS5's gfx1013. On a 2D T#, `image_sample_lz` ignores r and reads the (u,v) texel, `image_load` and `image_load_mip` read the (x,y) texel when z is 0 and return zero otherwise, and `image_get_resinfo` returns (width, height, 1, levels). On a 2D-array T#, these accesses behave like dim 2D-array: r or z is the layer, and `image_get_resinfo` returns the layer count as its third component. Every other access of such an image, including samples with an LOD, bias, derivatives, offset or comparison, gathers, `image_get_lod` and stores, keeps throwing because it is not measured. - [2D-array samples through a 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (`image_sample`, `image_sample_l` and `image_sample_lz` with DIM 2D_ARRAY) sample the volume as DIM 3D does: the third address VGPR is the normalized r coordinate, which the sampler's Z mode wraps or clamps, not a layer index, and a plain sample in a pixel shader takes its LOD from the derivatives of all three coordinates. Measured on RDNA2 (RX 6700 XT, gfx1031, driver 32.0.21045.5002, Windows 11) with the [hardware oracle](../HW_ORACLE.md) template run through the driver's comgr and HIP runtime, not on the PS5's gfx1013: on 16x16x4 R32_FLOAT 3D T#s with one and three levels, the three instructions with DIM 2D_ARRAY returned the same value as with DIM 3D in all 256 lanes of 66 cases (point, bilinear and z-linear filters, wrap addressing, r from -0.125 to 3, quad steps of 1 to 4 texels in u and v and of 0 to 4 slices in r, LOD 1). Other samples of such a pair (bias, LOD clamp, derivatives, offsets, comparison, 16-bit addresses), gathers, `image_get_lod` and a sample mixed with a load of the same image keep throwing because they are not measured. The host sampler ignores `Z_FILTER`, as for every 3D sample, so r is filtered with the XY filter. -- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, measured on RDNA2 (gfx1036, [by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6217609589), not the PS5's gfx1013) with the [hardware oracle](../HW_ORACLE.md) on 8x8 mip-less cube arrays (`DEPTH` 11) of 32_UINT, 32_FLOAT and 16_16_16_16_FLOAT ([the last also by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6224023050), with exactly representable half values) with `BASE_ARRAY` 0 and 6, wave32 and wave64: the 64 stored texels land on that face and every other byte of the 64 KiB backing is unchanged. Other formats, and Crimson Desert's own 256x256 tiled descriptor, are not measured. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw. +- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, the face the measured `image_load` reads; stores were not measured on hardware. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw. - [Unnormalized S#s](../../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by explicit-LOD `image_sample` without comparison, derivatives or `_a`, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#. - [Unnormalized texel offsets](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) are added to the f32 coordinates: exact on RDNA2 (gfx1035), which applies the offset before the clamp ([measured by adriadam10](https://github.com/boykopovar/AnyPS5/pull/822#issuecomment-6033192725)). - [Cube image gradients](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (shader recompiler) - `image_sample_d` and `image_sample_cd` on a cube pass their gradients to the host sample without the -1.0 that moves the face coordinates from [1, 2] to [0, 1], as Mesa's AMD lowering builds them (`src/amd/common/nir/ac_nir_lower_tex_coords.c`, `prepare_cube_coords`: face-space derivatives with no offset, coordinates offset by 1.5). Measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md): on a 16x16 32_FLOAT cube with five levels, point mip filter, `image_sample_d` reads the level of `round(log2(16 * max(|ddx|, |ddy|)))` for gradients of 2^(level ± 0.3) texels on each of the four components, both signs, mixed components, every face and two coordinates (1032 rows). `image_sample_cd` takes one level per quad of lanes, from the gradients of its first lane; the host sample computes it per lane. From e1eb85dc04df01d21a54eab2772405752e571edf Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:29:41 +0200 Subject: [PATCH 017/176] fix(libSceNet): inherit nonblocking mode on accepted sockets (cherry picked from commit 1fee29b6ada39d77dde6097543b807e213a1563f) --- core/libs/prx/libSceNet/Export.cpp | 8 ++++ core/libs/tests/GuestSceNet.cpp | 70 ++++++++++++++++++++++++++++++ docs/dev/debt/libSceNet.md | 1 + 3 files changed, 79 insertions(+) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index bcacdeceafea..c9d9d916923a 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -890,6 +890,14 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { std::lock_guard lk(g_mutex); g_socks.emplace(descriptor, connection); } + if (listener.nonblock) { + const int enabled = 1; + if (sceNetSetsockopt(descriptor, NET_SOL_SOCKET, NET_SO_NBIO, &enabled, sizeof(enabled)) != 0) { + const int error = *errno_slot(); + sceNetSocketClose(descriptor); + return fail(error); + } + } if (addr && !native_to_guest_address(peer, addr, addrlen)) { sceNetSocketClose(descriptor); return fail(NET_EAFNOSUPPORT); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 9032269a6baa..293007c76e8c 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -917,6 +917,71 @@ static void CheckUnspecifiedIpv6() { Require(sceNetSocketClose(receiver) == 0); } +static void CheckAcceptedNonblocking(int family, bool nonblocking) { + const auto addressSize = family == 2 ? 16u : 28u; + std::array address{}; + address[0] = static_cast(addressSize); + address[1] = static_cast(family); + if (family == 2) { address[4] = 127; address[7] = 1; } + else address[23] = 1; + const int listener = sceNetSocket("accept-mode", family, 1, 6); + Require(listener >= 0 && sceNetBind_nid_postfix(listener, address.data(), addressSize) == 0); + Require(sceNetListen(listener, 1) == 0); + const int mode = nonblocking ? 1 : 0; + Require(sceNetSetsockopt(listener, 0xffff, 0x1200, &mode, sizeof(mode)) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(listener, address.data(), &size) == 0 && size == addressSize); + const int client = sceNetSocket("accept-client", family, 1, 6); + Require(client >= 0 && sceNetConnect(client, address.data(), addressSize) == 0); + int accepted = -1; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + accepted = sceNetAccept(listener, nullptr, nullptr); + if (accepted >= 0) break; + Require(nonblocking && Failed(accepted, 35)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (std::chrono::steady_clock::now() < deadline); + Require(accepted >= 0); + int inherited = -1; + size = sizeof(inherited); + Require(sceNetGetsockopt(accepted, 0xffff, 0x1200, &inherited, &size) == 0 && size == sizeof(inherited)); + const int status = fcntl_nid_postfix(accepted, 3); + std::printf("Accepted IPv%d mode=%d SO_NBIO=%d F_GETFL=%d\n", family == 2 ? 4 : 6, mode, inherited, status); + std::fflush(stdout); + Require(inherited == mode && status == (2 | (nonblocking ? 4 : 0))); + const int changed = 1 - mode; + Require(sceNetSetsockopt(listener, 0xffff, 0x1200, &changed, sizeof(changed)) == 0); + size = sizeof(inherited); + Require(sceNetGetsockopt(accepted, 0xffff, 0x1200, &inherited, &size) == 0 && inherited == mode); + struct ReceiveResult { std::int64_t bytes; int error; char value; }; + auto receive = std::async(std::launch::async, [accepted] { + char byte = '?'; + const auto received = sceNetRecv(accepted, &byte, sizeof(byte), 0); + return ReceiveResult{received, *sceNetErrnoLoc(), byte}; + }); + const bool ready = receive.wait_for(nonblocking ? std::chrono::milliseconds(2000) : std::chrono::milliseconds(50)) == std::future_status::ready; + const char payload = 'n'; + Require(sceNetSend(client, &payload, sizeof(payload), 0) == sizeof(payload)); + const auto received = receive.get(); + Require(ready == nonblocking); + if (nonblocking) { + Require(received.bytes == static_cast(0x80410123) && received.error == 35 && received.value == '?'); + char byte = '?'; + std::int64_t count = -1; + const auto arrival = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + count = sceNetRecv(accepted, &byte, sizeof(byte), 0); + if (count >= 0) break; + Require(Failed(count, 35)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (std::chrono::steady_clock::now() < arrival); + Require(count == sizeof(byte) && byte == payload); + } else { + Require(received.bytes == sizeof(payload) && received.value == payload); + } + for (const int descriptor : {accepted, client, listener}) Require(sceNetSocketClose(descriptor) == 0); +} + static void CheckIpv6SocketsAreIpv6Only() { const int inet = sceNetSocket("ipv4-any", 2, 2, 17); Require(inet >= 0); @@ -1090,6 +1155,11 @@ int main(int argc, char** argv) { CheckUnboundDatagramTimeoutAfterAddress(); CheckUnspecifiedIpv6(); if (!ipv6Unavailable) CheckIpv6SocketsAreIpv6Only(); + for (const int family : {2, 28}) { + if (family == 28 && ipv6Unavailable) continue; + CheckAcceptedNonblocking(family, false); + CheckAcceptedNonblocking(family, true); + } CheckDatagramDisconnect(); CheckCloseKernelSocket(); CheckKernelSocketOption(); diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 3782ee84d16b..04ffc415c34d 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -11,6 +11,7 @@ ### Functional - [sceNetSocket and sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) map peer socket types 6 and 10 to host UDP and TCP transports and accept the PS5 broadcast option `0x20000` as `SO_BROADCAST`. Loopback transport is tested; console peer negotiation and peer virtual-port routing are not implemented. +- [sceNetAccept](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) inherits the listener's non-blocking mode through the existing `SO_NBIO` setter, as the libkernel socket path does and FreeBSD's [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L571) copies the socket state. The mode is sampled when accept starts; changes between connection creation and accept are not modelled. Not measured on a console. - [sceNetConnect to AF_UNSPEC](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had - [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. On Windows a receive or send on a stream socket that times out fails with `ETIMEDOUT`, where FreeBSD returns `EAGAIN` or the partial count: Winsock leaves the connection in an indeterminate state after a timeout and reports no partial count ([SOL_SOCKET options](https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options)), so FreeBSD's behaviour needs the timeout waited for without the host option. A receive on a datagram socket that times out fails with `EAGAIN` - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take `SO_SNDTIMEO` and `SO_RCVTIMEO` (0x1005, 0x1006) as a 16-byte `struct timeval`, as FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/uipc_socket.c#L2657-L2690) do, and Sony's 0x1105 and 0x1106 as an `int` of microseconds. A [console log of ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/issues/49) (firmware 12.20) shows receives timing out every 100 ms after it gave 0x1006 a `{0, 100000}` timeval. The errors for a value shorter than a timeval (`EINVAL`) or out of range (`EDOM`), and `sceNetGetsockopt` cutting the timeval to the caller's length, follow FreeBSD's `sooptcopyin` and `sooptcopyout` and are not measured on a console From 5e66fccf620cf9ec72684c87b1b0de4f81dfbb7f Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Thu, 15 Oct 2026 21:54:18 +0000 Subject: [PATCH 018/176] fix(relinker): exclude trailing PS4 relative jump tables Co-authored-by: SuperCatElvis <39642318+SuperCatElvis@users.noreply.github.com> (cherry picked from commit f25f22630e89486ef8a92f618d912065a107a3c8) --- core/relinker/CMakeLists.txt | 2 + .../relinker/analysis/RelativeJumpTables.hpp | 24 ++++ .../UnusedNidFilter/IControlFlowGraph.hpp | 4 +- .../src/analysis/CodeInstructionCollector.cpp | 20 ++- .../src/analysis/RelativeJumpTables.cpp | 131 ++++++++++++++++++ .../UnusedNidFilter/ControlFlowGraph.cpp | 13 +- .../src/pipeline/RelinkerPipeline.cpp | 28 +++- .../relinker/tests/ControlFlowGraphTests.cpp | 38 +++++ 8 files changed, 245 insertions(+), 15 deletions(-) create mode 100644 core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp create mode 100644 core/relinker/relinker/src/analysis/RelativeJumpTables.cpp diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index bafc25396d7d..f0af8bfa534b 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CodeInstructionCollector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SceCodeExtent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/RelativeJumpTables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp @@ -666,6 +667,7 @@ if(BUILD_TESTING) add_test_executable(control_flow_graph_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/ControlFlowGraphTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/RelativeJumpTables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionDecoder.cpp diff --git a/core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp b/core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp new file mode 100644 index 000000000000..3e8bc1015248 --- /dev/null +++ b/core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp @@ -0,0 +1,24 @@ +#ifndef RELINKER_ANALYSIS_RELATIVEJUMPTABLES_HPP +#define RELINKER_ANALYSIS_RELATIVEJUMPTABLES_HPP + +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +struct RelativeJumpTable { + VirtualAddress Function; + VirtualAddress Begin; + VirtualAddress End; +}; + +std::size_t FindTrailingRelativeJumpTable(std::span code); +std::vector FindRelativeJumpTables(std::span segment, VirtualAddress segmentVAddr, const std::vector>& functions); + +} + +#endif diff --git a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp index 7b1add380f2f..b8386a10b86c 100644 --- a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp +++ b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,8 @@ std::unique_ptr BuildControlFlowGraph( const std::vector& extraEntries, const IRelativeRelocationIndex& relativeRelocations, bool followCodeAddresses, - std::unordered_set& reachable + std::unordered_set& reachable, + const std::function& isData = {} ); std::unique_ptr BuildControlFlowGraph( diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index d88ef03db62d..0a7b2f56463b 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -162,7 +163,9 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector roots; std::map functions; - const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); }; + std::map tables; + const auto inTable = [&](std::uint64_t address) { const auto after = tables.upper_bound(address); return after != tables.begin() && address < std::prev(after)->second; }; + const auto addRoot = [&](std::uint64_t address) { if (isCode(address) && !inTable(address)) roots.insert(address); }; const auto addAddressTakenRoot = [&](std::uint64_t address, const std::uint8_t* code, const Codegen::DecodedInstructionInfo& info) { if (!info.HasRipRelativeDisp || code[info.OpcodeOffset] != 0x8d || !(info.RexPrefix & 8)) return; const auto displacement = std::bit_cast(Io::ReadU32(bytes, fileOffset(address, info.Length) + info.RipRelativeDispOffset)); @@ -298,6 +301,19 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector> ranges(functions.begin(), functions.end()); + for (const auto& header : codeHeaders) { + if (header.Type != 1 || (header.Flags & 1) == 0) continue; + const std::span segment(bytes.data() + header.Offset, header.FileSize); + for (const auto& table : FindRelativeJumpTables(segment, header.MappedAddress, ranges)) { + tables.emplace(table.Begin, table.End); + auto& end = functions.at(table.Function); + end = std::min(end, table.Begin); + } + } + std::erase_if(roots, inTable); + } if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); AddressBits instructions(headers); const Codegen::X64InstructionDecoder decoder; @@ -413,7 +429,7 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root); if (entries.empty()) continue; const std::span text(bytes.data() + header.Offset, header.FileSize); - const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, true, sectionReachable[i]); + const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, true, sectionReachable[i], inTable); for (const auto address : graph->ReachableVaddrs()) { if (!decodedInstructions.Insert(address)) continue; instructions.Insert(address); diff --git a/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp new file mode 100644 index 000000000000..0ccbc28717a3 --- /dev/null +++ b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +namespace Relinker { + +namespace { + +constexpr VirtualAddress MaxTableGapAfterFunction = 0x10000; + +struct TableLoad { + std::size_t At; + std::size_t Target; +}; + +bool IsNop(std::span code, std::size_t at, std::size_t length) { + auto position = at; + const auto end = at + length; + while (position < end && (code[position] == 0x66 || code[position] == 0x2e || code[position] == 0x3e)) ++position; + if (position >= end) return false; + if (code[position] == 0x90) return position + 1 == end; + return code[position] == 0x0f && position + 1 < end && code[position + 1] == 0x1f; +} + +bool OnlyPadding(std::span code, std::size_t from, const Codegen::X64InstructionDecoder& decoder) { + for (auto position = from; position < code.size();) { + if (code[position] == 0x00 || code[position] == 0xcc || code[position] == 0x90) { + ++position; + continue; + } + try { + const auto length = decoder.DecodeInstruction(code.data() + position, code.size() - position).Length; + if (length == 0 || length > code.size() - position || !IsNop(code, position, length)) return false; + position += length; + } catch (const Codegen::CodegenException&) { + return false; + } + } + return true; +} + +std::size_t TableEntries(std::span code, std::size_t table, std::size_t end, std::size_t firstTable, const std::vector& instructionStarts) { + std::size_t entries = 0; + while (table + (entries + 1) * 4 <= end) { + std::int32_t relative = 0; + std::memcpy(&relative, code.data() + table + entries * 4, sizeof(relative)); + const auto destination = static_cast(table) + relative; + if (destination < 0 || destination >= static_cast(firstTable) || !instructionStarts[static_cast(destination)]) break; + ++entries; + } + return entries; +} + +std::size_t TablesEnd(std::span code, std::size_t firstTable, const std::vector& loads, const std::vector& instructionStarts) { + auto table = firstTable; + while (table < code.size()) { + auto end = code.size(); + for (const auto& load : loads) + if (load.Target > table && load.Target < end) end = load.Target; + const auto entries = TableEntries(code, table, end, firstTable, instructionStarts); + if (entries == 0 || (table == firstTable && entries < 2)) return table == firstTable ? 0 : table; + table += entries * 4; + if (std::none_of(loads.begin(), loads.end(), [&](const auto& load) { return load.Target == table; })) return table; + } + return table; +} + +} + +std::size_t FindTrailingRelativeJumpTable(std::span code) { + const Codegen::X64InstructionDecoder decoder; + std::vector loads; + std::vector instructionStarts(code.size() + 1, false); + bool flowEnded = false; + bool sawIndirectJump = false; + std::size_t lastIndirectJump = 0; + for (std::size_t position = 0; position < code.size();) { + instructionStarts[position] = true; + if (flowEnded && sawIndirectJump) { + for (const auto& load : loads) { + if (load.Target != position || load.At >= lastIndirectJump) continue; + const auto end = TablesEnd(code, position, loads, instructionStarts); + if (end != 0 && OnlyPadding(code, end, decoder)) return position; + } + } + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(code.data() + position, code.size() - position); + } catch (const Codegen::CodegenException&) { + break; + } + if (info.Length == 0 || info.Length > code.size() - position) break; + const auto next = position + info.Length; + if (info.HasRipRelativeDisp && !info.IsTwoByteOpcode && info.OpcodeOffset < info.Length && info.Length >= 4 && code[position + info.OpcodeOffset] == 0x8d) { + std::int32_t displacement = 0; + std::memcpy(&displacement, code.data() + next - 4, sizeof(displacement)); + const auto target = static_cast(next) + displacement; + if (target > static_cast(next) && target < static_cast(code.size())) loads.push_back({position, static_cast(target)}); + } + if (info.FlowKind == Codegen::ControlFlowKind::IndirectJump) { + sawIndirectJump = true; + lastIndirectJump = position; + } + if (!IsNop(code, position, info.Length)) { + const bool directJumpOrCall = (info.Length == 5 && (code[position] == 0xe8 || code[position] == 0xe9)) || (info.Length == 2 && code[position] == 0xeb); + flowEnded = directJumpOrCall || info.FlowKind == Codegen::ControlFlowKind::Return || info.FlowKind == Codegen::ControlFlowKind::IndirectJump || info.FlowKind == Codegen::ControlFlowKind::Trap; + } + position = next; + } + return code.size(); +} + +std::vector FindRelativeJumpTables(std::span segment, VirtualAddress segmentVAddr, const std::vector>& functions) { + std::vector tables; + const auto segmentEnd = segmentVAddr + segment.size(); + for (std::size_t index = 0; index < functions.size(); ++index) { + const auto [begin, end] = functions[index]; + if (begin < segmentVAddr || begin >= segmentEnd || end <= begin) continue; + auto inspectEnd = std::min(end, segmentEnd); + if (index + 1 < functions.size() && functions[index + 1].first > inspectEnd && functions[index + 1].first - inspectEnd <= MaxTableGapAfterFunction) + inspectEnd = std::min(functions[index + 1].first, segmentEnd); + const auto code = segment.subspan(begin - segmentVAddr, inspectEnd - begin); + if (const auto tail = FindTrailingRelativeJumpTable(code); tail != 0 && tail < code.size()) + tables.push_back({begin, begin + tail, inspectEnd}); + } + return tables; +} + +} diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp index e0a4b0c5d977..f7fac0f0132b 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp @@ -39,7 +39,8 @@ void buildGraph( const IRelativeRelocationIndex& relativeRelocations, const std::vector* data, bool followCodeAddresses, - std::unordered_set& reachable + std::unordered_set& reachable, + const std::function& isData ) { if (text.size() > std::numeric_limits::max() - textVaddr) throw RelinkerException("Text section range exceeds the address space"); @@ -52,6 +53,7 @@ void buildGraph( auto enqueue = [&](VirtualAddress va) { if (reachable.count(va) > 0) return; + if (isData && isData(va)) return; if (va < textVaddr || va - textVaddr >= text.size()) return; const auto offset = static_cast(va - textVaddr); auto& word = visited[offset / 64]; @@ -83,7 +85,7 @@ void buildGraph( VirtualAddress va = worklist.front(); worklist.pop(); - while (va >= textVaddr && va - textVaddr < text.size() && !reachable.contains(va)) { + while (va >= textVaddr && va - textVaddr < text.size() && !reachable.contains(va) && !(isData && isData(va))) { std::size_t bufOff = static_cast(va - textVaddr); std::size_t available = text.size() - bufOff; if (available == 0) throw RelinkerException("CFG: zero available bytes at target", va); @@ -173,9 +175,10 @@ std::unique_ptr BuildControlFlowGraph( const std::vector& extraEntries, const IRelativeRelocationIndex& relativeRelocations, bool followCodeAddresses, - std::unordered_set& reachable + std::unordered_set& reachable, + const std::function& isData ) { - buildGraph(text, textVaddr, entryVaddr, extraEntries, relativeRelocations, nullptr, followCodeAddresses, reachable); + buildGraph(text, textVaddr, entryVaddr, extraEntries, relativeRelocations, nullptr, followCodeAddresses, reachable, isData); return std::make_unique(&reachable); } @@ -212,7 +215,7 @@ std::unique_ptr BuildAddressTakenControlFlowGraph( const std::vector& data ) { std::unordered_set reachable; - buildGraph(std::span(text.data(), text.size()), textVaddr, entryVaddr, extraEntries, relativeRelocations, &data, false, reachable); + buildGraph(std::span(text.data(), text.size()), textVaddr, entryVaddr, extraEntries, relativeRelocations, &data, false, reachable, {}); return std::make_unique(std::move(reachable)); } diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index 949978dc793f..c164781c75aa 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -326,8 +327,11 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf for (const auto& ref : nidRefs) _validationPolicy->ValidateRelocationTypeSupported(ref.RelocationTypeValue, ref.RelocationTableOffset); + std::vector> unwindRanges; if (std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; })) { const auto functions = UnusedNidFilter::ReadExceptionFunctions(raw, programHeaders, {}, {}); + for (const auto& function : functions) unwindRanges.emplace_back(function.Begin, function.End); + std::sort(unwindRanges.begin(), unwindRanges.end()); for (auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { static_cast(segmentFileOffset); if (const auto codeEnd = FindSceCodeEnd(segment, segmentVAddr, functions, gotVAddr, dynJmpRelSize)) @@ -335,13 +339,23 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf } } + std::vector> scanSegments; + for (const auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { + static_cast(segmentFileOffset); + auto& scanSegment = scanSegments.emplace_back(segment); + for (const auto& table : FindRelativeJumpTables(segment, segmentVAddr, unwindRanges)) + std::fill(scanSegment.begin() + static_cast(table.Begin - segmentVAddr), scanSegment.begin() + static_cast(table.End - segmentVAddr), std::uint8_t{0x90}); + } + std::vector syscallPatches; - for (auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { - for (const auto offset : _syscallScanner->ScanCodeSectionForSyscalls(segment, segmentVAddr, segment.size())) { + for (std::size_t index = 0; index < executableSegments.size(); ++index) { + auto& [segment, segmentVAddr, segmentFileOffset] = executableSegments[index]; + auto& scanSegment = scanSegments[index]; + for (const auto offset : _syscallScanner->ScanCodeSectionForSyscalls(scanSegment, segmentVAddr, scanSegment.size())) { if (offset > segment.size() || segment.size() - offset < 2u) throw RelinkerException("Syscall trap patch exceeds executable segment", segmentVAddr + offset); - segment[offset] = 0x0F; - segment[offset + 1] = 0x0B; + segment[offset] = scanSegment[offset] = 0x0F; + segment[offset + 1] = scanSegment[offset + 1] = 0x0B; syscallPatches.push_back({segmentFileOffset + offset, {0x0F, 0x0B}}); } } @@ -486,10 +500,10 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf entries.push_back(std::move(entry)); } - for (const auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { - static_cast(segmentFileOffset); + for (std::size_t index = 0; index < executableSegments.size(); ++index) { + const auto& segmentVAddr = std::get<1>(executableSegments[index]); for (auto& entry : entries) { - auto segmentSites = _callSiteResolver->ResolveCallSites(segment, segmentVAddr, entry.TargetOffset, 8); + auto segmentSites = _callSiteResolver->ResolveCallSites(scanSegments[index], segmentVAddr, entry.TargetOffset, 8); entry.CallSites.insert(entry.CallSites.end(), segmentSites.begin(), segmentSites.end()); entry.CallSitesResolved = !entry.CallSites.empty(); } diff --git a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp index aa724babbe34..2d0fb562ec0e 100644 --- a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp +++ b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -176,6 +177,42 @@ void CheckHighVirtualAddress() { Require(graph->IsReachable(highVaddr + 4), "Missing high branch target address"); } +void CheckRelativeJumpTables() { + constexpr std::size_t tableOffset = 16; + Bytes text(32, 0x90); + text[0] = 0x48; + text[1] = 0x8d; + text[2] = 0x05; + SetDisplacement(text, 3, 7, TextAddress + tableOffset); + text[7] = 0xff; + text[8] = 0xe0; + const std::int32_t targets[]{-16, -9}; + std::memcpy(text.data() + tableOffset, targets, sizeof(targets)); + Require(Relinker::FindTrailingRelativeJumpTable(text) == tableOffset, "Relative jump table was not found"); + + const std::vector> functions{{TextAddress, TextAddress + 9}, {TextAddress + text.size(), TextAddress + text.size() + 8}}; + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + Require(tables.size() == 1, "Expected one relative jump table"); + Require(tables[0].Function == TextAddress && tables[0].Begin == TextAddress + tableOffset && tables[0].End == TextAddress + text.size(), "Relative jump table range was incorrect"); + + Bytes oneEntry = text; + const std::int32_t singleTarget[]{-16}; + std::memcpy(oneEntry.data() + tableOffset, singleTarget, sizeof(singleTarget)); + const std::int32_t invalidSecondTarget[]{0x100}; + std::memcpy(oneEntry.data() + tableOffset + 4, invalidSecondTarget, sizeof(invalidSecondTarget)); + Require(Relinker::FindTrailingRelativeJumpTable(oneEntry) == oneEntry.size(), "Single-entry table was accepted"); + + Bytes invalidTarget = text; + const std::int32_t invalidTargets[]{0x100, 0x104}; + std::memcpy(invalidTarget.data() + tableOffset, invalidTargets, sizeof(invalidTargets)); + Require(Relinker::FindTrailingRelativeJumpTable(invalidTarget) == invalidTarget.size(), "Table with non-instruction targets was accepted"); + + Bytes nonPadding = text; + nonPadding.back() = 0x55; + Require(Relinker::FindTrailingRelativeJumpTable(nonPadding) == nonPadding.size(), "Table followed by non-padding bytes was accepted"); + +} + } int main() { @@ -189,6 +226,7 @@ int main() { CheckBorrowedReachability(); CheckAddressOverflow(); CheckHighVirtualAddress(); + CheckRelativeJumpTables(); std::cout << "Control-flow graph tests passed\n"; return 0; } catch (const std::exception& error) { From 8247d1a2847fb99d5d29fdd92535f315ad0b26bd Mon Sep 17 00:00:00 2001 From: Zinebyte <197974009+Zinebyte@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:30:43 -0400 Subject: [PATCH 019/176] fix(nid): report failed library writes (cherry picked from commit 8363c01352184fc73015975ba621b06e2424e1e4) --- core/libs/nid/src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/nid/src/main.cpp b/core/libs/nid/src/main.cpp index 9965d1cda1c6..bf6878ab4f63 100644 --- a/core/libs/nid/src/main.cpp +++ b/core/libs/nid/src/main.cpp @@ -18,6 +18,8 @@ void _writeFile(const std::string& path, const std::vector& data) std::ofstream f(path, std::ios::binary); if (!f) throw std::runtime_error("cannot write: " + path); f.write(reinterpret_cast(data.data()), static_cast(data.size())); + f.close(); + if (!f) throw std::runtime_error("cannot write: " + path); } } From 197b9e5bec08ede35ee9e25b1379fe05782dda03 Mon Sep 17 00:00:00 2001 From: ArjenS-LA Date: Thu, 15 Oct 2026 02:54:47 -0700 Subject: [PATCH 020/176] feat(libc): implement overlap-safe bcopy (cherry picked from commit af691b717ad068bb93ac50bfc428af63ab259613) --- core/libs/prx/libc/src/Strings.cpp | 4 ++++ core/libs/tests/GuestStrings.cpp | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index cc57f2fa359d..a9408ae226b1 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -94,6 +94,10 @@ void APS5_VABI bzero_nid_postfix(void* destination, size_t count) { std::memset(destination, 0, count); } +void APS5_VABI bcopy_nid_postfix(const void* source, void* destination, size_t count) { + if (count != 0) std::memmove(destination, source, count); +} + void* APS5_VABI memcpy_nid_postfix(void* dest, const void* src, size_t n) { return std::memcpy(dest, src, n); } diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index de2206b7ba2e..ab8748450c8c 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -1,4 +1,5 @@ #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -7,6 +8,7 @@ #include extern "C" { +void APS5_VABI bcopy_nid_postfix(const void*, void*, std::size_t); char* APS5_VABI basename_nid_postfix(const char*); char* APS5_VABI __inet_ntoa_nid_postfix(unsigned int); char* APS5_VABI dirname_nid_postfix(const char*); @@ -168,7 +170,28 @@ static void CheckSscanfS() { #endif } +static void CheckBcopy() { + constexpr std::array original = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187}; + for (std::size_t source = 0; source < original.size(); ++source) { + for (std::size_t destination = 0; destination < original.size(); ++destination) { + for (std::size_t count = 0; count <= original.size() - std::max(source, destination); ++count) { + auto actual = original; + auto expected = original; + for (std::size_t i = 0; i < count; ++i) expected[destination + i] = original[source + i]; + bcopy_nid_postfix(actual.data() + source, actual.data() + destination, count); + Require(actual == expected); + } + } + } + auto destination = original; + destination.fill(0xff); + bcopy_nid_postfix(original.data(), destination.data(), original.size()); + Require(destination == original); + bcopy_nid_postfix(nullptr, nullptr, 0); +} + int main() { + CheckBcopy(); CheckBoundsCheckedFunctions(); CheckCaseInsensitiveComparison(); CheckSscanfS(); From c655cf981306107383e49a741e63b48ea45ea2bd Mon Sep 17 00:00:00 2001 From: Yuri Olive Date: Thu, 15 Oct 2026 18:54:39 -0300 Subject: [PATCH 021/176] feat(libSceRtc): implement the remaining firmware exports (cherry picked from commit 51e9e5e8254035c5225b1070279ac380aabfa386) --- core/libs/prx/libSceRtc/Export.cpp | 102 +++++++++++++++++++++++++++++ core/libs/tests/GuestRtc.cpp | 32 +++++++++ docs/dev/debt/libSceRtc.md | 3 + 3 files changed, 137 insertions(+) diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index dcc861103dfa..629ee77fa560 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -630,4 +630,106 @@ int APS5_VABI sceRtcTickAddYears(RtcTick* dst, const RtcTick* src, int16_t years return addMonths(dst, src, static_cast(years) * 12); } +int APS5_VABI sceRtcInit(void) { + return 0; +} + +int APS5_VABI sceRtcEnd(void) { + return 0; +} + +int APS5_VABI sceRtcCompareTick(const RtcTick* first, const RtcTick* second) { + if (!first || !second) return SCE_RTC_ERROR_INVALID_POINTER; + if (first->tick < second->tick) return -1; + return first->tick > second->tick ? 1 : 0; +} + +int APS5_VABI sceRtcGetCurrentRawNetworkTick(RtcTick* tick) { + return sceRtcGetCurrentTick(tick); +} + +int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick* tick) { + return sceRtcGetCurrentTick(tick); +} + +int APS5_VABI sceRtcGetCurrentDebugNetworkTick(RtcTick* tick) { + return sceRtcGetCurrentTick(tick); +} + +static int checkSettableTick(const RtcTick* tick) { + if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; + return tick->tick < UNIX_EPOCH_TICK ? SCE_RTC_ERROR_INVALID_VALUE : 0; +} + +int APS5_VABI sceRtcSetCurrentTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetCurrentNetworkTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetCurrentAdNetworkTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetCurrentDebugNetworkTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetConf(int first, int second, int third, int fourth) { + (void)first; + (void)second; + (void)third; + (void)fourth; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcFormatRFC3339Precise(char* date_time, const RtcTick* utc, int time_zone_minutes, int precision) { + if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; + RtcTick now{}; + if (!utc) { + now.tick = currentTick(); + utc = &now; + } + if (time_zone_minutes < -1439 || time_zone_minutes > 1439 || precision < 0 || precision > 6) return SCE_RTC_ERROR_INVALID_VALUE; + RtcTick local{}; + if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; + const RtcDateTime time = fromTick(local.tick); + int written = std::snprintf(date_time, 20, "%04u-%02u-%02uT%02u:%02u:%02u", time.year, time.month, time.day, time.hour, time.minute, time.second); + if (precision != 0) { + std::uint32_t divisor = 1; + for (int digits = precision; digits < 6; ++digits) divisor *= 10; + written += std::snprintf(date_time + written, 8, ".%0*u", precision, time.microsecond / divisor); + } + if (time_zone_minutes == 0) { + std::snprintf(date_time + written, 2, "Z"); + } else { + const int offset = time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes; + std::snprintf(date_time + written, 7, "%c%02d:%02d", time_zone_minutes < 0 ? '-' : '+', offset / 60, offset % 60); + } + return 0; +} + +int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char* date_time, const RtcTick* utc, int precision) { + if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; + RtcTick now{}; + if (!utc) { + now.tick = currentTick(); + utc = &now; + } + if (precision < 0 || precision > 6) return SCE_RTC_ERROR_INVALID_VALUE; + if (utc->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; + return sceRtcFormatRFC3339Precise(date_time, utc, localOffsetMinutes(*utc), precision); +} + } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 94548c3df0cd..167257d2a009 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -52,6 +52,11 @@ int APS5_VABI sceRtcTickAddYears(RtcTick*, const RtcTick*, std::int16_t); int APS5_VABI sceRtcFormatRFC3339LocalTime(char*, const RtcTick*); int APS5_VABI sceRtcFormatRFC2822(char*, const RtcTick*, int); int APS5_VABI sceRtcFormatRFC2822LocalTime(char*, const RtcTick*); +int APS5_VABI sceRtcFormatRFC3339Precise(char*, const RtcTick*, int, int); +int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char*, const RtcTick*, int); +int APS5_VABI sceRtcCompareTick(const RtcTick*, const RtcTick*); +int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick*); +int APS5_VABI sceRtcSetCurrentTick(const RtcTick*); } static void Require(bool value) { if (!value) std::abort(); } @@ -561,4 +566,31 @@ int main() { Require(sceRtcFormatRFC2822LocalTime(rfc2822, &tick) == 0 && std::strcmp(rfc2822, "Wed, 31 Dec 1969 23:59:59 +0000") == 0); SetTimeZone("UTC0"); #endif + + { + char precise[40]; + RtcTick first{leapDayTick}, second{leapDayTick + 1}; + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 0) == 0 && std::strcmp(precise, "2024-02-29T12:34:56Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 3) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.789Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 6) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.789000Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &second, 90, 6) == 0 && std::strcmp(precise, "2024-02-29T14:04:56.789001+01:30") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, -300, 1) == 0 && std::strcmp(precise, "2024-02-29T07:34:56.7-05:00") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 7) == invalidValue); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, -1) == invalidValue); + Require(sceRtcFormatRFC3339Precise(precise, &first, 1440, 0) == invalidValue); + Require(sceRtcFormatRFC3339Precise(precise, &first, -1440, 0) == invalidValue); + Require(sceRtcFormatRFC3339Precise(nullptr, &first, 0, 0) == invalidPointer); + Require(sceRtcFormatRFC3339Precise(precise, nullptr, 0, 6) == 0 && precise[4] == '-'); + SetTimeZone("UTC0"); + Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &first, 2) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.78Z") == 0); + Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &first, 7) == invalidValue); + Require(sceRtcFormatRFC3339PreciseLocalTime(nullptr, &first, 0) == invalidPointer); + Require(sceRtcCompareTick(&first, &second) == -1 && sceRtcCompareTick(&second, &first) == 1 && sceRtcCompareTick(&first, &first) == 0); + Require(sceRtcCompareTick(nullptr, &first) == invalidPointer && sceRtcCompareTick(&first, nullptr) == invalidPointer); + RtcTick current{}; + Require(sceRtcGetCurrentAdNetworkTick(¤t) == 0 && current.tick > unixEpochTick); + Require(sceRtcGetCurrentAdNetworkTick(nullptr) == invalidPointer); + RtcTick tooEarly{unixEpochTick - 1}; + Require(sceRtcSetCurrentTick(&tooEarly) == invalidValue && sceRtcSetCurrentTick(nullptr) == invalidPointer); + } } diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index 62ca3e81dcc7..c9eda0419c0d 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -7,3 +7,6 @@ - [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset) - [sceRtcGetDosTime, sceRtcSetDosTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - bit layout from shadPS4 (PS4, the standard FAT date/time). A year outside 1980-2107 makes `GetDosTime` return `INVALID_YEAR` after writing 0 for an earlier one and `0xff9fbf7d` (2107-12-31 23:59:58, the largest encodable time) for a later one, and `SetDosTime` decodes the year as the unsigned 7-bit FAT field (1980-2107) without validating the fields, as the disassembly-derived [fpPS4](https://github.com/red-prig/fpPS4) reimplementation of the PS4 library does; not confirmed on a PS5 title, and shadPS4 instead masks out-of-range years into the packed value and sign-extends the 16-bit date - [sceRtcSetYear, sceRtcSetMonth, sceRtcSetDay, sceRtcSetHour, sceRtcSetMinute, sceRtcSetSecond, sceRtcSetMicrosecond](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - component setters validate component boundaries independently without cross-field calendar validation +- [sceRtcGetCurrentRawNetworkTick, sceRtcGetCurrentAdNetworkTick, sceRtcGetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clocks 0x13, 0x12 and 0x11 (the ad and debug clocks fall back to the network clock 0x10) and returns the clock time plus the Unix epoch in ticks, or `0x80B50001` when the clock is unsupported (errno 5). Those console clocks are not modelled; all three return the system clock like `sceRtcGetCurrentNetworkTick`. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console +- [sceRtcSetCurrentTick, sceRtcSetCurrentNetworkTick, sceRtcSetCurrentAdNetworkTick, sceRtcSetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console rejects a null pointer with `0x80B50002` and a tick before the Unix epoch with `0x80B50003`, then sets the system clock (clock 0, 0x10, 0x12 or 0x11) from the tick. A valid tick throws: the host clock is not set. sceRtcSetConf passes its third and fourth arguments to the same clock call with clock 0 and throws; what the pair selects is unknown +- [sceRtcFormatRFC3339Precise, sceRtcFormatRFC3339PreciseLocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - read from the disassembly of libSceRtc.sprx on firmware 13.60: a null tick means the current tick, a precision above 6 or a zone outside -1439 to 1439 minutes returns `0x80B50003`, and the fraction is the microseconds truncated to the requested number of digits. Not compared with console output. The output buffer size is unknown; at most 33 bytes are written From 7037c08099f1a64a528e0b8375c955374b0f5229 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 15 Oct 2026 09:58:43 +0400 Subject: [PATCH 022/176] test(relinker): cover GS loads, stores and ALU reads trapped in macOS output macos_gs_traps converts the forms Wine's ntdll in Prospero Win (PPSA99995) uses, a store, an ALU read and a register-address form shorter than a jump, and checks that each becomes ud2 and NOP padding of its own length before the next instruction, with none of the original bytes left. (cherry picked from commit 8da24830ac932bb6573eeebae21492be8edd4e2a) --- core/relinker/CMakeLists.txt | 3 ++ .../relinker/tests/test_macos_gs_traps.py | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 core/relinker/relinker/tests/test_macos_gs_traps.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index f0af8bfa534b..cbd756a8092c 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -497,6 +497,9 @@ if(BUILD_TESTING) add_test(NAME macos_executable_exports COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_executable_exports.py $) set_tests_properties(macos_executable_exports PROPERTIES TIMEOUT 30) + add_test(NAME macos_gs_traps COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_gs_traps.py $) + set_tests_properties(macos_gs_traps PROPERTIES TIMEOUT 60) add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp) diff --git a/core/relinker/relinker/tests/test_macos_gs_traps.py b/core/relinker/relinker/tests/test_macos_gs_traps.py new file mode 100644 index 000000000000..19cca7ba4ef1 --- /dev/null +++ b/core/relinker/relinker/tests/test_macos_gs_traps.py @@ -0,0 +1,50 @@ +"""Check that `relinker --macos` traps guest GS accesses in place.""" +from pathlib import Path +import subprocess +import sys +import tempfile + +from test_macos_output import MachO +from test_tls_function_coverage import make_image + + +FORMS = { + "gs-load-rcx": bytes.fromhex("65 48 8b 0c 25 78 03 00 00"), + "gs-load-r13": bytes.fromhex("65 4c 8b 2c 25 30 00 00 00"), + "gs-store": bytes.fromhex("65 48 89 04 25 30 00 00 00"), + "gs-alu": bytes.fromhex("65 48 33 04 25 28 00 00 00"), + "gs-short-register-address": bytes.fromhex("65 48 8b 00"), +} + + +def convert(relinker, directory, name, image): + source = Path(directory) / f"{name}.elf" + output = Path(directory) / name + source.write_bytes(bytes(image)) + result = subprocess.run([str(relinker), "--macos", "--skip-sce-module", str(source), str(output)], + capture_output=True, text=True, timeout=60) + if result.returncode != 0: + raise SystemExit(f"{name}: relinker failed: {result.stdout}{result.stderr}") + data = output.read_bytes() + return b"".join(data[lo:hi] for lo, hi in MachO(data).executable_ranges()) + + +def check_trapped(name, code, instruction): + trapped = b"\x0f\x0b" + b"\x90" * (len(instruction) - 2) + b"\xc3" + if trapped not in code: + raise SystemExit(f"{name}: the GS access was not replaced by ud2 and NOP padding") + if instruction + b"\xc3" in code: + raise SystemExit(f"{name}: the GS access was left in the code") + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-macos-gs-") as directory: + for name, instruction in FORMS.items(): + image = make_image("register", "unwind", body=instruction + b"\xc3") + check_trapped(name, convert(relinker, directory, name, image), instruction) + print("macOS GS trap checks passed") + + +if __name__ == "__main__": + main() From d1cccb81031ae60fffa8939a812d979de6baab9d Mon Sep 17 00:00:00 2001 From: David Date: Thu, 15 Oct 2026 09:59:24 +0400 Subject: [PATCH 023/176] test(relinker): trap a GS access in a macOS module without a TLS segment The macOS writer sets up guest TLS only for a module with a non-empty PT_TLS. A GS access in a module without one must still be trapped. (cherry picked from commit 6f89fd706fe9b0173b3dd041ad6fde02d4bcffb2) --- core/relinker/relinker/tests/test_macos_gs_traps.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/relinker/relinker/tests/test_macos_gs_traps.py b/core/relinker/relinker/tests/test_macos_gs_traps.py index 19cca7ba4ef1..8b9c21512d29 100644 --- a/core/relinker/relinker/tests/test_macos_gs_traps.py +++ b/core/relinker/relinker/tests/test_macos_gs_traps.py @@ -1,5 +1,6 @@ """Check that `relinker --macos` traps guest GS accesses in place.""" from pathlib import Path +import struct import subprocess import sys import tempfile @@ -43,6 +44,10 @@ def main(): for name, instruction in FORMS.items(): image = make_image("register", "unwind", body=instruction + b"\xc3") check_trapped(name, convert(relinker, directory, name, image), instruction) + instruction = FORMS["gs-load-rcx"] + image = make_image("register", "unwind", body=instruction + b"\xc3") + struct.pack_into(" Date: Sun, 18 Oct 2026 18:23:43 +0530 Subject: [PATCH 024/176] fix(relinker): guard PLT compactor against displacement underflow and offset overflow (cherry picked from commit 477f8a9cb8055d9831a5fc3149202ef44b4be422) --- .../analysis/UnusedNidFilter/PltCompactor.cpp | 13 +++++- .../relinker/tests/test_optional_plt.py | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp index c21acd03ba55..6632f2e0ae7e 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp @@ -22,20 +22,29 @@ CompactedPlt CompactPlt(const std::vector& originalReferences, con if (reference.RelocationTypeValue != 7) continue; if (!originalSlots.contains(reference.RelocationAddress) || !newSlots.emplace(reference.RelocationAddress, result.SlotCount).second) throw RelinkerException("Strict filter: invalid retained PLT slot", reference.RelocationAddress); - reference.RelocationTableOffset = tableOffset + static_cast(result.SlotCount++) * 24; + const auto slotOffset = static_cast(result.SlotCount++) * 24; + if (tableOffset > std::numeric_limits::max() - slotOffset) + throw RelinkerException("Strict filter: PLT relocation table offset overflow", tableOffset); + reference.RelocationTableOffset = tableOffset + slotOffset; } std::unordered_set found; for (std::size_t offset = 0; offset + 16 <= text.size(); ++offset) { if (text[offset] != 0xFF || text[offset + 1] != 0x25 || text[offset + 6] != 0x68 || text[offset + 11] != 0xE9) continue; + if (offset + 6 > std::numeric_limits::max() - textVaddr) continue; + const auto next = textVaddr + offset + 6; std::int32_t displacement; std::memcpy(&displacement, text.data() + offset + 2, sizeof(displacement)); - const auto slot = textVaddr + offset + 6 + static_cast(static_cast(displacement)); + if (displacement < 0 && next < static_cast(-static_cast(displacement))) continue; + if (displacement > 0 && std::numeric_limits::max() - next < static_cast(displacement)) continue; + const auto slot = next + static_cast(static_cast(displacement)); const auto original = originalSlots.find(slot); if (original == originalSlots.end()) continue; std::uint32_t index; std::memcpy(&index, text.data() + offset + 7, sizeof(index)); if (index != original->second) throw RelinkerException("Strict filter: PLT thunk index disagrees with its relocation", textVaddr + offset); found.insert(slot); + if (textOffset > std::numeric_limits::max() - 7 || offset > std::numeric_limits::max() - textOffset - 7) + throw RelinkerException("Strict filter: PLT patch offset overflow", textOffset); if (const auto kept = newSlots.find(slot); kept != newSlots.end()) { std::vector bytes(sizeof(std::uint32_t)); std::memcpy(bytes.data(), &kept->second, bytes.size()); diff --git a/core/relinker/relinker/tests/test_optional_plt.py b/core/relinker/relinker/tests/test_optional_plt.py index 548197caa0aa..c258014fa931 100644 --- a/core/relinker/relinker/tests/test_optional_plt.py +++ b/core/relinker/relinker/tests/test_optional_plt.py @@ -51,6 +51,44 @@ def convert(name, tags=(), error=None): if executed.returncode != 42: raise AssertionError((name, executed.returncode, executed.stdout, executed.stderr)) + def convert_strict(name, thunks=(), error=None, invalid_slot=False): + code_base = 0x1000 + slot = 0x800 + if invalid_slot: + slot = 0x1000 + 0x20 + 6 - 0x2000 + (1 << 64) + image = bytearray(0x1200) + image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9) + struct.pack_into(" Date: Thu, 15 Oct 2026 11:45:50 +0200 Subject: [PATCH 025/176] fix(agc): store pending image results after the range's writers finish A flush stores a storage image's pending results to guest memory and keeps the 64 KiB blocks the write tracker shows written since the image was last in sync. An address-based dispatch marks the pages it stored to only when its batch's completion runs. A flush between the GPU's store and that completion found the blocks unwritten and stored the image's older texels over the newer bytes; a CPU-resolved indirect dispatch then read those texels as its workgroup counts. FlushPending now looks for an unfinished batch that writes the range or holds it in an address-based lease (Recorder::PendingWriterSerial). Batches that already finished are completed first. If the writer is still unfinished, the flush finishes the in-flight batches up to it, or syncs when the writer is in the open batch, so its marks land and its blocks are kept. Inside a completion action it does not wait. If the wait throws, the images it took stay registered as pending. The test stores an image, then a global dword into the image's memory, and flushes the image twice: once with the writer's batch finished on the GPU but its completion not run, and once with the writer still in the open batch. Each time the dword is kept and a texel in another block is stored. Without write watching the in-flight case is skipped; the open-batch case still runs, as the write-back then compares guest memory, which the GPU has not written yet at the flush. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 9fffceaa9a07a70d0fac7f8ad7a0af335343f412) --- .../Graphics/include/Recorder.hpp | 3 +- .../libSceAgcDriver/Graphics/src/Recorder.cpp | 16 +- .../libSceAgcDriver/Graphics/src/Texture.cpp | 14 ++ .../tests/execution/StorageFlushOrdering.cpp | 173 ++++++++++++++++++ 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index a58de74ce446..6d157e115117 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -159,6 +159,7 @@ class Recorder { static ReadStatistics ReadCounts(); // Whether the OPEN (unsubmitted) batch writes the range: a wait on such a range must submit it. bool OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) const; + std::uint64_t PendingWriterSerial(std::uint64_t address, std::size_t bytes) const; // What SyncThrough(address, bytes) would wait for, for the [hooksync] attribution: the newest // batch whose noted range overlaps the access (the open batch counts first, with the serial it // will get), whether that batch's fence has already signaled (its GPU work is done, so the @@ -744,7 +745,7 @@ class Recorder { void removeLabels(Batch& batch); void release(Batch& batch) noexcept; static bool overlaps(const Batch& batch, std::uint64_t address, std::uint64_t end); - static std::optional readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end); + static std::optional readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end, std::optional only = std::nullopt); static std::vector> notedReads(const Batch& batch); bool signaled(const Batch& batch) const; // Rebuilds the lock-free snapshot of pending writes from open, inFlight and finishing. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 1f7c2c59c78e..fe032bcb8d97 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -2748,6 +2748,17 @@ bool Recorder::OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) const return bytes != 0 && open != nullptr && overlaps(*open, address, address + bytes); } +std::uint64_t Recorder::PendingWriterSerial(std::uint64_t address, std::size_t bytes) const { + if (bytes == 0) return 0; + const auto end = address + bytes; + const auto writes = [&](const Batch& batch) { return overlaps(batch, address, end) || readOverlap(batch, address, end, ReadKind::AddressBased).has_value(); }; + if (open != nullptr && writes(*open)) return OpenSerial(); + for (auto it = inFlight.rbegin(); it != inFlight.rend(); ++it) { + if (writes(**it)) return (*it)->serial; + } + return 0; +} + bool Recorder::signaled(const Batch& batch) const { return batch.submitted && fenceStatus(batch.fence) == VK_SUCCESS; } @@ -2840,8 +2851,9 @@ void Recorder::NotePendingReadList(std::shared_ptr ranges, ReadK open->readLists.push_back({std::move(ranges), open->reads.size(), kind}); } -std::optional Recorder::readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end) { +std::optional Recorder::readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end, std::optional only) { const auto listOverlaps = [&](const Batch::ReadListNote& note) { + if (only.has_value() && note.kind != *only) return false; const auto& list = *note.ranges; const auto found = std::partition_point(list.begin(), list.end(), [&](const auto& range) { return range.second <= address; }); return found != list.end() && found->first < end; @@ -2852,7 +2864,7 @@ std::optional Recorder::readOverlap(const Batch& batch, std: if (listOverlaps(batch.readLists[next])) return batch.readLists[next].kind; } const auto& read = batch.reads[index]; - if (address < read.end && read.begin < end) return read.kind; + if ((!only.has_value() || read.kind == *only) && address < read.end && read.begin < end) return read.kind; } for (; next < batch.readLists.size(); ++next) { if (listOverlaps(batch.readLists[next])) return batch.readLists[next].kind; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 73dd489bfca8..05871bbc60d1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -3046,6 +3046,20 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons for (const auto& texture : flush) std::fprintf(stderr, "[flush] image 0x%llx+0x%llx for %s 0x%llx+0x%zx\n", static_cast(texture->descriptor.baseAddress), static_cast(texture->guestBytes), reason, static_cast(address), bytes); } std::lock_guard gpu(GuestMemory::GpuMutex()); + if (auto* recorder = Recorder::Active(); recorder != nullptr && !Recorder::InCompletion() && recorder->PendingWriterSerial(address, bytes) != 0) { + try { + recorder->Reap(); + if (const auto writer = recorder->PendingWriterSerial(address, bytes); writer == recorder->OpenSerial()) { + Recorder::CountSync(recorder->OpenWriteOverlaps(address, bytes) ? 1 : 3); + recorder->Sync(); + } else if (writer != 0) { + recorder->FinishUpTo(writer); + } + } catch (...) { + for (const auto& texture : flush) texture->reconcilePending(); + throw; + } + } std::exception_ptr failure; const auto previousReason = std::exchange(flushReason, reason); for (const auto& texture : flush) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp new file mode 100644 index 000000000000..ec90020045fa --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::size_t BlockBytes = 1u << 20u; +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::size_t ImageBytes = Width * Height * 4u; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t Initial = 0x3f800000u; +constexpr std::uint32_t Stored = 0x11111111u; +constexpr std::uint32_t Overwritten = 0x22222222u; +constexpr std::size_t OverwrittenDword = 0x100u / 4u; +constexpr std::size_t FarDword = 100u * Width; + +alignas(256) constexpr std::array StoreImageCode{0x7e000280u, 0x7e020280u, 0x7e0402ffu, Stored, 0xf0200108u, 0x00010200u, 0x7e0202ffu, 0x00000064u, 0xf0200108u, 0x00010200u, 0xbf810000u}; +alignas(256) constexpr std::array StoreGlobalCode{0x7e000208u, 0x7e020209u, 0x7e0402ffu, Overwritten, 0xdc708100u, 0x007d0200u, 0xbf810000u}; + +class GuestBlock { +public: + explicit GuestBlock(bool watched) : watched(watched) { +#ifdef _WIN32 + block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, PAGE_READWRITE, BlockBytes); +#else + constexpr std::uintptr_t alignment = 65536; + void* mapped = mmap(nullptr, BlockBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped != MAP_FAILED) { + const auto begin = reinterpret_cast(mapped); + const auto aligned = (begin + alignment - 1) & ~(alignment - 1); + if (aligned != begin) munmap(mapped, aligned - begin); + if (aligned + BlockBytes != begin + BlockBytes + alignment) munmap(reinterpret_cast(aligned + BlockBytes), begin + alignment - aligned); + block = reinterpret_cast(aligned); + if (watched) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, BlockBytes); + } +#endif + Require(block != nullptr, "storage flush ordering: cannot allocate the guest block"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), BlockBytes), "storage flush ordering: the guest block is not write-watched"); + std::fill(Words(), Words() + BlockBytes / 4u, Initial); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + AgcDriver::GuestMemory::CollectWritesUncached(Address(), BlockBytes); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, BlockBytes); + GuestArena::GuestArenaRelease_nid_postfix(block, BlockBytes); +#else + munmap(block, BlockBytes); + if (watched) GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(block, BlockBytes); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint32_t* Words() const { return reinterpret_cast(block); } + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + +private: + bool watched; + std::uint8_t* block = nullptr; +}; + +ShaderRecompiler::RecompileResult Compile(std::span code, std::span userData, const ShaderRecompiler::SpirvTarget& target) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {64u, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +std::vector ImageUserData(std::uint64_t base) { + std::vector userData(12, 0u); + userData[4] = static_cast(base >> 8u); + userData[5] = static_cast((base >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u); + userData[6] = ((Width - 1u) >> 2u) | ((Height - 1u) << 14u); + userData[7] = 0xfacu | (Type2D << 28u); + return userData; +} + +std::vector GlobalUserData(std::uint64_t base) { + std::vector userData(10, 0u); + userData[8] = static_cast(base); + userData[9] = static_cast(base >> 32u); + return userData; +} + +bool AddressBasedStorePending(std::uint64_t image, bool open) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + const auto read = recorder != nullptr ? recorder->DescribePendingRead(image + ImageBytes, 4u) : std::nullopt; + return read.has_value() && read->kind == AgcDriver::Graphics::Recorder::ReadKind::AddressBased && read->open == open; +} + +void Run(AgcDriver::VulkanDevice& device, const GuestBlock& guest, std::size_t offset, bool submit, const char* name) { + const auto* words = guest.Words() + offset / 4u; + const auto image = guest.Address() + offset; + const auto target = device.Target(); + const auto storeImage = Compile(StoreImageCode, ImageUserData(image), target); + const auto storeGlobal = Compile(StoreGlobalCode, GlobalUserData(image), target); + device.Dispatch(storeImage, 1u, 1u, 1u, {}, reinterpret_cast(StoreImageCode.data())); + device.Dispatch(storeGlobal, 1u, 1u, 1u, {}, reinterpret_cast(StoreGlobalCode.data())); + if (submit) { + std::uint64_t serial = 0; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + serial = device.SubmitAndEpoch(); + } + device.WaitRecorded(serial); + } + const std::string what = std::string("storage flush ordering, ") + name + ": "; + Require(AddressBasedStorePending(image, !submit), what + "the address-based store was not pending in the expected batch at the flush"); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(image, ImageBytes, nullptr, "test"), what + "the image store left no results pending"); + device.WaitIdle(); + Require(words[FarDword] == Stored, what + "the pending image's texel away from the newer write was not stored"); + Require(words[OverwrittenDword] == Overwritten, what + "the pending image's store put its older texels over a newer address-based store"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool watched = AgcDriver::GuestMemory::WriteWatched(); + GuestBlock guest(watched); + if (!watched) std::puts("guest memory has no write watch: the writer-in-flight case is skipped"); + else if (!device->CanWaitUnlocked()) std::puts("the recorder cannot wait for the GPU without running completions: the writer-in-flight case is skipped"); + else Run(*device, guest, 0, true, "writer in flight"); + Run(*device, guest, BlockBytes / 2u, false, "writer in the open batch"); + std::puts("storage flush ordering tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 3fc8b2b89a9777caed5c9cadd9be59219e7a6166 Mon Sep 17 00:00:00 2001 From: Yuri Olive Date: Thu, 15 Oct 2026 19:24:39 -0300 Subject: [PATCH 026/176] feat(libScePad): export missing firmware pad interfaces (cherry picked from commit b27e4c971008bfe0e742a216bb02edba06f5d40c) --- core/libs/prx/libScePad/Export.cpp | 745 +++++++++++++++++++++++++++++ 1 file changed, 745 insertions(+) diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index 667dfcff49cc..b9249868ed52 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -1071,4 +1071,749 @@ int APS5_VABI scePadControllerUpdateTerm(void) { return 0; } +int APS5_VABI scePadSetTriggerEffectExt(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadLastLotusButtonData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadReadStateExt(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetExtensionReport(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetExtensionUnitInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnableExtensionPort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnableSpecificDeviceClass(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadOpenExt2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDeviceOpen(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetDataInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetUserColor(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableVibration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetVibrationStrength(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLightBarBaseBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLightBarBlinking(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetIdleCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetControllerIdleCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetPowerSavingState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetAutoPowerOffCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnableUsbConnection(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnableAutoDetect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnablePlayPauseEmulation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableTrackingLedBatterySaver(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLoginUserNumber(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetCapability(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadShareOutputData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadShareOutputDataWithFunctionType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetVibrationForce(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetVersionInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetControllerType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableDataConversion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetButtonRemappingInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetProcessPrivilegeOfButtonRemapping(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetForceIntercepted(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsLightBarBaseBrightnessControllable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSwitchConnection(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableTriggerEffect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetTriggerEffectStrength(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetTriggerEffectInternalState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetUserNumber(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableTransportButtons(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLotusIrCodeSetStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLotusIrCodeSetData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLotusIrCodeSetEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetLotusIrCodeSetResult(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLotusEnableIrKey(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLotusDisableIrKey(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetUsbDetectionState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadUiReadState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadUiReadStateForTuning(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetStickTriggerTuningMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetStickTuningParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetTriggerTuningParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetProfileHunt(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetProfileHunt(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadResetHunt(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetProfileInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetActiveProfileInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableActiveProfileChange(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisableActiveProfileChangeByDeviceId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetActiveProfileSlot(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetSavedSettingInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetSavedSettingInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVrControllerGetPhysicalUsbConnectionStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadParseUniqueDataLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetControllerTypeLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetControllerTypeLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetProfileLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetProfileLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadResetLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetProfileEditMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetProfileEditMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetProfileEditModeLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetSelectButtonCustomizeMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetSelectButtonCustomizeMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetToggleAllReleaseLeonardo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnableControllerIndicator(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnableProfileIndicator(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetProfileIndicatorAnimation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsCoPilot(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetIsCoPilot(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadRegisterCollaboInputMgrProcess(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetTelemetryInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetActiveTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetDeviceId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetInfoByPortType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadResetLightBarAll(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadResetLightBarAllByPortType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetLightBarForTracker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadReadForTracker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadReadBlasterForTracker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadResetOrientationForTracker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetMotionTimerUnit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetVrTrackingMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetMotionSensorPosition(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetSphereRadius(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsValidHandle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsDS4Connected(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsBlasterConnected(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsMoveConnected(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetBluetoothAddress(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadIsMoveReproductionModel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadReadAstonForTracker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetAstonModel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadMbusInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadMbusTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadConnectPort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisconnectPort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadDisconnectDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetDeviceInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetProcessFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVertualDeviceAddDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceAddDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceDeleteDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceInsertData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceInsertTriggerInternalState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceGetRemoteSetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceSetTriggerEffectSetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadVirtualDeviceDisableButtonRemapping(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetShareButtonMaskForRemotePlay(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetPS4BcVibrationMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetVirtualDeviceComponentInformation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadEnablePs4CompatibleMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetLicenseControllerInformation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadReadHistory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadStartRecording(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadStopRecording(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadRemoteControlInsertData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadRemoteControlInsertDataForAston(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadGetFeatureReport2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetFeatureReport2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadSetMuteLed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateIsReady(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateGetStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateSendData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateResetBond(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateGetReconnectStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadBondUpdateTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateIsUpdateNeeded2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateIsReady(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateGetStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateSendData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateResetController(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateGetReconnectStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePadControllerUpdateTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } From 06668df277650bea4c0685299ed6837bcf1ccea7 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 08:42:35 +0400 Subject: [PATCH 027/176] fix(libSceNetCtl): read the default route from the routing table on macOS sceNetCtlGetInfo found the host's default route in /proc/net/route, which macOS does not have, so every IPv4 query threw "the host routing table is unavailable" there. On macOS it now reads the IPv4 routes that have a gateway with sysctl(NET_RT_FLAGS, RTF_GATEWAY) and keeps those to 0.0.0.0 that are up, neither rejecting nor host routes, on an interface that still exists. An interface-scoped default route ranks after the unscoped one, the route macOS sends traffic by. The parsing is exported as NetCtlDefaultRoutes_nid_no_patch so it can be tested on a routing table built by hand. (cherry picked from commit 8af08029b8a578b750744cb22651fcdb8fcb1030) --- core/libs/prx/libSceNetCtl/Export.cpp | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index a7a5998b4279..8779ff212727 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -15,6 +15,9 @@ #ifdef __linux__ #include #endif +#ifdef __APPLE__ +#include +#endif #endif #include #include @@ -170,6 +173,56 @@ struct HostDefaultRoute { unsigned long metric = 0; }; +#ifdef __APPLE__ +struct NetCtlDefaultRoute { + char name[IF_NAMESIZE]; + in_addr gateway; + std::uint32_t metric; +}; + +extern "C" std::size_t NetCtlDefaultRoutes_nid_no_patch(const char* table, std::size_t size, NetCtlDefaultRoute* routes, std::size_t capacity) { + std::size_t count = 0; + for (std::size_t offset = 0; offset + sizeof(rt_msghdr) <= size;) { + const auto* header = reinterpret_cast(table + offset); + if (header->rtm_msglen < sizeof(rt_msghdr) || header->rtm_msglen > size - offset) break; + const sockaddr* addresses[RTAX_MAX] = {}; + const std::size_t end = offset + header->rtm_msglen; + std::size_t field = offset + sizeof(rt_msghdr); + for (int index = 0; index < RTAX_MAX && field + sizeof(sockaddr) <= end; ++index) { + if (!(header->rtm_addrs & (1 << index))) continue; + const auto* address = reinterpret_cast(table + field); + addresses[index] = address; + field += address->sa_len ? (address->sa_len + sizeof(std::uint32_t) - 1) & ~(sizeof(std::uint32_t) - 1) : sizeof(std::uint32_t); + } + offset = end; + const auto* destination = addresses[RTAX_DST]; + const auto* gateway = addresses[RTAX_GATEWAY]; + if (!destination || destination->sa_family != AF_INET || !gateway || gateway->sa_family != AF_INET) continue; + if (reinterpret_cast(destination)->sin_addr.s_addr != 0) continue; + if (!(header->rtm_flags & RTF_UP) || (header->rtm_flags & (RTF_REJECT | RTF_HOST))) continue; + NetCtlDefaultRoute route{}; + if (!if_indextoname(header->rtm_index, route.name)) continue; + route.gateway = reinterpret_cast(gateway)->sin_addr; + route.metric = (header->rtm_flags & RTF_IFSCOPE) ? 1 : 0; + if (count < capacity) routes[count] = route; + ++count; + } + return count; +} + +static std::vector HostDefaultRoutes() { + int name[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_GATEWAY}; + std::size_t size = 0; + if (sysctl(name, 6, nullptr, &size, nullptr, 0) != 0) throw std::runtime_error("sceNetCtlGetInfo: the host routing table is unavailable"); + std::vector table(size); + if (sysctl(name, 6, table.data(), &size, nullptr, 0) != 0) throw std::runtime_error("sceNetCtlGetInfo: the host routing table is unavailable"); + std::vector found(NetCtlDefaultRoutes_nid_no_patch(table.data(), size, nullptr, 0)); + found.resize(NetCtlDefaultRoutes_nid_no_patch(table.data(), size, found.data(), found.size())); + std::vector routes; + for (const auto& entry : found) routes.push_back({entry.name, entry.gateway, entry.metric}); + return routes; +} +#else static std::vector HostDefaultRoutes() { std::ifstream table("/proc/net/route"); if (!table) throw std::runtime_error("sceNetCtlGetInfo: the host routing table is unavailable"); @@ -192,6 +245,7 @@ static std::vector HostDefaultRoutes() { } return routes; } +#endif static std::uint32_t HostInterfaceMtu(const char* name) { const int descriptor = socket(AF_INET, SOCK_DGRAM, 0); From 3d3322790e41a8aa6d9eb8f7cfc330a14dd4e91d Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 08:42:35 +0400 Subject: [PATCH 028/176] test(libSceNetCtl): check which macOS routing messages give a default route A routing table built by hand with an unscoped default route, a host route, a host route to 0.0.0.0, a rejecting default route, an interface-scoped default route, one that is down, one on an interface that does not exist and a truncated last message must give the unscoped route first and the scoped one after it, and must count every match while writing only as many as fit. (cherry picked from commit 6d4cde8e40fe3cd2023540e54da2c60e66290925) --- core/libs/tests/GuestNetCtl.cpp | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/core/libs/tests/GuestNetCtl.cpp b/core/libs/tests/GuestNetCtl.cpp index ad3bfb9ec6b8..9203f1c97239 100644 --- a/core/libs/tests/GuestNetCtl.cpp +++ b/core/libs/tests/GuestNetCtl.cpp @@ -8,6 +8,11 @@ #include #include #endif +#ifdef __APPLE__ +#include +#include +#include +#endif #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -18,10 +23,21 @@ #include #include +#ifdef __APPLE__ +struct NetCtlDefaultRoute { + char name[IF_NAMESIZE]; + in_addr gateway; + std::uint32_t metric; +}; +#endif + extern "C" { int APS5_VABI sceNetCtlGetInfo(int, NetCtlInfo*); int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo*); int APS5_VABI sceNetCtlGetState(int*); +#ifdef __APPLE__ +std::size_t NetCtlDefaultRoutes_nid_no_patch(const char*, std::size_t, NetCtlDefaultRoute*, std::size_t); +#endif } namespace { @@ -85,10 +101,66 @@ bool Binds(in_addr address) { } +#ifdef __APPLE__ +void AppendRoute(std::vector& table, std::uint32_t destination, std::uint32_t gateway, int flags, unsigned short index) { + rt_msghdr header{}; + header.rtm_msglen = static_cast(sizeof(header) + 2 * sizeof(sockaddr_in) + sizeof(std::uint32_t)); + header.rtm_version = RTM_VERSION; + header.rtm_type = RTM_GET; + header.rtm_index = index; + header.rtm_flags = flags; + header.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; + sockaddr_in addresses[2]{}; + for (auto& address : addresses) { + address.sin_len = sizeof(address); + address.sin_family = AF_INET; + } + addresses[0].sin_addr.s_addr = htonl(destination); + addresses[1].sin_addr.s_addr = htonl(gateway); + const std::uint32_t emptyMask = 0; + const auto* bytes = reinterpret_cast(&header); + table.insert(table.end(), bytes, bytes + sizeof(header)); + bytes = reinterpret_cast(addresses); + table.insert(table.end(), bytes, bytes + sizeof(addresses)); + bytes = reinterpret_cast(&emptyMask); + table.insert(table.end(), bytes, bytes + sizeof(emptyMask)); +} + +void CheckDefaultRoutes() { + const auto loopback = static_cast(if_nametoindex("lo0")); + Require(loopback != 0); + constexpr int up = RTF_UP | RTF_GATEWAY | RTF_STATIC; + std::vector table; + AppendRoute(table, 0, 0x0A000001, up, loopback); + AppendRoute(table, 0x01020304, 0x0A000009, up | RTF_HOST, loopback); + AppendRoute(table, 0, 0x0A000009, up | RTF_HOST, loopback); + AppendRoute(table, 0, 0x0A000009, up | RTF_REJECT, loopback); + AppendRoute(table, 0, 0x0A000002, up | RTF_IFSCOPE, loopback); + AppendRoute(table, 0, 0x0A000009, RTF_GATEWAY | RTF_STATIC, loopback); + AppendRoute(table, 0, 0x0A000009, up, 0xFFFF); + const std::size_t complete = table.size(); + AppendRoute(table, 0, 0x0A000009, up, loopback); + table.resize(table.size() - sizeof(std::uint32_t)); + + NetCtlDefaultRoute routes[4]{}; + Require(NetCtlDefaultRoutes_nid_no_patch(table.data(), table.size(), routes, 4) == 2); + Require(std::strcmp(routes[0].name, "lo0") == 0 && ntohl(routes[0].gateway.s_addr) == 0x0A000001 && routes[0].metric == 0); + Require(std::strcmp(routes[1].name, "lo0") == 0 && ntohl(routes[1].gateway.s_addr) == 0x0A000002 && routes[1].metric == 1); + + NetCtlDefaultRoute first[1]{}; + Require(NetCtlDefaultRoutes_nid_no_patch(table.data(), complete, first, 1) == 2); + Require(ntohl(first[0].gateway.s_addr) == 0x0A000001); + Require(NetCtlDefaultRoutes_nid_no_patch(table.data(), 0, nullptr, 0) == 0); +} +#endif + int main() { #ifdef _WIN32 WSADATA data{}; Require(WSAStartup(MAKEWORD(2, 2), &data) == 0); +#endif +#ifdef __APPLE__ + CheckDefaultRoutes(); #endif NetCtlInfo info{}; Require(sceNetCtlGetInfo(0, &info) == kErrInvalidCode); From 67f602687a935346d57329c23bfd13f5f1922f4d Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:54:42 +0000 Subject: [PATCH 029/176] fix(agc): leave a stage the title's pipeline ABI cannot prepare to its first use sceAgcDriverResolveShaderAbi and sceAgcDriverResolveGraphicsStagesAbi prepare every stage when the title creates its pipeline, and threw when the recompiler could not: Resident Evil Village stops there on a pixel shader whose T# pointer is a phi of user data and a ballot, and a compute shader that calls user-data pointers stopped the same way although its dispatch links the callees. ResolveShaderAbi and PrepareGraphicsStages now catch the failure inside their own preparation transaction, so the ShaderPreparationScope that libSceAgc opens around the call still commits. The shader is logged once, marked deferred like a shader deferred at registration, and the ABI key is recorded, so a repeated call prepares nothing. The draw or dispatch that uses the stage prepares it as before and fails there if it cannot. ResolvePreparedGraphics skips the rectangles of a deferred stage that has no artifact yet, which also lets a stage deferred at registration through sceAgcDriverResolveGraphicsAbi. Driver failures, ProcessShutdown and a failure stored by the shader's registration preparation still propagate. (cherry picked from commit 8c55d15aa4fb920227b9f4ab43aae7290fc7d682) --- .../include/Driver/Draw/DrawCache.hpp | 2 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 30 ++++++++++++++++--- docs/dev/ARCHITECTURE.md | 1 + 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp index 31f580396430..74207caed600 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -40,7 +40,7 @@ struct PreparedGraphicsStage { PreparedShaders::Entry entry; }; -std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target); +std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared = false); struct DrawRecipeRecord { std::vector> stages; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index d34af3102a67..38942993feed 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -262,6 +262,7 @@ void ShaderPreparationTransaction::Commit() { destination.rectangleProgress.swap(change.prepared.rectangleProgress); destination.fragments.swap(change.prepared.fragments); std::swap(destination.rectangleRequested, change.prepared.rectangleRequested); + destination.deferred = destination.deferred || change.prepared.deferred; } } committed = true; @@ -745,6 +746,11 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap return plan != nullptr ? PrepareRegistered(*plan) : std::vector{}; } +void DeferPipelineStage(PreparedShaderState& prepared, const ShaderSnapshot& snapshot, const char* reason) { + if (!prepared.deferred) APS5_LOG_ERR("Shader 0x%llx could not be prepared when the title created its pipeline (%s); preparing it when it is used", static_cast(snapshot.codeAddress), reason); + prepared.deferred = true; +} + #ifdef __GLIBC__ void trimFreedMemory() { static std::atomic last{0}; @@ -998,7 +1004,7 @@ unsigned RegistrationPreparationThreads() { return RegistrationPreparePool::LiveWorkers(); } -std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target) { +std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared) { ShaderPreparationTransaction transaction; require(decoded.programs.size() == decoded.roles.size(), "graphics ABI program roles are incomplete"); for (const auto& program : decoded.programs) require(program.snapshot != nullptr, "graphics ABI has no registered shader snapshot"); @@ -1048,7 +1054,14 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } if (handle == nullptr) { PerformanceTimer timing("Shader.PrepareArtifact"); - handle = PrepareShaderWithDiagnostics(request); + try { + handle = PrepareShaderWithDiagnostics(request); + } catch (const std::exception& error) { + if (!deferUnprepared) throw; + DeferPipelineStage(transaction.Edit(*program.snapshot), *program.snapshot, error.what()); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); + continue; + } } const auto bytes = handle->artifact->bindings.pushConstantSizeBytes; require(bytes <= room, "prepared graphics stages exceed the push constant block"); @@ -1112,8 +1125,9 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std DrawDecode decoded{}; decoded.state.stages = Graphics::DecodeShaderStages(state); DecodeGraphicsPrograms(decoded, state, *registry, true, false); - auto prepared = PrepareGraphicsStages(decoded, localDevice->Target()); + auto prepared = PrepareGraphicsStages(decoded, localDevice->Target(), true); for (auto& stage : prepared) { + if (stage.entry.handle == nullptr) continue; const auto& entries = transaction.Read(*stage.snapshot).entries; const auto duplicate = std::ranges::any_of(entries, [&](const auto& existing) { return existing.codeOffset == stage.entry.codeOffset && existing.handle->artifact == stage.entry.handle->artifact; }); if (!duplicate) transaction.Edit(*stage.snapshot).entries.push_back(std::move(stage.entry)); @@ -1162,17 +1176,24 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span, RegisteredAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); + std::optional deferral; const auto& registeredAbis = transaction.Read(*snapshot).registeredAbis; if (std::ranges::find(registeredAbis, key) != registeredAbis.end()) { transaction.Commit(); return; } - auto entries = PrepareRegistered(*snapshot, *localDevice, state, false); + std::vector entries; + try { + entries = PrepareRegistered(*snapshot, *localDevice, state, false); + } catch (const std::exception& error) { + deferral = error.what(); + } auto& prepared = transaction.Edit(*snapshot); for (auto& entry : entries) { const auto duplicate = std::ranges::any_of(prepared.entries, [&](const auto& existing) { return existing.handle->artifact == entry.handle->artifact; }); if (!duplicate) prepared.entries.push_back(std::move(entry)); } + if (deferral) DeferPipelineStage(prepared, *snapshot, deferral->c_str()); prepared.registeredAbis.push_back(key); transaction.Commit(); } @@ -1251,6 +1272,7 @@ void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr< for (const auto& pixel : fragments) { const auto& current = transaction.Read(front); const auto& pixelPrepared = transaction.Read(*pixel); + if ((current.entries.empty() && current.deferred) || (pixelPrepared.entries.empty() && pixelPrepared.deferred)) continue; require(!current.entries.empty() && !pixelPrepared.entries.empty(), "rectangle ABI has missing stage artifacts"); const auto progress = std::ranges::find_if(current.rectangleProgress, [&](const auto& entry) { return entry.fragment.lock() == pixel; }); const auto vertexCount = progress == current.rectangleProgress.end() ? 0u : progress->vertexCount; diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 96b44b2fdb27..d034ea364755 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -93,6 +93,7 @@ flowchart LR - The shader registry maps a code address to every header registered for that code, in registration order. Static, graphics and rectangle ABI resolution pick the header at the bound address (or one equal to it, `SameHeader`, which also takes a hull header fused by `sceAgcFuseShaderHalves` (type 3) as the registered back half (type 7) it copies, the fused registers aside; graphics ABI resolution then uses the fused header's shader registers); draws and dispatches pick the program by the stage's binary type (`RegisteredProgram`), and a front entry matching both type 2 and type 4 throws. Within a type the last registration wins; re-registering an identical earlier header makes its snapshot current again without preparing it again, and changed code drops the older headers. - The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead. - A draw or dispatch whose state matches no artifact prepared at registration (a title that builds its static ABI state itself, without `sceAgcDriverResolve*Abi`) gets one prepared at use by `SourceHandleFor` or `InvocationFor`, logged once per artifact and kept in the snapshot for the next use. +- A stage that `sceAgcDriverResolve*Abi` cannot prepare for the state the title creates its pipeline with (a compute shader that calls user-data pointers, a descriptor the resource plan rejects) is left to its first use like a stage deferred at registration: `ResolveShaderAbi` and `PrepareGraphicsStages` catch the failure inside their own preparation transaction, so the `ShaderPreparationScope` the title's call runs in still commits, log it once per shader, mark the shader deferred and record the ABI key, so a repeated call prepares nothing; `ResolvePreparedGraphics` skips a deferred stage's rectangles until a draw has prepared it. A draw or dispatch that cannot prepare the stage either fails as before. Driver failures, `ProcessShutdown` and a failure stored by the shader's registration preparation still propagate from the ABI call. - A compute shader whose `s_swappc_b64` calls a user-data pointer cannot be linked at registration, because the callee's address is only known at dispatch. `InvocationFor` in [`ShaderRegistry.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) reads each callee from guest memory, appends one copy per call after the caller (`LinkUserDataCalls`) and prepares that linked code, so a different callee gets its own prepared entry. - A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking, `VK_EXT_extended_dynamic_state` and `VK_KHR_dynamic_rendering`, draws render with `vkCmdBeginRenderingKHR` instead of render passes and the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend, vertex layout or target format builds only that part (the shader libraries name no attachment format), and cull mode, front face, depth and stencil state are dynamic, so changing them only links. The fast link is what the draw waits for; a background thread then links the same libraries with link-time optimization and the pipeline binds that one from the next draw on. Push constants then use a 256-byte block with a 128-byte slot per side: the pre-rasterization stages stack from byte 0, the pixel shader always starts at byte 128 (`StagePushOffset`), so its library does not depend on how much push data its vertex shader uses. Pipelines with per-face depth bias, a separate alpha blend into an 8_8 target with the alternate component order (both draw twice), rect lists, geometry shaders or a stage without a variant id are created whole, still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines. - A draw whose `PA_SC_AA_CONFIG` selects 2, 4 or 8 samples renders into host-only multisampled images. [`MultisampleTarget.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) keeps one image per device, surface address, format, extent and sample count (`MultisampleTargetView`); the pipeline, render pass and library keys carry `State::samples`, and a multisampled depth target is a multisampled depth surface. The guest sample planes and FMASK are never read or written, so the registry also guards the target's guest range: a CPU read (`GuestMemory::SetReadGuard`), a DMA copy, and a sampled or storage image read of it throw. A `CB_COLOR_CONTROL` resolve mode over a multisampled CB0 runs as a render pass whose resolve attachment is CB1's resident image (`ResolveMultisampleTarget`). A zero fill of the target's CMASK (`NoteColorMetadataFill`, from fill kernels and CP DMA fills) clears the image at its next use. From e88ce1296e587e4f85e1bcde996145b13427f830 Mon Sep 17 00:00:00 2001 From: "m2 (AI Agent)" Date: Fri, 16 Oct 2026 02:43:49 +0200 Subject: [PATCH 030/176] fix(shader): ballot each predicate once per block Every OpGroupNonUniformBallot now goes through SpirvModule::AddSubgroupBallot. A repeated ballot of the same predicate in the same block becomes an OpCopyObject of the first ballot. Within one block the active invocations do not change, so the result is identical. The cache is cleared at every label, function boundary, function call and demote, and helper functions keep their own cache. When SPIRV-Tools is enabled, block merging and specialization can still put two ballots of one predicate into the same block after emission, so ValidateAndOptimizeSpirv collapses repeated block ballots the same way on every module it returns, optimized or not. NVIDIA 580's SPIR-V compiler segfaulted (libnvidia-gpucomp+0xe3facc) on GTA V compute shaders that ballot one predicate many times in a block. One such shader had 163 repeated ballots across three blocks. With them collapsed, both crashing captures (compute-fe506552cec99bfc and compute-c02e5e3f35010fa5) compile. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit d83f541c54a9f55965ad0af362e4be292eca4b07) --- .../prx/libSceAgcDriver/tests/SpirvModule.cpp | 59 ++++++++++++ .../libSceAgcDriver/tests/SpirvOptimizer.cpp | 92 +++++++++++++++++++ core/libs/tests/Wave32WideSubgroup.cpp | 1 + .../include/SpirvBackend/SpirvModule.hpp | 7 ++ .../SpirvBackend/src/SpirvEmitter.cpp | 2 +- .../src/SpirvMemory/SpirvSubgroup.cpp | 2 +- .../SpirvBackend/src/SpirvModule.cpp | 29 ++++++ .../SpirvBackend/src/SpirvOptimizer.cpp | 50 +++++++++- 8 files changed, 238 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp index 791036b03795..ce8c173969d7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp @@ -373,6 +373,64 @@ void testIdsBeyondTheBoundAreRejected() { check(rejected, "a module whose ids reach its bound was specialized"); } +void testBlockBallotsOfOnePredicateShareOneBallot() { + SpirvModule module; + const auto typeBool = module.Type(spv::OpTypeBool); + const auto typeU32 = module.Type(spv::OpTypeInt, 32u, 0u); + const auto typeBallot = module.Type(spv::OpTypeVector, typeU32, 4u); + const auto typeVoid = module.Type(spv::OpTypeVoid); + const auto typeFunction = module.Type(spv::OpTypeFunction, typeVoid); + const auto scope = module.Constant(spv::OpConstant, typeU32, static_cast(spv::ScopeSubgroup)); + const auto truth = module.Constant(spv::OpConstantTrue, typeBool); + const auto other = module.Constant(spv::OpConstantFalse, typeBool); + const auto helper = module.AllocateId(); + const auto main = module.AllocateId(); + const auto first = module.AllocateId(); + const auto repeated = module.AllocateId(); + const auto distinct = module.AllocateId(); + const auto inHelper = module.AllocateId(); + const auto afterHelper = module.AllocateId(); + const auto afterCall = module.AllocateId(); + const auto afterLabel = module.AllocateId(); + const auto next = module.AllocateId(); + module.AddFunction(spv::OpFunction, typeVoid, main, spv::FunctionControlMaskNone, typeFunction); + module.AddFunction(spv::OpLabel, module.AllocateId()); + module.AddSubgroupBallot(typeBallot, first, scope, truth); + module.AddSubgroupBallot(typeBallot, repeated, scope, truth); + module.AddSubgroupBallot(typeBallot, distinct, scope, other); + module.BeginHelperFunction(); + module.AddFunction(spv::OpFunction, typeVoid, helper, spv::FunctionControlMaskNone, typeFunction); + module.AddFunction(spv::OpLabel, module.AllocateId()); + module.AddSubgroupBallot(typeBallot, inHelper, scope, truth); + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + module.EndHelperFunction(); + module.AddSubgroupBallot(typeBallot, afterHelper, scope, truth); + module.AddFunction(spv::OpFunctionCall, typeVoid, module.AllocateId(), helper); + module.AddSubgroupBallot(typeBallot, afterCall, scope, truth); + module.AddFunction(spv::OpBranch, next); + module.AddFunction(spv::OpLabel, next); + module.AddSubgroupBallot(typeBallot, afterLabel, scope, truth); + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + const auto words = module.Finalize(); + std::map ballotSources; + std::map copySources; + for (std::size_t offset = 5; offset < words.size(); offset += words[offset] >> spv::WordCountShift) { + if ((words[offset] >> spv::WordCountShift) == 0u) break; + const auto opcode = words[offset] & 0xffffu; + if (opcode == spv::OpGroupNonUniformBallot) ballotSources[words[offset + 2u]] = words[offset + 4u]; + if (opcode == spv::OpCopyObject) copySources[words[offset + 2u]] = words[offset + 3u]; + } + check(ballotSources.size() == 5u, "a block did not ballot each predicate exactly once"); + check(copySources.size() == 2u && copySources.contains(repeated) && copySources.contains(afterHelper), "a repeated ballot in one block was not a copy of the first"); + check(copySources[repeated] == first && copySources[afterHelper] == first, "a repeated ballot did not copy the block's first ballot"); + check(ballotSources[distinct] == other, "a ballot of another predicate was reused"); + check(ballotSources.contains(inHelper), "a helper function reused a ballot of the function it interrupted"); + check(ballotSources.contains(afterCall), "a ballot after a function call reused one from before the call"); + check(ballotSources.contains(afterLabel), "a ballot in a later block reused one from an earlier block"); +} + int main() { testUnusedHelperFunctions(); testFoldingLeavesNoDeadBlocksOrConstants(); @@ -388,6 +446,7 @@ int main() { testModulesAreIndependent(); testIdsStayUniqueBeyondCacheCapacity(); testEmittedWordsAreStable(); + testBlockBallotsOfOnePredicateShareOneBallot(); if (failures != 0) { std::fprintf(stderr, "%d check(s) failed\n", failures); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp index 52e11768a621..24b46ca1f06a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp @@ -535,6 +535,97 @@ OpDecorate %bytes Binding 1 else check(adds >= blocks && adds <= blocks * 4, "control-flow optimization lost arithmetic or retained redundant values"); } +std::size_t repeatedBlockBallots(const std::vector& words) { + std::size_t repeats = 0; + std::set> seen; + for (std::size_t offset = 5; offset < words.size(); offset += words[offset] >> spv::WordCountShift) { + const auto opcode = words[offset] & spv::OpCodeMask; + if (opcode == spv::OpLabel || opcode == spv::OpFunctionCall) seen.clear(); + if (opcode == spv::OpGroupNonUniformBallot && !seen.insert({words[offset + 1], words[offset + 3], words[offset + 4]}).second) ++repeats; + } + return repeats; +} + +std::string ballotModuleSource(const std::string& body) { + return R"(OpCapability Shader +OpCapability GroupNonUniformBallot +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 32 1 1 +OpDecorate %runtime ArrayStride 16 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %buffer DescriptorSet 0 +OpDecorate %buffer Binding 0 +%void = OpTypeVoid +%function = OpTypeFunction %void +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uvec4 = OpTypeVector %uint 4 +%runtime = OpTypeRuntimeArray %uvec4 +%block = OpTypeStruct %runtime +%blockPointer = OpTypePointer StorageBuffer %block +%elementPointer = OpTypePointer StorageBuffer %uvec4 +%buffer = OpVariable %blockPointer StorageBuffer +%zero = OpConstant %uint 0 +%one = OpConstant %uint 1 +%two = OpConstant %uint 2 +%three = OpConstant %uint 3 +%subgroup = OpConstant %uint 3 +%helper = OpFunction %void None %function +%helperEntry = OpLabel +OpReturn +OpFunctionEnd +%main = OpFunction %void None %function +%entry = OpLabel +%slot0 = OpAccessChain %elementPointer %buffer %zero %zero +%slot1 = OpAccessChain %elementPointer %buffer %zero %one +%slot2 = OpAccessChain %elementPointer %buffer %zero %two +%slot3 = OpAccessChain %elementPointer %buffer %zero %three +%loaded = OpLoad %uvec4 %slot0 +%value = OpCompositeExtract %uint %loaded 0 +%predicate = OpINotEqual %bool %value %zero +%other = OpIEqual %bool %value %one +)" + body + "OpReturn\nOpFunctionEnd\n"; +} + +void testBallotsMergedIntoOneBlockShareOneBallot(bool optimizationEnabled) { + const auto merged = assemble(ballotModuleSource(R"(%first = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot1 %first +OpBranch %next +%next = OpLabel +%second = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot2 %second +)")); + const auto output = ShaderRecompiler::ValidateAndOptimizeSpirv(merged, Vulkan11, Spirv13, false); + check(repeatedBlockBallots(output) == 0, "a block merged by the optimizer ballots one predicate twice"); + check(opcodeCount(output, spv::OpStore) == 2, "ballot collapse lost a store"); + if (optimizationEnabled) check(opcodeCount(output, spv::OpGroupNonUniformBallot) == 1, "blocks merged by the optimizer kept both ballots"); + + const auto oneBlock = assemble(ballotModuleSource(R"(%first = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +%different = OpGroupNonUniformBallot %uvec4 %subgroup %other +%second = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot1 %first +OpStore %slot2 %different +OpStore %slot3 %second +)")); + for (const bool optimize : {false, true}) { + const auto collapsed = ShaderRecompiler::ValidateAndOptimizeSpirv(oneBlock, Vulkan11, Spirv13, false, optimize); + const auto label = std::string(optimize ? "optimized" : "unoptimized"); + check(repeatedBlockBallots(collapsed) == 0, label + ": one block ballots one predicate twice"); + check(opcodeCount(collapsed, spv::OpGroupNonUniformBallot) == 2, label + ": a different predicate lost its ballot"); + } + + const auto called = assemble(ballotModuleSource(R"(%first = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot1 %first +%call = OpFunctionCall %void %helper +%second = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot2 %second +)")); + const auto acrossCall = ShaderRecompiler::ValidateAndOptimizeSpirv(called, Vulkan11, Spirv13, false, false); + check(opcodeCount(acrossCall, spv::OpGroupNonUniformBallot) == 2, "a ballot was shared across a function call"); +} + } int main() { @@ -549,6 +640,7 @@ int main() { testSpecializedDeadContinue(); testLongControlFlow(optimizationEnabled, false); testLongControlFlow(optimizationEnabled, true); + testBallotsMergedIntoOneBlockShareOneBallot(optimizationEnabled); if (failures != 0) { std::fprintf(stderr, "%d optimizer check(s) failed\n", failures); return 1; diff --git a/core/libs/tests/Wave32WideSubgroup.cpp b/core/libs/tests/Wave32WideSubgroup.cpp index af28fb59d916..751361863277 100644 --- a/core/libs/tests/Wave32WideSubgroup.cpp +++ b/core/libs/tests/Wave32WideSubgroup.cpp @@ -63,6 +63,7 @@ class Subgroup { case spv::OpConstantTrue: value[0] = 1; break; case spv::OpConstantFalse: value[0] = 0; break; case spv::OpLoad: value = arg(3); break; + case spv::OpCopyObject: value = arg(3); break; case spv::OpBitwiseAnd: value[0] = arg(3)[0] & arg(4)[0]; break; case spv::OpBitwiseOr: value[0] = arg(3)[0] | arg(4)[0]; break; case spv::OpBitwiseXor: value[0] = arg(3)[0] ^ arg(4)[0]; break; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp index 48870b3597a6..17e535a57b97 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp @@ -56,6 +56,7 @@ class SpirvModule { void AddMemoryModel(std::uint32_t addressingModel, std::uint32_t memoryModel); void AddName(std::uint32_t target, const std::string& name); void AddFunction(std::span words); + void AddSubgroupBallot(std::uint32_t type, std::uint32_t result, std::uint32_t scope, std::uint32_t predicate); void BeginHelperFunction(); void EndHelperFunction(); [[nodiscard]] SpirvDeferredPhi AddDeferredPhi(std::uint32_t type, std::uint32_t result, std::size_t incomingCount); @@ -257,6 +258,12 @@ class SpirvModule { std::vector globalVariables; std::vector functionInstructions; std::vector helperFunctionInstructions; + struct BlockBallots { + std::map, std::uint32_t> results; + std::size_t scanned = 0; + }; + BlockBallots ballots; + BlockBallots helperBallots; bool inHelperFunction = false; }; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index 19a85371306d..7d4f332fc7ef 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -126,7 +126,7 @@ std::uint32_t SpirvValueEmitContext::Ballot(const IrValue* predicate) { const auto lowWord = state.module.AllocateId(); const auto highWord = state.module.AllocateId(); const auto ballot = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformBallot, ballotType, high, scope, half == 1u ? Def(predicate) : otherHalf->Def(predicate)); + state.module.AddSubgroupBallot(ballotType, high, scope, half == 1u ? Def(predicate) : otherHalf->Def(predicate)); state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), lowWord, low, 0u); state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), highWord, high, 0u); state.module.AddFunction(spv::OpCompositeConstruct, ballotType, ballot, lowWord, highWord, ConstantU32(state, 0u), ConstantU32(state, 0u)); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 6efac49c44f0..a349d02e80dc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -39,7 +39,7 @@ namespace ShaderRecompiler state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4u), result, own, zero, zero, zero); return; } - state.module.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4u), result, ConstantU32(state, spv::ScopeSubgroup), predicate); + state.module.AddSubgroupBallot(TypeU32Vector(state, 4u), result, ConstantU32(state, spv::ScopeSubgroup), predicate); } void EmitSubgroupBallotFindLsb(SpirvEmitterState& state, std::uint32_t result, std::uint32_t ballot) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp index db387b77e34f..f10a1cdd9ea9 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp @@ -114,12 +114,14 @@ std::vector SpirvModule::Finalize() const { void SpirvModule::BeginHelperFunction() { if (inHelperFunction) throw std::runtime_error("SpirvModule helper functions cannot nest"); std::swap(functionInstructions, helperFunctionInstructions); + std::swap(ballots, helperBallots); inHelperFunction = true; } void SpirvModule::EndHelperFunction() { if (!inHelperFunction) throw std::runtime_error("SpirvModule::EndHelperFunction without a helper function"); std::swap(functionInstructions, helperFunctionInstructions); + std::swap(ballots, helperBallots); inHelperFunction = false; } @@ -221,6 +223,33 @@ void SpirvModule::AddFunction(std::span words) { AppendInstructionWords(functionInstructions, words); } +void SpirvModule::AddSubgroupBallot(std::uint32_t type, std::uint32_t result, std::uint32_t scope, std::uint32_t predicate) { + if (ballots.scanned > functionInstructions.size()) throw std::runtime_error("SpirvModule function instructions shrank under a subgroup ballot"); + for (auto offset = ballots.scanned; offset < functionInstructions.size();) { + const auto wordCount = functionInstructions[offset] >> spv::WordCountShift; + if (wordCount == 0u || wordCount > functionInstructions.size() - offset) throw std::runtime_error("SpirvModule malformed function instruction before a subgroup ballot"); + switch (functionInstructions[offset] & spv::OpCodeMask) { + case spv::OpLabel: + case spv::OpFunction: + case spv::OpFunctionEnd: + case spv::OpFunctionCall: + case spv::OpDemoteToHelperInvocation: + ballots.results.clear(); + break; + default: + break; + } + offset += wordCount; + } + const auto [found, inserted] = ballots.results.try_emplace({type, scope, predicate}, result); + if (inserted) { + appendInstruction(functionInstructions, spv::OpGroupNonUniformBallot, type, result, scope, predicate); + } else { + appendInstruction(functionInstructions, spv::OpCopyObject, type, result, found->second); + } + ballots.scanned = functionInstructions.size(); +} + void SpirvModule::appendString(std::vector& words, const std::string& text) { const auto length = text.size(); const auto wordCount = (length + 1u + 3u) / 4u; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp index 079100bb7c51..fffcf9541b6a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp @@ -2,7 +2,9 @@ #include #include #include +#include #include +#include #include #include @@ -56,6 +58,49 @@ bool HasLimitedUseTypes(std::span spirv) { return (int8 && !int8Capability) || (int16 && !int16Capability) || (float16 && !float16Capability); } +std::vector CollapseBlockBallots(std::span spirv) { + if (spirv.size() < 5) throw std::runtime_error("SPIRV-Tools: incomplete module header"); + const bool swapped = spirv[0] != spv::MagicNumber; + const auto native = [swapped](std::uint32_t word) { + return swapped ? (word >> 24u) | ((word >> 8u) & 0x0000ff00u) | + ((word << 8u) & 0x00ff0000u) | (word << 24u) + : word; + }; + std::vector collapsed(spirv.begin(), spirv.begin() + 5); + collapsed.reserve(spirv.size()); + std::map, std::uint32_t> ballots; + for (std::size_t offset = 5; offset < spirv.size();) { + const auto instruction = native(spirv[offset]); + const auto wordCount = instruction >> spv::WordCountShift; + const auto opcode = instruction & spv::OpCodeMask; + if (wordCount == 0 || wordCount > spirv.size() - offset || + (opcode == spv::OpGroupNonUniformBallot && wordCount != 5)) { + throw std::runtime_error("SPIRV-Tools: malformed instruction while collapsing block ballots"); + } + switch (opcode) { + case spv::OpLabel: + case spv::OpFunction: + case spv::OpFunctionEnd: + case spv::OpFunctionCall: + case spv::OpDemoteToHelperInvocation: + ballots.clear(); + break; + case spv::OpGroupNonUniformBallot: { + const auto [found, inserted] = ballots.try_emplace({spirv[offset + 1], spirv[offset + 3], spirv[offset + 4]}, spirv[offset + 2]); + if (!inserted) { + collapsed.insert(collapsed.end(), {native((4u << spv::WordCountShift) | spv::OpCopyObject), spirv[offset + 1], spirv[offset + 2], found->second}); + offset += wordCount; + continue; + } + break; + } + } + collapsed.insert(collapsed.end(), spirv.begin() + offset, spirv.begin() + offset + wordCount); + offset += wordCount; + } + return collapsed; +} + } std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand, bool optimize, bool specialize) { @@ -92,11 +137,11 @@ std::vector ValidateAndOptimizeSpirv(std::span(spirv.begin(), spirv.end()); + if (!optimize) return CollapseBlockBallots(spirv); spvtools::Optimizer optimizer(environment); optimizer.SetMessageConsumer(consumer); static const char* mode = std::getenv("APS5_SPIRV_OPT"); - if (mode != nullptr && std::string(mode) == "none") return std::vector(spirv.begin(), spirv.end()); + if (mode != nullptr && std::string(mode) == "none") return CollapseBlockBallots(spirv); // The recompiler emits helpers (BDA lookup, fault reporting) as functions called from every memory // access. Exhaustive inlining multiplies module size by ~10x and optimization time by ~20x, so the // default pipeline keeps the performance passes that work per function and leaves inlining to the driver. @@ -174,6 +219,7 @@ std::vector ValidateAndOptimizeSpirv(std::span Date: Thu, 15 Oct 2026 09:33:20 +0000 Subject: [PATCH 031/176] perf(agc): tell the flush hook that no image is pending over a range without the registry mutex Every guest read through GuestMemory (and every table vouch) runs the recorder's flush hook, which asks the storage-texture registry for pending images over the range: under the registry's mutex, even when no pending image is anywhere near it. With queue workers recording and storing images, that mutex is contended: a Village-like bench (three workers, a pending storage image per submission) spent 72 us per hook call waiting for it, 200 us per table vouch. The registry now publishes the merged ranges of its pending images with the version they were built from, rebuilt under the mutex as before. overlappingPending answers a range that no published range overlaps without the mutex, when the published version is the registry's current one; any other case takes the mutex as before. A change bumps the current version with release order before the mutex is released, so a reader that could see the change through any other path cannot read the old ranges as current. In the bench the hook drops from 65-81 us to 4.5 us per call and a table vouch from about 200 us to 23 us. The existing storage-texture tests cover the pending images the hook must still find. (cherry picked from commit 6643e2870c59013f20ddbb2bbde78b8cc00cd26c) --- .../libSceAgcDriver/Graphics/src/Texture.cpp | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 05871bbc60d1..4a91d3e88c92 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1032,28 +1032,48 @@ namespace { class PendingList { public: using iterator = std::vector::iterator; + using Ranges = std::vector>; iterator begin() { return textures.begin(); } iterator end() { return textures.end(); } void push_back(StorageTexture* texture) { textures.push_back(texture); - ++version; + changed(); } iterator erase(iterator it) { - ++version; - return textures.erase(it); + const auto next = textures.erase(it); + changed(); + return next; } void remove(const StorageTexture* texture) { - if (std::erase(textures, texture) != 0) ++version; + if (std::erase(textures, texture) != 0) changed(); } bool MayOverlap(std::uint64_t address, std::size_t bytes) { - if (textures.empty() || bytes == 0) return false; if (indexed != version) rebuild(); + return !textures.empty() && Overlaps(ranges, address, bytes); + } + bool SurelyClear(std::uint64_t address, std::size_t bytes) const { + const auto current = published.load(std::memory_order_acquire); + return current != nullptr && current->version == changes.load(std::memory_order_acquire) && !Overlaps(current->ranges, address, bytes); + } + +private: + struct Snapshot { + std::uint64_t version = 0; + Ranges ranges; + }; + + static bool Overlaps(const Ranges& ranges, std::uint64_t address, std::size_t bytes) { + if (bytes == 0) return false; const auto end = bytes > std::numeric_limits::max() - address ? std::numeric_limits::max() : address + bytes; const auto it = std::upper_bound(ranges.begin(), ranges.end(), address, [](std::uint64_t value, const std::pair& range) { return value < range.second; }); return it != ranges.end() && it->first < end; } -private: + void changed() { + ++version; + changes.store(version, std::memory_order_release); + } + void rebuild() { ranges.clear(); for (const auto* texture : textures) ranges.emplace_back(texture->Descriptor().baseAddress, texture->Descriptor().baseAddress + texture->GuestBytes()); @@ -1065,11 +1085,14 @@ class PendingList { } ranges.resize(merged); indexed = version; + published.store(std::make_shared(Snapshot{version, ranges}), std::memory_order_release); } std::vector textures; - std::vector> ranges; + Ranges ranges; std::uint64_t version = 0; std::uint64_t indexed = ~std::uint64_t{0}; + std::atomic changes{0}; + std::atomic> published; }; // Storage images whose results have not reached guest memory yet. @@ -3182,6 +3205,7 @@ std::vector> StorageTexture::overlappingPending( // FlushPending's listing; the images stay alive across the caller's scan as there. std::vector> overlapping; auto& pending = Pending(); + if (pending.textures.SurelyClear(address, bytes)) return overlapping; std::lock_guard lock(pending.mutex); if (!pending.textures.MayOverlap(address, bytes)) return overlapping; const auto* exempt = refreshing; From 2d6ef68f3d0dc01d833c6a908b077ca32feac40e Mon Sep 17 00:00:00 2001 From: xXPBSXx Date: Fri, 16 Oct 2026 06:57:12 +0200 Subject: [PATCH 032/176] test(libSceRtc): cover microseconds, minutes, hours and weeks tick addition Co-Authored-By: Claude Sonnet 5.5 (cherry picked from commit f2b580bc3248a691fc0369862a74cdc41010f224) --- core/libs/tests/GuestRtc.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 167257d2a009..a1d820e72d3c 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -45,8 +45,12 @@ int APS5_VABI sceRtcFormatRFC3339(char*, const RtcTick*, int); int APS5_VABI sceRtcParseRFC3339(RtcTick*, const char*); int APS5_VABI sceRtcParseDateTime(RtcTick*, const char*); int APS5_VABI sceRtcTickAddTicks(RtcTick*, const RtcTick*, std::int64_t); +int APS5_VABI sceRtcTickAddMicroseconds(RtcTick*, const RtcTick*, std::int64_t); int APS5_VABI sceRtcTickAddSeconds(RtcTick*, const RtcTick*, std::int64_t); +int APS5_VABI sceRtcTickAddMinutes(RtcTick*, const RtcTick*, std::int64_t); +int APS5_VABI sceRtcTickAddHours(RtcTick*, const RtcTick*, std::int32_t); int APS5_VABI sceRtcTickAddDays(RtcTick*, const RtcTick*, std::int32_t); +int APS5_VABI sceRtcTickAddWeeks(RtcTick*, const RtcTick*, std::int32_t); int APS5_VABI sceRtcTickAddMonths(RtcTick*, const RtcTick*, std::int32_t); int APS5_VABI sceRtcTickAddYears(RtcTick*, const RtcTick*, std::int16_t); int APS5_VABI sceRtcFormatRFC3339LocalTime(char*, const RtcTick*); @@ -434,6 +438,23 @@ int main() { source.tick = maxTick; Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue); Require(sceRtcTickAddTicks(nullptr, &source, 1) == invalidPointer); + source.tick = leapDayTick; + result.tick = 123; + Require(sceRtcTickAddMicroseconds(&result, &source, 1500) == 0 && result.tick == leapDayTick + 1500); + Require(sceRtcTickAddMinutes(&result, &source, 90) == 0 && result.tick == leapDayTick + 5400000000ull); + Require(sceRtcTickAddHours(&result, &source, -2) == 0 && result.tick == leapDayTick - 7200000000ull); + Require(sceRtcTickAddWeeks(&result, &source, 1) == 0 && result.tick == leapDayTick + 604800000000ull); + Require(sceRtcSetTick(&converted, &result) == 0 && converted.month == 3 && converted.day == 7); + result.tick = 123; + Require(sceRtcTickAddMinutes(&result, &source, std::numeric_limits::max()) == invalidValue && result.tick == 123); + Require(sceRtcTickAddWeeks(&result, &source, std::numeric_limits::max()) == invalidValue && result.tick == 123); + Require(sceRtcTickAddHours(nullptr, &source, 1) == invalidPointer); + Require(sceRtcTickAddMicroseconds(&result, nullptr, 1) == invalidPointer); + source.tick = maxTick; + Require(sceRtcTickAddHours(&result, &source, 1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddMicroseconds(&result, &source, 1) == invalidValue && result.tick == 123); + source.tick = 0; + Require(sceRtcTickAddMinutes(&result, &source, -1) == invalidValue && result.tick == 123); const std::uint64_t invalidTicks[] = {maxTick + 1, std::numeric_limits::max()}; for (std::uint64_t invalidTick : invalidTicks) { From c1329f8e42ce3e0710580f34a6fedf939d31c099 Mon Sep 17 00:00:00 2001 From: Karim Ibrahim <40270714+karimibrahim07@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:23:47 -0400 Subject: [PATCH 033/176] feat(libc): implement qsort_s The Quake homebrew's libOSMesa.so.8 imports qsort_s, the only strong import left unresolved once its symbol names resolve through NIDs. qsort_s sorts as qsort does, with the context passed as the comparator's third argument, and returns EINVAL after calling the constraint handler for the runtime constraint violations of FreeBSD 13's qsort_s. The sort helpers take any comparator so both functions share the FreeBSD 11 algorithm. (cherry picked from commit edf4efe360e530f7f3d023889851a2b7124fe798) --- core/libs/prx/libc/src/FileAndHeap.cpp | 27 +++++++++++++-- core/libs/tests/GuestQsort.cpp | 48 ++++++++++++++++++++++++++ docs/dev/debt/libc.md | 1 + 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index c235383189d9..4c00567886bc 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -21,6 +21,7 @@ #include #endif +#include "prx/libc/include/SonyCrt.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" @@ -127,6 +128,7 @@ static bool IsPipeStream(std::FILE* native) { #endif using GuestCompare = int (APS5_VABI *)(const void*, const void*); +using GuestContextCompare = int (APS5_VABI *)(const void*, const void*, void*); static void SwapElements(unsigned char* left, unsigned char* right, size_t size) { for (size_t index = 0; index < size; ++index) std::swap(left[index], right[index]); @@ -136,19 +138,22 @@ static void SwapRanges(unsigned char* left, unsigned char* right, size_t bytes) if (bytes > 0) SwapElements(left, right, bytes); } -static unsigned char* MedianOfThree(unsigned char* a, unsigned char* b, unsigned char* c, GuestCompare compare) { +template +static unsigned char* MedianOfThree(unsigned char* a, unsigned char* b, unsigned char* c, Compare compare) { return compare(a, b) < 0 ? (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a)) : (compare(b, c) > 0 ? b : (compare(a, c) < 0 ? a : c)); } -static void InsertionSort(unsigned char* base, size_t count, size_t size, GuestCompare compare) { +template +static void InsertionSort(unsigned char* base, size_t count, size_t size, Compare compare) { for (unsigned char* next = base + size; next < base + count * size; next += size) for (unsigned char* element = next; element > base && compare(element - size, element) > 0; element -= size) SwapElements(element, element - size, size); } -static void SortElements(unsigned char* base, size_t count, size_t size, GuestCompare compare) { +template +static void SortElements(unsigned char* base, size_t count, size_t size, Compare compare) { for (;;) { if (count < 7) { InsertionSort(base, count, size, compare); @@ -590,4 +595,20 @@ void APS5_VABI qsort_nid_postfix(void* base, size_t count, size_t size, int (APS SortElements(static_cast(base), count, size, compare); } +int APS5_VABI qsort_s_nid_postfix(void* base, size_t count, size_t size, GuestContextCompare compare, void* context) { + constexpr size_t RsizeMax = std::numeric_limits::max() >> 1; + const char* const violation = count > RsizeMax ? "qsort_s : n > RSIZE_MAX" + : size > RsizeMax ? "qsort_s : es > RSIZE_MAX" + : count != 0 && !base ? "qsort_s : a == NULL" + : count != 0 && !compare ? "qsort_s : cmp == NULL" + : nullptr; + if (violation) { + SonyInvokeConstraintHandler(violation, nullptr, 22); + return 22; + } + if (count > 1 && size != 0) + SortElements(static_cast(base), count, size, [compare, context](const void* left, const void* right) { return compare(left, right, context); }); + return 0; +} + } diff --git a/core/libs/tests/GuestQsort.cpp b/core/libs/tests/GuestQsort.cpp index 6124f2540fc1..cb5503c66ed8 100644 --- a/core/libs/tests/GuestQsort.cpp +++ b/core/libs/tests/GuestQsort.cpp @@ -12,6 +12,7 @@ extern "C" { int APS5_VABI mergesort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); int* APS5_VABI __error_nid_postfix(); void APS5_VABI qsort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); +int APS5_VABI qsort_s_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*, void*), void*); } namespace { @@ -70,6 +71,50 @@ int APS5_VABI CompareFirstByte(const void* left, const void* right) { return (first > second) - (first < second); } +int APS5_VABI CompareKeysWithDirection(const void* left, const void* right, void* context) { + return *static_cast(context) * CompareKeys(left, right); +} + +bool ExpectOrderS(const char* name, const std::vector& keys, const std::vector& tags) { + std::vector elements; + for (std::size_t index = 0; index < keys.size(); ++index) elements.push_back({keys[index], static_cast(index)}); + int direction = 1; + if (qsort_s_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareKeysWithDirection, &direction) != 0) { + std::fprintf(stderr, "%s: qsort_s failed\n", name); + return false; + } + for (std::size_t index = 0; index < tags.size(); ++index) { + if (elements[index].tag != tags[index]) { + std::fprintf(stderr, "%s: qsort_s element %zu has tag %d, expected %d\n", name, index, elements[index].tag, tags[index]); + return false; + } + } + return true; +} + +bool ExpectQsortS() { + std::vector elements; + for (int index = 0; index < 50; ++index) elements.push_back({(index * 13) % 17, index}); + int direction = -1; + if (qsort_s_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareKeysWithDirection, &direction) != 0) return false; + for (std::size_t index = 1; index < elements.size(); ++index) { + if (elements[index - 1].key < elements[index].key) { + std::fprintf(stderr, "qsort_s with a descending context is not sorted at %zu\n", index); + return false; + } + } + if (qsort_s_nid_postfix(nullptr, 0, sizeof(Element), nullptr, nullptr) != 0) { + std::fprintf(stderr, "qsort_s of no elements failed\n"); + return false; + } + unsigned char bytes[] = {3, 1, 2}; + if (qsort_s_nid_postfix(bytes, 3, 0, CompareKeysWithDirection, &direction) != 0 || bytes[0] != 3 || bytes[1] != 1 || bytes[2] != 2) { + std::fprintf(stderr, "qsort_s with zero-size elements changed them\n"); + return false; + } + return true; +} + bool ExpectOrder(const char* name, const std::vector& keys, const std::vector& tags) { std::vector elements; for (std::size_t index = 0; index < keys.size(); ++index) elements.push_back({keys[index], static_cast(index)}); @@ -140,6 +185,9 @@ int main() { bool correct = true; correct &= ExpectOrder("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); correct &= ExpectOrder("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); + correct &= ExpectOrderS("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); + correct &= ExpectOrderS("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); + correct &= ExpectQsortS(); std::vector keys; for (int index = 0; index < 48; ++index) keys.push_back((index * 7 + 3) % 5); correct &= ExpectOrder("pseudomedian of nine", keys, diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index add29f73d4b8..7288cf712814 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -41,6 +41,7 @@ - [vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Not confirmed on a PS5 title - [localeconv](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, `CHAR_MAX` for the 14 `char` fields, `decimal_point` ".", `_False`/`_True` "false"/"true") is read from the `libc.prx` that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with `setlocale` is not known, and `_Frac_grouping`, `_Frac_sep`, `_No`, `_Yes` are empty strings as in that dump - [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified +- [qsort_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) sorts as `qsort` does and passes its context as the comparator's third argument, as FreeBSD 13's [`qsort_s`](https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/stdlib/qsort.c#L97-L127) (C11 Annex K) does. It returns `EINVAL` after calling the constraint handler when `n` or `es` exceeds `RSIZE_MAX`, or when `n` is not 0 and `a` or `cmp` is null; FreeBSD 11, which the rest of libc follows, has no `qsort_s`. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The handler is the one `SonyCrt.hpp` keeps per source file, so a handler set with `set_constraint_handler_s` is not the one called here. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its `libOSMesa.so.8` - [__udivti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc, re-exported by libSceAmpr) - the hardware traps on a zero divisor; this throws instead - [__umodti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead From eae4b53b5f2d108ad2a6a318aeed1d7a2733b2dd Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:54:42 +0000 Subject: [PATCH 034/176] test(agc): resolve the pipeline ABI of stages that only their use can prepare Four prepared-shader modes, run with the recompiler's failure memo off so a second preparation would show as a second front-end build: - compute: a shader that calls user-data pointers is resolved twice and prepared once, then its dispatch links the callees and every lane holds 3 * (5 + 64); a shader whose registration preparation failed still throws its stored failure from the ABI call. - pixel: a waterfall whose T# pointer is a phi of user data and a ballot is logged once with its reason, resolved again without a second preparation, and resolved for another state inside a ShaderPreparationScope that commits. A draw of it fails at use with the same reason, and the ABI call then throws the driver's failure. - stages: the same chain in a vertex shader that registration leaves alone, through ResolveShaderAbi inside a scope and through ResolveGraphicsStagesAbi. - rectangle: sceAgcDriverResolveGraphicsAbi with a pixel shader deferred at registration and with one deferred at its ABI call. (cherry picked from commit 695ec6cc4f134f345d06d7d05d135cc487291ab7) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 11 +- .../tests/execution/PreparedShaders.cpp | 261 +++++++++++++++++- 2 files changed, 268 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 756939ad98a9..9433b3526f4b 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1514,9 +1514,12 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") - if(preparedMode STREQUAL "indirect") + if(preparedMode MATCHES "^abi_deferred_") + string(REPLACE "_" "-" preparedArgument ${preparedMode}) + add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --${preparedArgument}) + elseif(preparedMode STREQUAL "indirect") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --indirect) elseif(preparedMode STREQUAL "deferred") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --deferred) @@ -1529,7 +1532,9 @@ if(BUILD_TESTING) add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests) endif() set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") - if(preparedMode STREQUAL "uncached") + if(preparedMode MATCHES "^abi_deferred_") + set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_FAILURE_MEMO=1") + elseif(preparedMode STREQUAL "uncached") set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_DISPATCH_CACHE=1") elseif(preparedMode STREQUAL "debug" OR preparedMode STREQUAL "debug_failure") set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_PROFILE_DRAW=1" "APS5_NO_UNLOCKED_DEVICE=1") diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index bec235513948..c36b899cc8f5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -5,7 +5,9 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp" +#include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include @@ -28,6 +30,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif namespace { @@ -604,11 +611,263 @@ void DeferredUndecodableRegistration() { ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "unsupported MIMG opcode"); } +class StderrCapture { +public: + StderrCapture() { + std::fflush(stderr); + file = std::tmpfile(); + Require(file != nullptr, "cannot open a temporary file for stderr"); +#ifdef _WIN32 + saved = _dup(_fileno(stderr)); + _dup2(_fileno(file), _fileno(stderr)); +#else + saved = dup(fileno(stderr)); + dup2(fileno(file), fileno(stderr)); +#endif + } + std::string Finish() { + std::fflush(stderr); +#ifdef _WIN32 + _dup2(saved, _fileno(stderr)); + _close(saved); +#else + dup2(saved, fileno(stderr)); + close(saved); +#endif + std::rewind(file); + std::string text; + char buffer[4096]; + for (std::size_t read = 0; (read = std::fread(buffer, 1, sizeof(buffer), file)) != 0;) text.append(buffer, read); + std::fclose(file); + std::fwrite(text.data(), 1, text.size(), stderr); + return text; + } + +private: + std::FILE* file = nullptr; + int saved = -1; +}; + +template +std::string Logged(TAction action) { + StderrCapture capture; + try { + action(); + } catch (...) { + static_cast(capture.Finish()); + throw; + } + return capture.Finish(); +} + +std::size_t Deferrals(const std::string& log) { + constexpr std::string_view line = "could not be prepared when the title created its pipeline"; + std::size_t count = 0; + for (auto found = log.find(line); found != std::string::npos; found = log.find(line, found + line.size())) ++count; + return count; +} + +struct AbiHeader { + Shader shader{}; + std::array registers{}; + std::array context{}; + ShaderSpecialRegs specials{}; + ShaderUserData users{}; +}; + +std::uint32_t ProgramLow(const void* code) { + return static_cast(reinterpret_cast(code) >> 8u); +} + +std::uint32_t ProgramHigh(const void* code) { + return static_cast(reinterpret_cast(code) >> 40u); +} + +template +void DescribeAbi(AbiHeader& header, const std::array& code, std::uint8_t type, std::initializer_list registers, std::initializer_list context) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18; + header.shader.header_size = sizeof(AbiHeader); + header.shader.shader_size = sizeof(code); + header.shader.code = code.data(); + header.shader.type = type; + header.shader.user_data = &header.users; + std::copy(registers.begin(), registers.end(), header.registers.begin()); + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = static_cast(registers.size()); + std::copy(context.begin(), context.end(), header.context.begin()); + header.shader.cx_registers = header.context.data(); + header.shader.num_cx_registers = static_cast(context.size()); +} + +alignas(256) constexpr std::array UserDataCaller{ + 0x7e020280u, 0xbe940383u, 0xbe8e0300u, 0xbe8f0301u, 0xbe8e210eu, 0xbe8e0302u, 0xbe8f0303u, 0xbe8e210eu, + 0x80948114u, 0xbf078014u, 0xbf85fff7u, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u, 0xbf9f0000u, + 0x30306c73u, 0x00000040u, 0x00000008u}; +alignas(256) constexpr std::array AddFiveCallee{0x4a020285u, 0xbe80200eu}; +alignas(256) constexpr std::array AddSixtyFourCallee{0x4a0202c0u, 0xbe80200eu}; +alignas(256) std::array CallerOutput{}; +alignas(256) constexpr std::array WaterfallStoreCode{ + 0x7e020280u, 0x7e040280u, 0x7e080280u, 0x7e0a0280u, 0x7e0c0280u, 0x7e0e0280u, 0xbe86047eu, 0xf40c0200u, + 0xfa000000u, 0xd4c10000u, 0x00020083u, 0xbf8cc07fu, 0xf0200f08u, 0x00020401u, 0x8afe007eu, 0xbf89fff7u, + 0xbefe0406u, 0xbf810000u}; +alignas(256) constexpr std::array WaterfallPixelCode{ + 0xc80a0002u, 0x7e000280u, 0x7e020280u, 0xbe86047eu, 0xf40c0200u, 0xfa000000u, 0xd4c10000u, 0x00020483u, + 0xbf8cc07fu, 0xf0000f08u, 0x00020400u, 0x8afe007eu, 0xbf89fff7u, 0xbefe0406u, 0xbf8c3f70u, 0xf800180fu, + 0x07060504u, 0xbf810000u}; +alignas(256) constexpr std::array WaterfallVertexCode{ + 0x7e020280u, 0x7e040280u, 0xbe86037eu, 0xf40c0404u, 0xfa000000u, 0xd4c10008u, 0x00020083u, 0xbf8cc07fu, + 0xf0000f08u, 0x00040401u, 0x8a7e087eu, 0xbf89fff7u, 0xbefe0306u, 0xbf8a0000u, 0xbf8c3f70u, 0xf80008cfu, + 0x07060504u, 0xbf810000u}; +alignas(256) constexpr std::array OtherWaterfallVertexCode = WaterfallVertexCode; +alignas(256) constexpr std::array FrontCode{0x7e0002ffu, 0, 0x7e0202ffu, 0, 0x7e0402ffu, 0, 0x7e0602ffu, 0x3f800000u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; +alignas(256) constexpr std::array UndecodablePixelCode{0xf1949f05u, 0x00060018u, 0xbf810000u}; + +void DescribeWaterfallPixel(AbiHeader& pixel) { + DescribeAbi(pixel, WaterfallPixelCode, 1, {{0x8, ProgramLow(WaterfallPixelCode.data())}, {0x9, ProgramHigh(WaterfallPixelCode.data())}, {0xa, 0}, {0xb, 2u << 1u}}, {{0x1b6, 1}, {0x1b3, 2}, {0x1b4, 2}, {0x191, 0}, {0x1c5, 4}}); +} + +void DescribeFront(AbiHeader& vertex) { + DescribeAbi(vertex, FrontCode, 2, {{0xc8, ProgramLow(FrontCode.data())}, {0xc9, ProgramHigh(FrontCode.data())}, {0x8b, 0}, {0x8a, 0}, {0xca, 0}, {0xcb, 0}, {0xcc, 0}}, {{0x2d5, 0x2000}}); + vertex.shader.specials = &vertex.specials; + vertex.specials.dispatch_modifier = 0x8000; +} + +void DescribeWaterfallVertex(AbiHeader& vertex, const std::array& code) { + DescribeAbi(vertex, code, 2, {{0xc8, ProgramLow(code.data())}, {0xc9, ProgramHigh(code.data())}, {0x8a, 0}, {0x8b, 2u << 1u}}, {{0x1ff, 64}, {0x2ab, 4}, {0x2ce, 0}}); + vertex.shader.specials = &vertex.specials; + vertex.specials = {{0x25b, 0x8040}, {0x2d5, 0x00402000}, 0, {}, {}, {0x29b, 2}, {0x260, 0}}; +} + +void AbiDeferredCompute() { + static AbiHeader caller; + DescribeAbi(caller, UserDataCaller, 0, {{0x20c, ProgramLow(UserDataCaller.data())}, {0x20d, ProgramHigh(UserDataCaller.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 8u << 1u}}, {}); + caller.shader.specials = &caller.specials; + caller.specials.dispatch_modifier = 0x8000u; + AgcDriverRegisterShader_nid_postfix(&caller.shader); + const auto before = ShaderRecompiler::FrontEndBuilds(); + AgcDriverResolveShaderAbi_nid_postfix(&caller.shader, {}, {}); + const auto attempted = ShaderRecompiler::FrontEndBuilds(); + AgcDriverResolveShaderAbi_nid_postfix(&caller.shader, {}, {}); + Require(attempted > before && ShaderRecompiler::FrontEndBuilds() == attempted, "a compute static ABI deferred to its dispatch was prepared again for the same state"); + const auto first = reinterpret_cast(AddFiveCallee.data()); + const auto second = reinterpret_cast(AddSixtyFourCallee.data()); + const auto output = reinterpret_cast(CallerOutput.data()); + const std::array users{static_cast(first), static_cast(first >> 32u), static_cast(second), static_cast(second >> 32u), + static_cast(output), static_cast((output >> 32u) & 0xffffu), static_cast(sizeof(CallerOutput)), 0x31016facu}; + std::vector commands; + for (std::size_t index = 0; index < caller.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, caller.registers[index].offset, caller.registers[index].value}); + for (std::uint32_t index = 0; index < users.size(); ++index) commands.insert(commands.end(), {0xc0017600u, 0x240u + index, users[index]}); + commands.insert(commands.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); + CallerOutput.fill(0xcafebabeu); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + sceAgcDriverSubmitAcb(0x20, &packet); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(output, sizeof(CallerOutput)); + Require(std::ranges::all_of(CallerOutput, [](std::uint32_t lane) { return lane == 3u * (5u + 64u); }), "a compute shader its static ABI deferred did not run its user-data callees at its dispatch"); + static AbiHeader store; + DescribeAbi(store, WaterfallStoreCode, 0, {{0x20c, ProgramLow(WaterfallStoreCode.data())}, {0x20d, ProgramHigh(WaterfallStoreCode.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 2u << 1u}}, {}); + store.shader.specials = &store.specials; + AgcDriverRegisterShader_nid_postfix(&store.shader); + ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(&store.shader, {}, {}); }, "is not a valid runtime value"); + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredPixel() { + static AbiHeader pixel; + DescribeWaterfallPixel(pixel); + AgcDriverRegisterShader_nid_postfix(&pixel.shader); + const auto before = ShaderRecompiler::FrontEndBuilds(); + auto log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); }); + const auto attempted = ShaderRecompiler::FrontEndBuilds(); + log += Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); }); + Require(attempted > before && ShaderRecompiler::FrontEndBuilds() == attempted, "a pixel static ABI deferred to its draw was prepared again for the same state"); + Require(Deferrals(log) == 1 && log.find("is not a valid runtime value") != std::string::npos, "a pixel shader its static ABI cannot prepare was not logged once with its reason"); + { + AgcDriver::ShaderPreparationScope scope({&pixel.shader}); + const std::array interpolant{{{0x191, 0x20}}}; + log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, interpolant, {}); }); + scope.Commit(); + } + Require(ShaderRecompiler::FrontEndBuilds() > attempted && Deferrals(log) == 0, "a deferred pixel shader was not prepared once more for another state, or was logged again"); + static AbiHeader front; + DescribeFront(front); + AgcDriverRegisterShader_nid_postfix(&front.shader); + std::vector commands; + for (const auto* header : {&front, &pixel}) { + for (std::size_t index = 0; index < header->shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, header->registers[index].offset, header->registers[index].value}); + for (std::size_t index = 0; index < header->shader.num_cx_registers; ++index) commands.insert(commands.end(), {0xc0016900u, header->context[index].offset, header->context[index].value}); + } + commands.insert(commands.end(), {0xc0016900u, 0x8e, 0xfu, 0xc0016900u, 0x8f, 0xfu, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 4, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw of a deferred pixel shader was not submitted"); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "is not a valid runtime value"); + ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); }, "is not a valid runtime value"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "is not a valid runtime value"); +} + +void AbiDeferredStages() { + static AbiHeader nested; + static AbiHeader direct; + DescribeWaterfallVertex(nested, WaterfallVertexCode); + DescribeWaterfallVertex(direct, OtherWaterfallVertexCode); + AgcDriverRegisterShader_nid_postfix(&nested.shader); + AgcDriverRegisterShader_nid_postfix(&direct.shader); + const std::array primitive{{{0x242, 4}}}; + const auto before = ShaderRecompiler::FrontEndBuilds(); + std::string log; + { + AgcDriver::ShaderPreparationScope scope({&nested.shader}); + log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&nested.shader, {}, primitive); }); + scope.Commit(); + } + const auto nestedAttempt = ShaderRecompiler::FrontEndBuilds(); + Require(nestedAttempt > before && Deferrals(log) == 1, "a vertex shader the static ABI resolves through its graphics stages was not deferred to its draw"); + log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&nested.shader, {}, primitive); }); + Require(ShaderRecompiler::FrontEndBuilds() == nestedAttempt && Deferrals(log) == 0, "a deferred vertex static ABI was prepared or logged again for the same state"); + const std::array stages{&direct.shader}; + log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); }); + const auto directAttempt = ShaderRecompiler::FrontEndBuilds(); + Require(directAttempt > nestedAttempt && Deferrals(log) == 1, "a graphics stage its ABI cannot prepare was not deferred to its draw"); + log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); }); + Require(ShaderRecompiler::FrontEndBuilds() == directAttempt && Deferrals(log) == 0, "a deferred graphics stage ABI was prepared or logged again for the same state"); + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredRectangle() { + static AbiHeader front; + static AbiHeader undecodable; + static AbiHeader pixel; + DescribeFront(front); + DescribeAbi(undecodable, UndecodablePixelCode, 1, {{0x8, ProgramLow(UndecodablePixelCode.data())}, {0x9, ProgramHigh(UndecodablePixelCode.data())}, {0xa, 0}, {0xb, 0}}, {}); + DescribeWaterfallPixel(pixel); + for (auto* header : {&front, &undecodable, &pixel}) AgcDriverRegisterShader_nid_postfix(&header->shader); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &undecodable.shader, 17); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); + { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + scope.Commit(); + } + AgcDriverShutdown_nid_postfix(); +} + } int main(int argc, char** argv) { try { - Require(argc == 1 || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); + const std::string_view abiMode = argc == 2 ? argv[1] : ""; + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle"; + Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); + if (abiDeferred) { + if (!OpenVulkanTestDevice()) return VulkanTestSkipped; + if (abiMode == "--abi-deferred-compute") AbiDeferredCompute(); + else if (abiMode == "--abi-deferred-pixel") AbiDeferredPixel(); + else if (abiMode == "--abi-deferred-stages") AbiDeferredStages(); + else AbiDeferredRectangle(); + std::cout << "shader ABI deferral tests passed: " << abiMode << '\n'; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--deferred") { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; DeferredRegistration(); From 2eb2a503016c06bf80678faca0630f14a0572e0c Mon Sep 17 00:00:00 2001 From: hypermist Date: Sat, 10 Oct 2026 18:17:41 +1300 Subject: [PATCH 035/176] fix(agc): check GPU-side indirect dispatch group counts against the device limit A DISPATCH_INDIRECT read on the GPU took its group counts unchecked. A count buffer that a skipped producer never wrote (PPSA08427: 0xffffffff left by skipped depth-plane passes) launched up to 2^32 groups and kept the GPU busy until the title hung. The CPU path already refuses counts above maxComputeWorkGroupCount. - Recorder::RecordDispatchArguments copies the three counts from the import into a 12-byte indirect buffer with a one-thread kernel (DispatchArguments.comp), all three 0 when one exceeds the device limit, as RecordMeshArguments does for mesh indirect records. - VulkanDevice records the dispatch from that buffer; without the kernel the import is read directly, as before. - agc_driver_indirect_dispatch_limit: 0xffffffff groups run nothing, one group still runs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8tinbP1MUpUaDsbNWnGnu (cherry picked from commit e54d2f35fedee05ca75b82aba539b0cf87f641df) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 14 +++ .../Execution/src/VulkanDevice.cpp | 12 ++- .../Graphics/include/Recorder.hpp | 4 + .../Graphics/shaders/DispatchArguments.comp | 23 +++++ .../libSceAgcDriver/Graphics/src/Recorder.cpp | 49 +++++++++++ .../tests/execution/IndirectDispatchLimit.cpp | 88 +++++++++++++++++++ 6 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 9433b3526f4b..ea6d03c9e2a4 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -83,6 +83,19 @@ add_custom_command( VERBATIM ) +set(agcDispatchArgumentsShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/DispatchArguments.comp) +set(agcDispatchArgumentsShaderSpv ${agcTextureDetileGeneratedDir}/DispatchArguments.spv) +set(agcDispatchArgumentsShaderHeader ${agcTextureDetileGeneratedDir}/DispatchArguments_spv.h) +add_custom_command( + OUTPUT "${agcDispatchArgumentsShaderHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -o "${agcDispatchArgumentsShaderSpv}" "${agcDispatchArgumentsShaderSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcDispatchArgumentsShaderSpv} -DOUTPUT=${agcDispatchArgumentsShaderHeader} + -DSYMBOL=DISPATCH_ARGUMENTS_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcDispatchArgumentsShaderSource}" glslang-standalone + VERBATIM +) + set(agcSampleCounterShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/SampleCounter.comp) set(agcSampleCounterShaderSpv ${agcTextureDetileGeneratedDir}/SampleCounter.spv) set(agcSampleCounterShaderHeader ${agcTextureDetileGeneratedDir}/SampleCounter_spv.h) @@ -217,6 +230,7 @@ set(agcDriverSources ${agcCmaskClearShaderHeader} ${agcMeshArgumentsShaderHeader} ${agcTextureDetileImageShaderHeader} + ${agcDispatchArgumentsShaderHeader} ${agcSampleCounterShaderHeader} ${agcSampleDumpsShaderHeader} ${CMAKE_CURRENT_SOURCE_DIR}/Eq/src/Event.cpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 8d32347a9f36..582f3c702730 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -3664,6 +3664,16 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { recorder.EndGpuTiming(timing); ++d.preBarriersRecorded; } + VkBuffer indirectBuffer = argumentImport != nullptr ? argumentImport->buffer : VK_NULL_HANDLE; + VkDeviceSize indirectOffset = argumentImport != nullptr ? arguments - argumentImport->base : 0; + if (argumentImport != nullptr && argumentImport->address != 0) { + auto checked = std::make_shared(context, 12, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + if (recorder.RecordDispatchArguments(commands, argumentImport->address + (arguments - argumentImport->base), checked->DeviceAddress())) { + indirectBuffer = checked->Handle(); + indirectOffset = 0; + recorder.Keep(std::move(checked)); + } + } context.Resolved(&Graphics::DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, record.objects->pipeline); resources.Bind(commands, VK_PIPELINE_BIND_POINT_COMPUTE, record.objects->layout); if (const auto dwords = record.shader->program->workgroupMemoryDwords; dwords != 0) { @@ -3676,7 +3686,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { } const auto gpuTiming = recorder.BeginGpuTiming(record.programAddress != 0 ? record.programAddress : record.shader->program->variantId); if (argumentImport != nullptr) { - context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, argumentImport->buffer, arguments - argumentImport->base); + context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, indirectBuffer, indirectOffset); } else { const auto* limit = state->properties.limits.maxComputeWorkGroupCount; if (record.x <= limit[0] && record.y <= limit[1] && record.z <= limit[2]) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index a58de74ce446..f73b7b882fc6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -179,6 +179,7 @@ class Recorder { // ever submitted); WaitSerial(serial) then covers all recorded work. std::uint64_t SubmitAndEpoch(); bool HasTimeline() const { return timeline != VK_NULL_HANDLE; } + bool RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments); // Waits until every batch up to `serial` completed on the GPU. Called WITHOUT GuestMemory::GpuMutex: // it touches only the immutable timeline semaphore (the caller keeps the device alive). Requires // HasTimeline(); the debug switch APS5_NO_TIMELINE makes the device create the recorder without one. @@ -692,6 +693,9 @@ class Recorder { VkPipelineLayout dumpLayout = VK_NULL_HANDLE; VkPipeline dumpPipeline = VK_NULL_HANDLE; std::shared_ptr segmentPools; + int dispatchArgumentState = 0; + VkPipelineLayout dispatchArgumentLayout = VK_NULL_HANDLE; + VkPipeline dispatchArgumentPipeline = VK_NULL_HANDLE; // BeginGpuTiming on the open batch without Commands() (RecordStore times its own run, which // Commands() would close). std::uint32_t beginTiming(std::uint64_t key); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp new file mode 100644 index 000000000000..ed0b2525613c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp @@ -0,0 +1,23 @@ +#version 450 +#extension GL_EXT_buffer_reference : require + +layout(local_size_x = 1) in; +layout(buffer_reference, std430, buffer_reference_align = 4) buffer Words { uint words[]; }; +layout(push_constant) uniform Parameters { + Words source; + Words arguments; + uint maxX; + uint maxY; + uint maxZ; + uint unused; +} parameters; + +void main() { + uint x = parameters.source.words[0]; + uint y = parameters.source.words[1]; + uint z = parameters.source.words[2]; + bool valid = x <= parameters.maxX && y <= parameters.maxY && z <= parameters.maxZ; + parameters.arguments.words[0] = valid ? x : 0u; + parameters.arguments.words[1] = valid ? y : 0u; + parameters.arguments.words[2] = valid ? z : 0u; +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 1f7c2c59c78e..b631d4ef65c5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -13,6 +13,7 @@ #include "prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp" #include "prx/libc/include/CpuTopology.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libSceAgcDriver/Graphics/shaders/DispatchArguments_spv.h" #include #include #include @@ -1171,6 +1172,8 @@ Recorder::~Recorder() { context.Function("vkDestroyPipelineLayout")(context.device, dumpLayout, nullptr); sampleCounter.reset(); } + if (dispatchArgumentPipeline != VK_NULL_HANDLE) context.Function("vkDestroyPipeline")(context.device, dispatchArgumentPipeline, nullptr); + if (dispatchArgumentLayout != VK_NULL_HANDLE) context.Function("vkDestroyPipelineLayout")(context.device, dispatchArgumentLayout, nullptr); for (auto& [commands, fence] : spare) { context.Function("vkFreeCommandBuffers")(context.device, context.pool, 1, &commands); context.Function("vkDestroyFence")(context.device, fence, nullptr); @@ -1942,6 +1945,52 @@ void Recorder::EndGpuTiming(std::uint32_t index, std::uint64_t bytes) { context.Function("vkCmdWriteTimestamp")(open->commands, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, open->queries, index * 2 + 1); } +bool Recorder::RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments) { + constexpr std::uint32_t PushBytes = 32; + if (dispatchArgumentState == 0) { + dispatchArgumentState = -1; + VkShaderModule module = VK_NULL_HANDLE; + try { + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes}; + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &push; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &dispatchArgumentLayout), "vkCreatePipelineLayout dispatch arguments"); + VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + moduleInfo.codeSize = sizeof(DISPATCH_ARGUMENTS_SPV); + moduleInfo.pCode = DISPATCH_ARGUMENTS_SPV; + Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule dispatch arguments"); + VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineInfo.stage.module = module; + pipelineInfo.stage.pName = "main"; + pipelineInfo.layout = dispatchArgumentLayout; + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &dispatchArgumentPipeline), "vkCreateComputePipelines dispatch arguments"); + context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + dispatchArgumentState = 1; + } catch (const std::exception& error) { + if (module != VK_NULL_HANDLE) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + std::fprintf(stderr, "[gpu] indirect dispatches read their group counts unchecked: %s\n", error.what()); + } + } + if (dispatchArgumentState < 0) return false; + recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + function(cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, dispatchArgumentPipeline); + const auto* limit = context.limits.maxComputeWorkGroupCount; + struct { + VkDeviceAddress source; + VkDeviceAddress arguments; + std::array limits; + } parameters{source, arguments, {limit[0], limit[1], limit[2], 0u}}; + static_assert(sizeof(parameters) == PushBytes); + function(cmdPushConstants, "vkCmdPushConstants")(commands, dispatchArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); + function(cmdDispatch, "vkCmdDispatch")(commands, 1, 1, 1); + recordBarrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT); + CountBarriers(CommandClass::DispatchLeading, 2); + return true; +} + void Recorder::CountSamples() { GuestMemory::AssertGpuLockHeld("Recorder::CountSamples"); gpuSampleCounter(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp new file mode 100644 index 000000000000..5b4420522edb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp @@ -0,0 +1,88 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::size_t BlockBytes = 65536; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array StoreCode{0x34020082u, 0x7e040287u, 0xe0701000u, 0x80010201u, 0xbf810000u}; + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), count * 4u, 0x31016facu}; +} + +int Run(AgcDriver::VulkanDevice& device, std::uint32_t* block, std::array groups) { + Output.fill(0xdeadbeefu); + std::memcpy(block, groups.data(), sizeof(groups)); + std::vector userData(8, 0u); + const auto output = BufferDescriptor(Output.data(), Threads); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(StoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {64, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + const auto outcome = device.DispatchIndirect(result, reinterpret_cast(block), {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return outcome.cpuReason; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + auto* block = static_cast(::operator new(BlockBytes, std::align_val_t{BlockBytes})); + std::memset(block, 0, BlockBytes); + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, BlockBytes, true, true, true); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + int failures = 0; + try { + const int invalidPath = Run(*device, block, {0xffffffffu, 1u, 1u}); + if (invalidPath != 0) { + std::printf("skipped, the group counts were read on the CPU (reason %d): the device cannot import the block\n", invalidPath); + } else { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 0xdeadbeefu, "indirect dispatch limit: thread " + std::to_string(tid) + " ran with 0xffffffff groups"); + Require(Run(*device, block, {1u, 1u, 1u}) == 0, "indirect dispatch limit: valid counts left the GPU path"); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 7u, "indirect dispatch limit: thread " + std::to_string(tid) + " stored " + std::to_string(Output[tid]) + " with one group, expected 7"); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + failures = 1; + } + mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + ::operator delete(block, std::align_val_t{BlockBytes}); + if (failures != 0) return 1; + std::puts("indirect dispatch limit tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From f25fbe2b0d49196234eaffe6a87a0880d9036ffd Mon Sep 17 00:00:00 2001 From: Jota <139649225+jotitadev@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:21:41 -0300 Subject: [PATCH 036/176] fix(relinker): detect output aliases with a filesystem probe outside Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresh outputs whose names differ only in case, or that the volume treats as the same name otherwise, were not detected outside Windows. On default macOS APFS, for example, Ä/ä, Ω/ω, Ж/ж and canonically equivalent NFC/NFD names refer to the same file, so one output silently overwrote another. Outside Windows, create the first name in an identity directory next to the outputs and check whether the second name resolves to it. This lets the filesystem decide instead of folding text, so case-sensitive volumes still report distinct names. (cherry picked from commit 502b55722a8edb7204ba0440935f98bd20cb0c8c) --- core/relinker/main.cpp | 16 ++++++-- .../relinker/tests/test_output_collisions.py | 39 ++++++++++++++++++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index f8fe75b960ff..78e75861e896 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -39,8 +39,6 @@ namespace { -#ifdef _WIN32 - class OutputIdentityDirectory { public: OutputIdentityDirectory(const OutputIdentityDirectory&) = delete; @@ -83,8 +81,6 @@ class OutputIdentityDirectory { std::filesystem::path directory; }; -#endif - bool SameOutputFile(const std::filesystem::path& first, const std::filesystem::path& second) { if (std::filesystem::equivalent(first, second)) return true; #ifdef _WIN32 @@ -129,6 +125,18 @@ bool SameOutputDestination(const std::filesystem::path& first, const std::filesy probe.Remove(); return same; } +#else + if (std::filesystem::exists(firstPath.parent_path()) && std::filesystem::exists(secondPath.parent_path()) && + std::filesystem::equivalent(firstPath.parent_path(), secondPath.parent_path())) { + OutputIdentityDirectory probe(firstPath.parent_path()); + const auto name = probe.GetDirectory() / firstPath.filename(); + const auto alternate = probe.GetDirectory() / secondPath.filename(); + std::filesystem::create_directory(name); + const bool same = std::filesystem::exists(alternate) && std::filesystem::equivalent(name, alternate); + std::filesystem::remove(name); + probe.Remove(); + return same; + } #endif return false; } diff --git a/core/relinker/relinker/tests/test_output_collisions.py b/core/relinker/relinker/tests/test_output_collisions.py index 2026412310a5..d897a4ee4bd4 100644 --- a/core/relinker/relinker/tests/test_output_collisions.py +++ b/core/relinker/relinker/tests/test_output_collisions.py @@ -48,11 +48,48 @@ def case_sensitive(directory): return sensitive +def filesystem_aliases(relinker, work): + if os.name == "nt": + return + for windows in (False, True): + for upper, lower in (("A", "a"), ("\u00c4", "\u00e4"), ("\u03a9", "\u03c9"), ("\u0416", "\u0436"), + ("\u00e9", "e\u0301"), ("\u00c4", "a")): + case = work / f"alias-{windows}-{ord(upper)}-{ord(lower[0])}-{len(lower)}" + command, paths, _ = prepare(case, windows) + probe = case / (upper + ".probe") + probe.write_bytes(b"identity probe") + alternate = case / (lower + ".probe") + collide = alternate.exists() and os.path.samefile(probe, alternate) + probe.unlink() + source = case / lower / "b.bin" + source.parent.mkdir() + source.write_bytes(main_fixture()) + suffix = paths["executable"].suffix + output = case / (upper + ".b.bin" + suffix) + secondary = case / (lower + ".b.bin" + suffix) + inputs = {path: path.read_bytes() for path in case.rglob("*") if path.is_file()} + result = subprocess.run([str(relinker), *command[:-2], "--executable", f"{lower}/b.bin", + command[-2], str(output)], cwd=case, + capture_output=True, text=True, encoding="utf-8", timeout=20) + for path, original in inputs.items(): + assert path.read_bytes() == original, path + if collide: + assert result.returncode == 2 and "Output paths collide" in result.stderr, ( + windows, upper, lower, result.stdout, result.stderr) + assert {path for path in case.rglob("*") if path.is_file()} == set(inputs) + else: + assert result.returncode == 0 and output.exists() and secondary.exists(), ( + windows, upper, lower, result.stdout, result.stderr) + assert not os.path.samefile(output, secondary) + assert not list(case.rglob(".anyps5-output-identity-*")) + + def main(): relinker = Path(sys.argv[1]).resolve() checked = 0 with tempfile.TemporaryDirectory(prefix="anyps5-output-collisions-") as directory: work = Path(directory) + filesystem_aliases(relinker, work) for windows in (False, True): command, paths, inputs = prepare(work / f"{windows}-control", windows) links = paths["executable"].parent / "separate-links" @@ -90,7 +127,7 @@ def main(): assert not list(case.rglob(".anyps5-output-identity-*")) checked += 1 names = ["windows-diagnostics-imports.txt"] - if os.name == "nt" and not case_sensitive(work): + if not case_sensitive(work): names.append("WINDOWS-DIAGNOSTICS-IMPORTS.TXT") for index, name in enumerate(names): for existing in (False, True): From 4c36cb314a1f4a5c5b47e0e4ce767e3fb9ced17d Mon Sep 17 00:00:00 2001 From: fdfawef324 <287205748+fdfawef324@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:33:45 -0400 Subject: [PATCH 037/176] fix(shader): give a selection a merge when one arm returns through nested branches mergeBesideReturns ignores the edges into blocks that only lead to a return when it looks for the merge of a selection whose arms do not meet. It only recognized a straight run of single-predecessor blocks ending in a return, so an arm whose return path branches again (an early return inside a nested if/else) still counted as a block of its own, the arms had no common post-dominator, and the whole program fell back to flag structurization ("conditional block 0 has no structured merge"). A block now returns when it has one predecessor, is dominated by the header and every successor returns. Flag structurization turns every original block into a guarded region, and the register phis at the region joins carry values from paths that never reach a later use: in Resident Evil 4 (PPSA07411) pixel program 0x20762add00 the T# of an image_load became Phi(0, load through Phi(user data, compare mask)) and resource analysis refused it. Structured, the T# is the load through the user-data pointer and the program prepares, captures and materializes. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit a2f497a05c5c83faee3bbdba84920fefbfaf8e5e) --- .../libSceAgcDriver/tests/Structurizer.cpp | 15 +++++++++++ .../ControlFlow/src/Structurizer.cpp | 25 +++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp index ca8b02d46541..c3f0359632d7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp @@ -1,6 +1,7 @@ #include "ControlFlow/Structurizer.hpp" #include #include +#include #include #include #include @@ -251,6 +252,17 @@ void requireSameStructuredExecutions(const std::vector>& successors, const char* name) { + if (std::getenv("APS5_FORCE_STRUCTURIZER_FALLBACK") != nullptr) return; + auto graph = makeGraph(successors); + Structurizer{}.Structurize(graph); + for (const auto& block : graph.blocks) { + if (block.terminator.condition == BranchCondition::GotoVariable || block.terminator.gotoValue >= 0) { + throw std::runtime_error(std::string(name) + ": block " + std::to_string(block.id) + " routes through a goto variable instead of a structured selection"); + } + } +} + void requireGrowthBudgetRefusal(ControlFlowGraph graph, const char* name) { const std::string growthBudget = "CFG shared merge splitting exceeded growth budget"; try { @@ -423,6 +435,9 @@ int main() { requireStructuredBranches(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge"); requireDominatedMerges(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge"); requireOriginalBranches(elseReturn, elseReturnSuccessors, "an early return in one arm of a selection whose other arm reaches the same merge"); + const std::vector> branchingReturn{{5, 1}, {3, 2}, {13}, {9, 4}, {13}, {10, 6}, {8, 7}, {13}, {13, 9}, {13}, {12, 11}, {12}, {13}, {}}; + requireSameStructuredExecutions(branchingReturn, "an arm whose returns branch once a tail shared across arms is cloned"); + requireNoRouteVariables(branchingReturn, "an arm whose returns branch once a tail shared across arms is cloned"); std::vector> diamonds; for (std::uint32_t diamond = 0; diamond < 50u; ++diamond) { const auto head = diamond * 3u; diff --git a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp index 3d80b21d8eec..3267461138ca 100644 --- a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp +++ b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp @@ -530,12 +530,27 @@ std::uint32_t mergeBesideReturns(const ControlFlowGraph& graph, const BasicBlock } } const auto count = static_cast(graph.blocks.size()); - const auto returns = [&](std::uint32_t blockId) { - if (!graph.Dominates(header.id, blockId) || !hasLinearPathToTerminal(graph, blockId)) { - return false; + enum class Returning : std::uint8_t { Unknown, Visiting, No, Yes }; + std::vector returning(count, Returning::Unknown); + const auto returns = [&](std::uint32_t start) { + std::vector pending{start}; + while (!pending.empty()) { + const auto blockId = pending.back(); + const auto& block = graph.FindBlock(blockId); + if (returning[blockId] == Returning::Unknown) { + if (block.predecessors.size() == 1u && graph.Dominates(header.id, blockId)) { + returning[blockId] = Returning::Visiting; + std::copy_if(block.successors.begin(), block.successors.end(), std::back_inserter(pending), [&](std::uint32_t successor) { return returning[successor] == Returning::Unknown; }); + continue; + } + returning[blockId] = Returning::No; + } else if (returning[blockId] == Returning::Visiting) { + const bool privateReturn = std::all_of(block.successors.begin(), block.successors.end(), [&](std::uint32_t successor) { return returning[successor] == Returning::Yes; }); + returning[blockId] = privateReturn ? Returning::Yes : Returning::No; + } + pending.pop_back(); } - const auto tail = linearTail(graph, blockId); - return std::all_of(tail.begin(), tail.end(), [&](std::uint32_t member) { return graph.FindBlock(member).predecessors.size() == 1u; }); + return returning[start] == Returning::Yes; }; std::vector> kept(count); for (const auto& block : graph.blocks) { From 32284dab27e2248f76b65ae1f36b97bb297f603a Mon Sep 17 00:00:00 2001 From: fogydes Date: Sun, 18 Oct 2026 22:01:24 +0530 Subject: [PATCH 038/176] fix(relinker): guard base relocations against page boundary overflow and deduplicate entries (cherry picked from commit f36bdeba2bfea68a89d7384831c8b7a4b07d10d9) --- .../src/windows/WindowsRelocationBuilder.cpp | 13 ++++++++++-- .../relinker/tests/test_none_relocations.py | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp index 524a4d790cea..510edeac077b 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,8 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom const auto symbol = info >> 32; if ((plt && type != 7) || (type != 1 && type != 6 && type != 7 && type != 8 && type != 16 && type != 17 && type != 18)) throw Domain::RelinkerException("Unsupported Windows relocation type " + std::to_string(type), target); + if (target > std::numeric_limits::max() - 8) + throw Domain::RelinkerException("Relocation target address overflow", target); const auto rva = image.GetRva(target, 8); const auto next = ranges.lower_bound(target); if ((next != ranges.end() && next->first < target + 8) || (next != ranges.begin() && std::prev(next)->second > target)) @@ -81,8 +84,14 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom std::vector WindowsRelocationBuilder::BuildBaseRelocations(const std::vector& targets) const { std::map> pages; - for (const auto target : targets) - pages[target & ~0xfffu].push_back(static_cast(0xa000u | (target & 0xfffu))); + for (const auto target : targets) { + if ((target & 0xfffu) > 0x1000u - 8) + throw Domain::RelinkerException("Base relocation target crosses a page boundary", target); + auto& entries = pages[target & ~0xfffu]; + const auto entry = static_cast(0xa000u | (target & 0xfffu)); + if (std::find(entries.begin(), entries.end(), entry) == entries.end()) + entries.push_back(entry); + } std::vector result; for (const auto& [page, entries] : pages) { Io::AppendU32(result, page); diff --git a/core/relinker/relinker/tests/test_none_relocations.py b/core/relinker/relinker/tests/test_none_relocations.py index cbf3d4cb3666..538b0247d8f3 100644 --- a/core/relinker/relinker/tests/test_none_relocations.py +++ b/core/relinker/relinker/tests/test_none_relocations.py @@ -110,6 +110,26 @@ def main(): capture_output=True, text=True, timeout=20) assert result.returncode == 2 and "RELATIVE relocation has a nonzero symbol index" in result.stderr, (name, result.stderr) assert not output.exists() + overflow_image = image_with_relocations([(0xfffffffffffffff9, 8, 0x210)], False, False) + overflow_source = work / "invalid-relocation-overflow.elf" + overflow_source.write_bytes(overflow_image) + overflow_output = work / "invalid-relocation-overflow.exe" + overflow_result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", + str(overflow_source), str(overflow_output)], + capture_output=True, text=True, timeout=20) + assert overflow_result.returncode == 2 and "Relocation target address overflow" in overflow_result.stderr, overflow_result.stderr + assert not overflow_output.exists() + page_image = image_with_relocations([(0xffc, 8, 0x210)], False, False) + page_image.extend(bytes(0x1000)) + struct.pack_into(" Date: Sat, 17 Oct 2026 16:12:37 +0530 Subject: [PATCH 039/176] fix(relinker): resolve EIP-relative GOT accesses and guard target overflow (cherry picked from commit beb615fbec084bf834f66ddfa74a7e9424285297) --- .../analysis/UnusedNidFilter/GotAccessIndex.cpp | 3 +++ .../tests/test_unused_filter_indirect.py | 16 +++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp index e9405ca433af..9d1e5a3c2e3b 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp @@ -43,7 +43,10 @@ std::unique_ptr BuildGotAccessIndex( VirtualAddress nextVaddr = va + static_cast(info.Length); std::int32_t disp = 0; std::memcpy(&disp, text.data() + bufOff + info.RipRelativeDispOffset, 4); + if (disp < 0 && nextVaddr < static_cast(-static_cast(disp))) continue; + if (disp > 0 && std::numeric_limits::max() - nextVaddr < static_cast(disp)) continue; VirtualAddress target = nextVaddr + static_cast(static_cast(disp)); + if (info.AddressSize32) target = static_cast(target); accessed.insert(target); } diff --git a/core/relinker/relinker/tests/test_unused_filter_indirect.py b/core/relinker/relinker/tests/test_unused_filter_indirect.py index f3fd080ab4f0..a0f50a2f8eec 100644 --- a/core/relinker/relinker/tests/test_unused_filter_indirect.py +++ b/core/relinker/relinker/tests/test_unused_filter_indirect.py @@ -17,16 +17,16 @@ def rel(next_address, target): return struct.pack(" Date: Sun, 18 Oct 2026 19:00:50 +0530 Subject: [PATCH 040/176] fix(relinker): validate relative relocation table bounds, dynamic tag variants, and slot targets (cherry picked from commit 324a802fdf2ff47cd8fec4deca59efc52d5badd3) --- .../RelativeRelocationIndex.cpp | 107 +++++++++++++++--- .../relinker/relinker/tests/test_rela_size.py | 32 ++++++ 2 files changed, 121 insertions(+), 18 deletions(-) diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp index 1d0be749bcbc..d6a5eb9ffe74 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Relinker::UnusedNidFilter { @@ -90,7 +91,12 @@ void collectRelativeEntries( if (relType != R_X86_64_RELATIVE) continue; - out[static_cast(rOffset)] = static_cast(rAddend); + if (rAddend < 0) + throw RelinkerException("Relative relocation target address is negative", rOffset); + + auto [it, inserted] = out.emplace(static_cast(rOffset), static_cast(rAddend)); + if (!inserted && it->second != static_cast(rAddend)) + throw RelinkerException("Conflicting relative relocation targets for slot", rOffset); } } @@ -155,6 +161,7 @@ std::unique_ptr BuildRelativeRelocationIndex( } std::unordered_map targets; + bool hasDynamic = false; for (std::uint16_t i = 0; i < phCount; ++i) { std::size_t phPos = static_cast(phOff) + i * phEntSize; @@ -162,43 +169,107 @@ std::unique_ptr BuildRelativeRelocationIndex( std::uint32_t type = read32(elfBytes, phPos); if (type != PT_DYNAMIC) continue; + if (hasDynamic) throw RelinkerException("Multiple PT_DYNAMIC segments", phPos); + hasDynamic = true; std::uint64_t segOff = read64(elfBytes, phPos + 8); std::uint64_t segSz = read64(elfBytes, phPos + 32); if (segOff > elfBytes.size() || segSz > elfBytes.size() - segOff) throw RelinkerException("PT_DYNAMIC segment out of bounds"); - - VirtualAddress relaVa = 0; - std::uint64_t relaSz = 0; - VirtualAddress jmprelVa = 0; - std::uint64_t pltrelsz = 0; - bool relaSce = false; - bool jmprelSce = false; + if (segSz % 16 != 0) throw RelinkerException("Invalid dynamic segment size", segOff); + + std::optional relaVa; + std::optional relaSz; + std::optional jmprelVa; + std::optional pltrelsz; + bool hasSysvRela = false; + bool hasOsRela = false; + bool hasSysvRelaSz = false; + bool hasOsRelaSz = false; + bool hasSysvJmprel = false; + bool hasOsJmprel = false; + bool hasSysvPltrelsz = false; + bool hasOsPltrelsz = false; + bool terminated = false; for (std::uint64_t off = 0; off <= segSz && segSz - off >= 16; off += 16) { std::size_t pos = static_cast(segOff + off); std::int64_t tag = static_cast(read64(elfBytes, pos)); std::uint64_t val = read64(elfBytes, pos + 8); - if (tag == DT_NULL) break; - if (tag == DT_RELA || tag == DT_OS_RELA) { + if (tag == DT_NULL) { + terminated = true; + break; + } + if (tag == DT_RELA) { + if (hasOsRela) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELA"); + if (hasSysvRela && relaVa.value() != val) throw RelinkerException("Conflicting duplicate DT_RELA tags"); + hasSysvRela = true; relaVa = val; - relaSce = tag == DT_OS_RELA; } - if (tag == DT_RELASZ || tag == DT_OS_RELASZ) relaSz = val; - if (tag == DT_JMPREL || tag == DT_OS_JMPREL) { + if (tag == DT_OS_RELA) { + if (hasSysvRela) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELA"); + if (hasOsRela && relaVa.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_RELA tags"); + hasOsRela = true; + relaVa = val; + } + if (tag == DT_RELASZ) { + if (hasOsRelaSz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELASZ"); + if (hasSysvRelaSz && relaSz.value() != val) throw RelinkerException("Conflicting duplicate DT_RELASZ tags"); + hasSysvRelaSz = true; + relaSz = val; + } + if (tag == DT_OS_RELASZ) { + if (hasSysvRelaSz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELASZ"); + if (hasOsRelaSz && relaSz.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_RELASZ tags"); + hasOsRelaSz = true; + relaSz = val; + } + if (tag == DT_JMPREL) { + if (hasOsJmprel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_JMPREL"); + if (hasSysvJmprel && jmprelVa.value() != val) throw RelinkerException("Conflicting duplicate DT_JMPREL tags"); + hasSysvJmprel = true; jmprelVa = val; - jmprelSce = tag == DT_OS_JMPREL; } - if (tag == DT_PLTRELSZ || tag == DT_OS_PLTRELSZ) pltrelsz = val; + if (tag == DT_OS_JMPREL) { + if (hasSysvJmprel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_JMPREL"); + if (hasOsJmprel && jmprelVa.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_JMPREL tags"); + hasOsJmprel = true; + jmprelVa = val; + } + if (tag == DT_PLTRELSZ) { + if (hasOsPltrelsz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_PLTRELSZ"); + if (hasSysvPltrelsz && pltrelsz.value() != val) throw RelinkerException("Conflicting duplicate DT_PLTRELSZ tags"); + hasSysvPltrelsz = true; + pltrelsz = val; + } + if (tag == DT_OS_PLTRELSZ) { + if (hasSysvPltrelsz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_PLTRELSZ"); + if (hasOsPltrelsz && pltrelsz.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_PLTRELSZ tags"); + hasOsPltrelsz = true; + pltrelsz = val; + } } - collectRelativeEntries(elfBytes, loads, relaVa, relaSz, relaSce, sceTableBase, targets); - collectRelativeEntries(elfBytes, loads, jmprelVa, pltrelsz, jmprelSce, sceTableBase, targets); + if (!terminated) throw RelinkerException("Unterminated dynamic segment", segOff); - break; + if ((hasSysvRela && hasOsRelaSz) || (hasOsRela && hasSysvRelaSz)) + throw RelinkerException("Mismatched DT_RELA and DT_RELASZ tag variants"); + if (relaVa.has_value() != relaSz.has_value()) + throw RelinkerException("Incomplete relocation table metadata"); + + if ((hasSysvJmprel && hasOsPltrelsz) || (hasOsJmprel && hasSysvPltrelsz)) + throw RelinkerException("Mismatched DT_JMPREL and DT_PLTRELSZ tag variants"); + if (jmprelVa.has_value() != pltrelsz.has_value()) + throw RelinkerException("Incomplete PLT relocation table metadata"); + + if (relaVa.has_value()) + collectRelativeEntries(elfBytes, loads, *relaVa, *relaSz, hasOsRela, sceTableBase, targets); + if (jmprelVa.has_value()) + collectRelativeEntries(elfBytes, loads, *jmprelVa, *pltrelsz, hasOsJmprel, sceTableBase, targets); } return std::make_unique(std::move(targets)); } } + diff --git a/core/relinker/relinker/tests/test_rela_size.py b/core/relinker/relinker/tests/test_rela_size.py index b9b6c4ca59c9..d010e2381495 100644 --- a/core/relinker/relinker/tests/test_rela_size.py +++ b/core/relinker/relinker/tests/test_rela_size.py @@ -40,6 +40,38 @@ def main(): valid = tags.get(8, 0) == size if not valid: failures.append((tag, size, mode, result.returncode, result.stdout, result.stderr)) + + def verify_failure(name, image, expected_error, options=("unused-filter=1",)): + source = work / f"{name}.elf" + output = work / f"{name}.out" + source.write_bytes(image) + result = subprocess.run([str(relinker), "--skip-sce-module", *options, str(source), str(output)], + capture_output=True, text=True, timeout=20) + if result.returncode != 2 or expected_error not in result.stderr or output.exists(): + failures.append((name, result.returncode, result.stdout, result.stderr)) + + neg_image = fixture() + struct.pack_into(" Date: Sun, 18 Oct 2026 22:25:51 +0530 Subject: [PATCH 041/176] fix(relinker): reject program header table overlapping the extra block (cherry picked from commit 93d3f89b512d79454981aa92e37d580bbf044060) --- .../src/general/ProgramHeaderLayoutBuilder.cpp | 10 ++++++++-- .../elfpatcher/tests/ProgramLayoutRangeTests.cpp | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp index 8f3ecfcb5b03..79d212b58020 100644 --- a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp @@ -125,6 +125,8 @@ std::uint64_t ProgramHeaderLayoutBuilder::GuestLoadPageEnd( if (ph.Type != PT_LOAD) continue; foundLoad = true; + if (ph.FileSize > ph.MemorySize) + throw Domain::RelinkerException("PT_LOAD file size exceeds memory size", ph.Offset); if (ph.MemorySize > std::numeric_limits::max() - ph.MappedAddress) throw Domain::RelinkerException("Load segment virtual range overflowed"); const std::uint64_t vaddrEnd = ph.MappedAddress + ph.MemorySize; @@ -171,8 +173,12 @@ std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( if (tableEnd > buf.size()) throw Domain::RelinkerException("Program header table exceeds the output file"); const bool tableInExtraBlock = request.PhOff >= request.ExtraBlockOffset; - if (tableInExtraBlock && tableEnd - request.ExtraBlockOffset > request.ExtraBlockSize) - throw Domain::RelinkerException("Program header table does not fit within the extra block"); + if (tableInExtraBlock) { + if (tableEnd - request.ExtraBlockOffset > request.ExtraBlockSize) + throw Domain::RelinkerException("Program header table does not fit within the extra block"); + } else if (tableEnd > request.ExtraBlockOffset) { + throw Domain::RelinkerException("Program header table overlaps the extra block"); + } const std::uint64_t headerBlockSize = tableInExtraBlock ? kElfHeaderSize : tableEnd; if (request.ExtraBlockOffset > buf.size() || request.ExtraBlockSize > buf.size() - request.ExtraBlockOffset) throw Domain::RelinkerException("Extra block exceeds the output file"); diff --git a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp index 458cd8bc2d83..1d1bffbe7598 100644 --- a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp +++ b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp @@ -97,7 +97,7 @@ int main() { case 7: request.InterpOffset = maximum; break; case 8: request.ExtraBlockOffset = maximum; break; case 9: request.ExtraBlockSize = 0x1000; break; - case 10: request.PhOff = 0xF00; request.ExtraBlockVaddr = maximum - 0x1fff; break; + case 10: request.PhOff = 0xF00; break; } std::vector bytes(0x1100, 0xA5); const auto before = bytes; From 1a4f3cb21b6cfe4ce1da6348a686e3462d6d7679 Mon Sep 17 00:00:00 2001 From: fogydes Date: Sun, 18 Oct 2026 23:05:27 +0530 Subject: [PATCH 042/176] fix(relinker): validate section header table bounds and reject overlapping sections (cherry picked from commit 5536aa8c0d3c03fa6452f4e5526259e746f65fd0) --- .../src/general/SectionHeaderTableBuilder.cpp | 31 +++++++++++++++++-- .../elfpatcher/tests/SectionRangeTests.cpp | 9 ++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp index a7c4cc1f0f78..e07068514fa2 100644 --- a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp @@ -1,8 +1,10 @@ #include #include #include -#include +#include #include +#include +#include namespace Elfpatcher { @@ -41,14 +43,39 @@ void SectionHeaderTableBuilder::WriteTable( std::vector& buf, const SectionHeaderTableRequest& request ) const { + if (buf.size() < kElfHeaderSize) + throw std::runtime_error("Output program header table is out of bounds"); const auto phOffset = Io::ReadU64(buf, kEhdrPhOffOffset); const auto phCount = Io::ReadU16(buf, kEhdrPhNumOffset); const auto phStride = Io::ReadU16(buf, kEhdrPhEntSizeOffset); - if (phStride < 56 || phOffset > buf.size() || phCount > (buf.size() - phOffset) / phStride) + if (phStride < 56 || (phCount > 0 && phOffset < kElfHeaderSize) || phOffset > buf.size() || phCount > (buf.size() - phOffset) / phStride) throw std::runtime_error("Output program header table is out of bounds"); + const auto phTableEnd = phOffset + static_cast(phCount) * phStride; + const std::array, 4> sections{{ + {request.DynStrOffset, request.DynStrSize}, + {request.DynSymOffset, request.DynSymSize}, + {request.DynamicSegmentOffset, request.DynamicSegmentSize}, + {request.StubOffset, request.StubSize}, + }}; + for (std::size_t i = 0; i < sections.size(); ++i) { + if (sections[i].second == 0) continue; + for (std::size_t j = i + 1; j < sections.size(); ++j) { + if (sections[j].second == 0) continue; + const auto startA = sections[i].first; + const auto endA = startA + sections[i].second; + const auto startB = sections[j].first; + const auto endB = startB + sections[j].second; + if (startA < endB && startB < endA) + throw std::runtime_error("Allocated output sections overlap"); + } + } const auto mappedAddress = [&](std::uint64_t offset, std::uint64_t sectionSize) { if (offset > buf.size() || sectionSize > buf.size() - offset) throw std::runtime_error("Allocated output section exceeds file"); + if (offset < kElfHeaderSize && sectionSize > 0) + throw std::runtime_error("Allocated output section overlaps the ELF header"); + if (sectionSize > 0 && phCount > 0 && offset < phTableEnd && phOffset < offset + sectionSize) + throw std::runtime_error("Allocated output section overlaps the program header table"); for (std::uint16_t index = 0; index < phCount; ++index) { const auto header = phOffset + index * phStride; if (Io::ReadU32(buf, header) != PT_LOAD) continue; diff --git a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp index 7f718b66ce71..c75c4e977347 100644 --- a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp +++ b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp @@ -29,7 +29,7 @@ int main() { auto empty = valid; empty.StubOffset = 384; empty.StubSize = 0; bytes = Fixture(); builder.WriteTable(bytes, empty); Require(Io::ReadU64(bytes, Io::ReadU64(bytes, 40) + 5 * 64 + 16) == 0x1100); - for (int kind = 0; kind < 8; ++kind) { + for (int kind = 0; kind < 11; ++kind) { bytes = Fixture(); auto request = valid; switch (kind) { case 0: request.DynStrSize = 257; break; @@ -40,12 +40,15 @@ int main() { case 5: Io::WriteU64(bytes, 32, std::numeric_limits::max()); break; case 6: Io::WriteU64(bytes, 80, std::numeric_limits::max() - 4); break; case 7: Io::WriteU16(bytes, 56, 20); break; + case 8: request.DynStrSize = 16; break; + case 9: bytes.resize(40); break; + case 10: Io::WriteU64(bytes, 32, 48); Io::WriteU32(bytes, 48, 1); Io::WriteU64(bytes, 56, 128); break; } const auto before = bytes; bool rejected = false; - try { builder.WriteTable(bytes, request); } catch (const std::exception&) { rejected = true; } + try { builder.WriteTable(bytes, request); } catch (const std::runtime_error&) { rejected = true; } Require(rejected && bytes == before); } - std::cout << "10 section range cases passed\n"; + std::cout << "13 section range cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } From 52e3c0476250bd7670a5bdb47199e4a4de168d9b Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 16:26:04 +0400 Subject: [PATCH 043/176] refactor(relinker): take the macOS image base from the load image WriteMacOsImage wrote the Windows ImageBase constant into the Mach-O segment and section addresses, __PAGEZERO, the metadata load bias, the TLS descriptors and the init pointer, while the relocations it rebases were computed from the WindowsLoadImage base. Read the base from the load image everywhere instead. Every caller still builds its image at ImageBase, so the output is unchanged. (cherry picked from commit 96e373f8ad16b68b42c68be81ad3b5ddb03bf747) --- .../elfpatcher/src/macos/MacOsImage.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 81fc38d4db65..8b0800faa95c 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -20,7 +20,6 @@ namespace Elfpatcher::MacOs { namespace { -using Windows::ImageBase; using Windows::LoadRva; constexpr std::uint64_t PageSize = 0x1000; @@ -381,6 +380,7 @@ std::uint64_t GuestTlsBlockSize(const Domain::ProgramHeader& tls) { std::vector WriteMacOsImage(MacOsImageInput& input) { auto& image = *input.Image; const auto& headers = *input.Headers; + const auto imageBase = image.GetImageBase(); const auto tls = ScanTls(*input.Source, headers, image); const bool hasTls = tls.Tls != nullptr && tls.Tls->MemorySize != 0; if (!hasTls && !tls.Accesses.empty()) throw Domain::RelinkerException("Guest TLS access without a non-empty PT_TLS"); @@ -436,7 +436,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { Io::WriteU64(meta, 0, MetaVersion); const auto firstLoad = std::find_if(headers.begin(), headers.end(), [](const auto& header) { return header.Type == 1; }); if (firstLoad == headers.end()) throw Domain::RelinkerException("No PT_LOAD segments found"); - Io::WriteU64(meta, MetaLoadBias, ImageBase + image.GetRva(firstLoad->MappedAddress) - firstLoad->MappedAddress); + Io::WriteU64(meta, MetaLoadBias, imageBase + image.GetRva(firstLoad->MappedAddress) - firstLoad->MappedAddress); input.Rebases.push_back(metaRva + MetaLoadBias); Io::WriteU64(meta, MetaHeaderCount, headers.size()); for (std::size_t index = 0; index < headers.size(); ++index) { @@ -478,7 +478,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { if (!hasTls) throw Domain::RelinkerException("Guest TLS export has no TLS block: " + variable.Symbol); if (variable.Offset >= tls.Tls->MemorySize) throw Domain::RelinkerException("Guest TLS export is outside the TLS block: " + variable.Symbol); const auto at = tlsExports + index * 16; - Io::WriteU64(meta, at, ImageBase + descriptor); + Io::WriteU64(meta, at, imageBase + descriptor); Io::WriteU64(meta, at + 8, variable.Offset); input.Rebases.push_back(metaRva + static_cast(at)); input.Exports.push_back({variable.Symbol, metaRva + static_cast(at), variable.Flags}); @@ -498,7 +498,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { } Io::WriteU64(meta, MetaTlsDescriptor, 1); if (header.FileSize != 0) { - Io::WriteU64(meta, MetaTlsDescriptor + 8, ImageBase + image.GetRva(header.MappedAddress, header.FileSize)); + Io::WriteU64(meta, MetaTlsDescriptor + 8, imageBase + image.GetRva(header.MappedAddress, header.FileSize)); input.Rebases.push_back(descriptor + 8); } Io::WriteU64(meta, MetaTlsDescriptor + 0x10, header.FileSize); @@ -508,7 +508,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { input.Binds.push_back({"_Aps5GuestTlsRegister_nid_no_patch", metaRva + static_cast(MetaRegisterSlot)}); for (const auto slot : input.TlsModuleSlots) { auto& segment = SegmentAt(segments, slot, 8); - Io::WriteU64(segment.Data, slot - segment.Rva, ImageBase + descriptor); + Io::WriteU64(segment.Data, slot - segment.Rva, imageBase + descriptor); input.Rebases.push_back(slot); } @@ -623,7 +623,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { code.Rip({0xff, 0x15}, metaRva + static_cast(MetaCxaAtexitSlot)); } code.Emit({0x5d, 0xc3}); - Io::WriteU64(meta, initPointer, ImageBase + initStub); + Io::WriteU64(meta, initPointer, imageBase + initStub); input.Rebases.push_back(metaRva + static_cast(initPointer)); dataSections.push_back({"__mod_init_func", initPointer, 8, SectionModInitFuncPointers, 3}); } @@ -822,19 +822,19 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { Io::WriteU64(commands, at + 48, filesize); }; std::size_t index = 0; - if (input.Executable) writeSegment(segmentCommandOffsets[index++], 0, ImageBase, 0, 0); - writeSegment(segmentCommandOffsets[index++], ImageBase, LoadRva, 0, LoadRva); + if (input.Executable) writeSegment(segmentCommandOffsets[index++], 0, imageBase, 0, 0); + writeSegment(segmentCommandOffsets[index++], imageBase, LoadRva, 0, LoadRva); for (; index < segments.size(); ++index) { const auto& segment = segments[index]; - writeSegment(segmentCommandOffsets[index], ImageBase + segment.Rva, AlignUp(segment.MemorySize, PageSize), segment.FileOffset, segment.Data.size()); + writeSegment(segmentCommandOffsets[index], imageBase + segment.Rva, AlignUp(segment.MemorySize, PageSize), segment.FileOffset, segment.Data.size()); for (std::size_t sectionIndex = 0; sectionIndex < segment.Sections.size(); ++sectionIndex) { const auto& section = segment.Sections[sectionIndex]; const auto at = segmentCommandOffsets[index] + 72 + sectionIndex * 80; - Io::WriteU64(commands, at + 32, ImageBase + segment.Rva + section.Offset); + Io::WriteU64(commands, at + 32, imageBase + segment.Rva + section.Offset); Io::WriteU32(commands, at + 48, static_cast(segment.FileOffset + section.Offset)); } } - writeSegment(linkeditCommand, ImageBase + imageEnd, AlignUp(linkeditSize, PageSize), linkeditOffset, linkeditSize); + writeSegment(linkeditCommand, imageBase + imageEnd, AlignUp(linkeditSize, PageSize), linkeditOffset, linkeditSize); Io::WriteU32(commands, dyldInfoCommand + 8, static_cast(rebaseOffset)); Io::WriteU32(commands, dyldInfoCommand + 12, static_cast(rebase.size())); Io::WriteU32(commands, dyldInfoCommand + 16, static_cast(bindOffset)); From 2dafd86d6686855bf49a3a8a991dbea2794d643f Mon Sep 17 00:00:00 2001 From: fogydes Date: Tue, 13 Oct 2026 21:49:29 +0530 Subject: [PATCH 044/176] fix(relinker): validate syscall scanner code section bounds and offset overflow (cherry picked from commit 80a987f01def4f978b264f99c53d3928b61c6392) --- .../relinker/src/analysis/SyscallScanner.cpp | 7 ++++++- .../relinker/tests/SdkMmanSyscallTests.cpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/relinker/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/relinker/src/analysis/SyscallScanner.cpp index 5b4e47de6c0f..3e301c843864 100644 --- a/core/relinker/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/relinker/src/analysis/SyscallScanner.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -189,14 +190,18 @@ std::vector SyscallScanner::ScanCodeSectionForSyscalls( const std::vector& codeSection, const FileByteOffset codeSectionOffset, const FileByteOffset codeSectionSize) { - const std::size_t limit = std::min(codeSection.size(), static_cast(codeSectionSize)); + if (codeSectionSize > codeSection.size()) throw RelinkerException("Code section exceeds the input buffer", codeSectionOffset); + if (codeSectionSize > std::numeric_limits::max() - codeSectionOffset) throw RelinkerException("Code section offset overflow", codeSectionOffset); + const auto limit = static_cast(codeSectionSize); RecentInstructions recent; std::vector trapped; const auto scanner = Codegen::MakeInstructionScanner(); scanner->VisitCodeSection(codeSection, codeSectionOffset, codeSectionSize, [&](const Codegen::InstructionMatch& match) { recent.Push(match); + if (match.Offset < codeSectionOffset) throw RelinkerException("Instruction match precedes code section offset", match.Offset); const std::size_t start = static_cast(match.Offset - codeSectionOffset); + if (start > codeSection.size() || match.Length > codeSection.size() - start) throw RelinkerException("Instruction match exceeds code section", match.Offset); const auto opcodeOffset = Codegen::DecodedInstruction{codeSection.data() + start, match.Length}.OpcodeOffset(); const std::size_t opcode = start + opcodeOffset; const std::size_t spanLimit = std::min(limit, start + static_cast(match.Length)); diff --git a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp index 264cb6057c7d..1c73264ae26d 100644 --- a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp +++ b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,20 @@ void CheckFixtures() { Require(Rejected(embedded, true, embedded.size()), "SDK matching accepted a prefix that starts inside an immediate"); for (const auto& code : std::vector{{0xB8, 0xDD, 0x01, 0x00, 0x00, 0x0F, 0x05, 0xC3}, {0xCD, 0x80}, {0x0F, 0x34}, {0x0F, 0x07}}) Require(Rejected(code, true, code.size()), "SDK trap mode accepted an unrelated privileged instruction"); + bool overflowRejected = false; + try { + Relinker::MakeSyscallScanner(true)->ScanCodeSectionForSyscalls(Fixtures.front().code, std::numeric_limits::max(), 1); + } catch (const Domain::RelinkerException& error) { + overflowRejected = std::string(error.what()) == "Code section offset overflow"; + } + Require(overflowRejected, "Scanner accepted code section offset overflow"); + bool sizeExceededRejected = false; + try { + Relinker::MakeSyscallScanner(true)->ScanCodeSectionForSyscalls(Fixtures.front().code, 0, Fixtures.front().code.size() + 1); + } catch (const Domain::RelinkerException& error) { + sizeExceededRejected = std::string(error.what()) == "Code section exceeds the input buffer"; + } + Require(sizeExceededRejected, "Scanner accepted code section size exceeding input"); std::cout << "SDK inline syscall checks passed, " << rejectedMutations << " near-match rejections\n"; } From 6823f82e5a6a34f6b2d20aa0acef90c38a1777d5 Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Sun, 18 Oct 2026 10:50:02 +0200 Subject: [PATCH 045/176] perf(agc): skip the staging and write-back leading barriers a trailing barrier already covers (cherry picked from commit 91033ce36fe3b91198decb85bb429c031ffe9b81) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 9 +- .../Graphics/include/BarrierBatch.hpp | 25 ++++ .../Graphics/include/Recorder.hpp | 2 + .../Graphics/src/BarrierBatch.cpp | 27 +++++ .../Graphics/src/GuestBufferMemory.cpp | 20 +++- .../libSceAgcDriver/Graphics/src/Recorder.cpp | 7 ++ .../libSceAgcDriver/Graphics/src/Texture.cpp | 4 +- .../libSceAgcDriver/tests/BarrierBatch.cpp | 113 ++++++++++++++++++ 8 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp create mode 100644 core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 756939ad98a9..de2b07de3a65 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -211,6 +211,7 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureDetiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Texture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PassHazards.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/BarrierBatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/UnitShadow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Sampler.cpp ${agcTextureDetileShaderHeader} @@ -691,6 +692,12 @@ target_link_libraries(agc_pass_hazards_tests PRIVATE libc) set_target_properties(agc_pass_hazards_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_pass_hazards_tests) +add_test_executable(agc_barrier_batch_tests EXCLUDE_FROM_ALL tests/BarrierBatch.cpp Graphics/src/BarrierBatch.cpp) +target_include_directories(agc_barrier_batch_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +target_link_libraries(agc_barrier_batch_tests PRIVATE libc) +set_target_properties(agc_barrier_batch_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_barrier_batch_tests) + add_test_executable(agc_dcc_key_sharing_tests EXCLUDE_FROM_ALL tests/DccKeySharing.cpp) target_include_directories(agc_dcc_key_sharing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_dcc_key_sharing_tests PRIVATE libc) @@ -1352,7 +1359,7 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_bindless agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests) + foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests) if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests") continue() endif() diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp new file mode 100644 index 000000000000..3cd1257efdee --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp @@ -0,0 +1,25 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BARRIERBATCH_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BARRIERBATCH_HPP + +#ifndef VK_NO_PROTOTYPES +#define VK_NO_PROTOTYPES +#endif +#include + +namespace AgcDriver::Graphics { + +struct BarrierMask { + VkPipelineStageFlags srcStage = 0; + VkPipelineStageFlags dstStage = 0; + VkAccessFlags srcAccess = 0; + VkAccessFlags dstAccess = 0; + bool changesLayout = false; + bool transfersQueue = false; +}; + +bool Covers(const BarrierMask& outer, const BarrierMask& inner); +bool OrderedByTrailing(VkAccessFlags covered, const BarrierMask& wanted); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 6d157e115117..d691f510e9fc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RECORDER_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RECORDER_HPP +#include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" #include "prx/libSceAgcDriver/Graphics/include/PassHazards.hpp" #include @@ -462,6 +463,7 @@ class Recorder { // (or APS5_NO_BARRIER_ELISION=1) records every leading barrier, as before. static bool MergeBarriers(); static void CountMerged(CommandClass which); + static bool ElideCovered(CommandClass which, const BarrierMask& wanted, VkAccessFlags covered); // Hazard tracker, counting mode (APS5_BARRIER_VALIDATE=1): every command tells the tracker // what it reads and writes (guest ranges through host imports, images) with its stage before // it is recorded; the tracker simulates barriers emitted only on a hazard against the accesses diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp new file mode 100644 index 000000000000..0b7197ddc6fe --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp @@ -0,0 +1,27 @@ +#include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" + +namespace AgcDriver::Graphics { + +namespace { + +bool StageCovers(VkPipelineStageFlags outer, VkPipelineStageFlags inner) { + auto rest = inner & ~outer; + if ((outer & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT) != 0) rest &= VK_PIPELINE_STAGE_HOST_BIT; + return rest == 0; +} + +} + +bool Covers(const BarrierMask& outer, const BarrierMask& inner) { + if (inner.changesLayout || inner.transfersQueue) return false; + return StageCovers(outer.srcStage, inner.srcStage) && StageCovers(outer.dstStage, inner.dstStage) && (inner.srcAccess & ~outer.srcAccess) == 0 && (inner.dstAccess & ~outer.dstAccess) == 0; +} + +bool OrderedByTrailing(VkAccessFlags covered, const BarrierMask& wanted) { + if (wanted.changesLayout || wanted.transfersQueue || (wanted.dstStage & VK_PIPELINE_STAGE_HOST_BIT) != 0) return false; + constexpr VkAccessFlags shaderAndTransfer = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + const VkAccessFlags needed = shaderAndTransfer | wanted.dstAccess; + return (covered & needed) == needed; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 7ba2d0dd83c7..0fe94118f3ec 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -3377,15 +3377,19 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad recorder->FlushKeyStoresOverlapping(region->begin, static_cast(region->end - region->begin)); recorder->FlushStoresOverlapping(region->begin, static_cast(region->end - region->begin)); } - const auto commands = recorder->Commands(); + VkAccessFlags covered = 0; + const auto commands = recorder->Commands(&covered); const auto timing = recorder->BeginGpuTiming(StagingCopyInKey); if (Recorder::BarrierValidate()) { std::vector> reads; for (const auto* region : copies) reads.emplace_back(region->begin, region->end); recorder->NoteAccess(Recorder::CommandClass::StagingIn, Recorder::Access{reads, {}, {}, VK_PIPELINE_STAGE_TRANSFER_BIT}); } - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - Recorder::CountBarriers(Recorder::CommandClass::StagingIn); + constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT}; + if (!Recorder::ElideCovered(Recorder::CommandClass::StagingIn, leading, covered)) { + RecordMemoryBarrier(context, commands, leading.srcStage, leading.dstStage, leading.srcAccess, leading.dstAccess); + Recorder::CountBarriers(Recorder::CommandClass::StagingIn); + } bool stagedAny = false; std::uint64_t copiedBytes = 0; for (auto* region : copies) { @@ -3496,11 +3500,15 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { const auto to = std::min(end, region.end); if (from >= to) continue; if (!recording) { - commands = recorder.Commands(); + VkAccessFlags covered = 0; + commands = recorder.Commands(&covered); timing = recorder.BeginGpuTiming(StagingCopyBackKey); // The shader's stores into the buffer (whichever stage made them) precede the copy. - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - Recorder::CountBarriers(Recorder::CommandClass::StagingOut); + constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT}; + if (!Recorder::ElideCovered(Recorder::CommandClass::StagingOut, leading, covered)) { + RecordMemoryBarrier(context, commands, leading.srcStage, leading.dstStage, leading.srcAccess, leading.dstAccess); + Recorder::CountBarriers(Recorder::CommandClass::StagingOut); + } recording = true; } // Exactly the sub-range the shader may write, in place in the import: what the shader diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index fe032bcb8d97..5a50717c75bf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1826,6 +1826,13 @@ bool Recorder::MergeBarriers() { return merge; } +bool Recorder::ElideCovered(CommandClass which, const BarrierMask& wanted, VkAccessFlags covered) { + static const bool batch = std::getenv("APS5_NO_BARRIER_BATCH") == nullptr; + if (!batch || !MergeBarriers() || !OrderedByTrailing(covered, wanted)) return false; + CountMerged(which); + return true; +} + void Recorder::CountMerged(CommandClass which) { if (!DrawOrGpuProfiled()) return; classMerged[static_cast(which)].fetch_add(1, std::memory_order_relaxed); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 4a91d3e88c92..4e0fa2148c17 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -2789,7 +2789,9 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp toRead.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; toRead.newLayout = VK_IMAGE_LAYOUT_GENERAL; - context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &importReady, 0, nullptr, 1, &toRead); + const BarrierMask ready{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, importReady.srcAccessMask | toRead.srcAccessMask, importReady.dstAccessMask | toRead.dstAccessMask, toRead.oldLayout != toRead.newLayout, toRead.srcQueueFamilyIndex != toRead.dstQueueFamilyIndex}; + const bool ordered = recorder != nullptr && seeds.empty() && padding.copies.empty() && Recorder::ElideCovered(Recorder::CommandClass::StorageWriteBack, ready, covered); + if (!ordered) context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &importReady, 0, nullptr, 1, &toRead); for (std::size_t i = 0; i < windows.size(); ++i) { const auto& window = windows[i]; auto retile = window.window; diff --git a/core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp b/core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp new file mode 100644 index 000000000000..440bf1bde39f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp @@ -0,0 +1,113 @@ +#include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" +#include + +using namespace AgcDriver::Graphics; + +namespace { + +int failures = 0; + +void Expect(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "FAIL: %s\n", what); + ++failures; +} + +constexpr VkPipelineStageFlags All = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; +constexpr VkPipelineStageFlags Transfer = VK_PIPELINE_STAGE_TRANSFER_BIT; +constexpr VkPipelineStageFlags Compute = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; +constexpr VkPipelineStageFlags Host = VK_PIPELINE_STAGE_HOST_BIT; +constexpr VkAccessFlags SW = VK_ACCESS_SHADER_WRITE_BIT; +constexpr VkAccessFlags SR = VK_ACCESS_SHADER_READ_BIT; +constexpr VkAccessFlags TW = VK_ACCESS_TRANSFER_WRITE_BIT; +constexpr VkAccessFlags TR = VK_ACCESS_TRANSFER_READ_BIT; +constexpr VkAccessFlags HW = VK_ACCESS_HOST_WRITE_BIT; +constexpr VkAccessFlags HR = VK_ACCESS_HOST_READ_BIT; +constexpr VkAccessFlags CAW = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +constexpr VkAccessFlags DSW = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; +constexpr VkAccessFlags Full = SR | SW | TR | TW; + +const BarrierMask PassEnd{All, All, SW | CAW | DSW | TW, Full}; +const BarrierMask LabelLeading{All, Transfer, SW | TW | CAW, TW}; +const BarrierMask StagingInLeading{All | Host, Transfer, SW | TW | HW, TR}; +const BarrierMask StagingOutLeading{All, Transfer, SW, TR}; +const BarrierMask WriteBackReady{All | Host, Compute, SR | SW | TR | TW | HW | CAW, SR | SW}; + +void testCoversStages() { + Expect(Covers(PassEnd, LabelLeading), "a pass end covers the label run's leading barrier"); + Expect(!Covers(LabelLeading, PassEnd), "the leading barrier does not cover the pass end"); + Expect(Covers(PassEnd, PassEnd), "a barrier covers itself"); + Expect(Covers(BarrierMask{}, BarrierMask{}), "the empty barrier covers the empty one"); + Expect(Covers(PassEnd, StagingOutLeading), "ALL_COMMANDS holds a transfer destination and a shader write"); + Expect(!Covers(PassEnd, StagingInLeading), "ALL_COMMANDS does not hold the host stage or a host write"); + Expect(Covers(StagingInLeading, StagingOutLeading), "a wider source covers the narrower one"); + const BarrierMask computeOnly{Compute, Compute, SW, SR}; + Expect(!Covers(computeOnly, PassEnd), "a compute-only barrier does not cover ALL_COMMANDS"); + Expect(!Covers(computeOnly, StagingOutLeading), "a compute destination does not hold a transfer destination"); +} + +void testCoversAccess() { + const BarrierMask noColor{All, All, SW | TW, Full}; + Expect(!Covers(noColor, LabelLeading), "a source without the colour attachment write does not cover a barrier that asks for it"); + const BarrierMask readOnlyDestination{All, All, SW | CAW | DSW | TW, SR | SW}; + Expect(!Covers(readOnlyDestination, LabelLeading), "a destination without the transfer write does not cover a barrier that asks for it"); + const BarrierMask same{All, Transfer, SW | TW | CAW, TW}; + Expect(Covers(same, LabelLeading) && Covers(LabelLeading, same), "equal barriers cover each other"); +} + +void testCoversImage() { + BarrierMask transition = LabelLeading; + transition.changesLayout = true; + Expect(!Covers(PassEnd, transition), "a barrier that changes a layout is never covered by a memory barrier"); + BarrierMask transfer = LabelLeading; + transfer.transfersQueue = true; + Expect(!Covers(PassEnd, transfer), "a queue family ownership transfer is never covered"); + BarrierMask sameLayout = LabelLeading; + Expect(Covers(PassEnd, sameLayout), "an image barrier that keeps the layout and the queue is covered like a memory barrier"); +} + +void testOrderedByTrailing() { + constexpr VkAccessFlags dispatched = Full | HR; + constexpr VkAccessFlags filled = Full | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | HR; + Expect(OrderedByTrailing(dispatched, StagingInLeading), "a dispatch's trailing barrier orders the staging copy-in"); + Expect(OrderedByTrailing(dispatched, StagingOutLeading), "a dispatch's trailing barrier orders the staging copy-out"); + Expect(OrderedByTrailing(filled, WriteBackReady), "a fill's trailing barrier orders the write-back's import-ready barrier"); + Expect(OrderedByTrailing(Full, LabelLeading), "the pass end's destination accesses order the label run"); + Expect(!OrderedByTrailing(0, StagingInLeading), "an intervening command clears the covered accesses, so the barrier is kept"); + Expect(!OrderedByTrailing(0, StagingOutLeading) && !OrderedByTrailing(0, WriteBackReady), "no trailing barrier, no elision"); + Expect(!OrderedByTrailing(SR | SW, StagingInLeading), "a shader-only trailing barrier does not order a transfer read"); + Expect(!OrderedByTrailing(SR | SW | TW, StagingInLeading), "a trailing barrier without the transfer read is kept"); + constexpr VkAccessFlags stagedIn = SR | VK_ACCESS_UNIFORM_READ_BIT | SW; + Expect(!OrderedByTrailing(stagedIn, StagingInLeading), "the staging copy-in's own trailing barrier does not order the next copy-in"); + Expect(!OrderedByTrailing(Full, BarrierMask{All, Transfer, SW, TR | VK_ACCESS_INDIRECT_COMMAND_READ_BIT}), "a destination access the trailing barrier lacks is kept"); +} + +void testHostAndImage() { + BarrierMask toHost = StagingOutLeading; + toHost.dstStage |= Host; + toHost.dstAccess |= HR; + Expect(!OrderedByTrailing(Full | HR, toHost), "a barrier that makes writes visible to the host is kept"); + BarrierMask ready = WriteBackReady; + Expect(OrderedByTrailing(Full | HR, ready), "an image barrier that keeps its layout is elided like a memory barrier"); + ready.changesLayout = true; + Expect(!OrderedByTrailing(Full | HR, ready), "an image barrier that changes the layout is kept"); + ready.changesLayout = false; + ready.transfersQueue = true; + Expect(!OrderedByTrailing(Full | HR, ready), "a queue family transfer is kept"); +} + +} + +int main() { + testCoversStages(); + testCoversAccess(); + testCoversImage(); + testOrderedByTrailing(); + testHostAndImage(); + if (failures != 0) { + std::fprintf(stderr, "%d barrier batch checks failed\n", failures); + return 1; + } + std::printf("barrier batch tests passed\n"); + return 0; +} From 58bf9a028cb615637b848496775d21d66376dea3 Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Sun, 18 Oct 2026 10:50:38 +0200 Subject: [PATCH 046/176] perf(agc): skip the label run's leading barrier right after a render pass end (cherry picked from commit 0d704d32f6958af144b3363e2bf2cb262dbe53b2) --- .../Graphics/include/Recorder.hpp | 1 + .../libSceAgcDriver/Graphics/src/Recorder.cpp | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index d691f510e9fc..59e6de79bb31 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -463,6 +463,7 @@ class Recorder { // (or APS5_NO_BARRIER_ELISION=1) records every leading barrier, as before. static bool MergeBarriers(); static void CountMerged(CommandClass which); + static bool BarrierBatch(); static bool ElideCovered(CommandClass which, const BarrierMask& wanted, VkAccessFlags covered); // Hazard tracker, counting mode (APS5_BARRIER_VALIDATE=1): every command tells the tracker // what it reads and writes (guest ranges through host imports, images) with its stage before diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 5a50717c75bf..1f17b577d05e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -504,6 +504,8 @@ void ReleaseDeferredKeeps() { // Lock-free state of the active recorder's open batch, read by the queue workers between packets // and inside WAIT_REG_MEM polls (see the static readers in Recorder.hpp). Written under the mutex. constexpr std::int64_t NoPendingLabel = std::numeric_limits::min(); +constexpr BarrierMask PassEndBarrier{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT}; +constexpr BarrierMask LabelLeadingBarrier{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT}; std::atomic pendingLabelSince{NoPendingLabel}; std::atomic writeGeneration{0}; std::atomic publishGeneration{0}; @@ -1417,7 +1419,7 @@ void Recorder::endOpenRenderPass(bool barrier) { // The pass's attachment and shader writes are visible to everything recorded after it (the // host sees them at the batch's fence). if (barrier) { - recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + recordBarrier(open->commands, PassEndBarrier.srcStage, PassEndBarrier.dstStage, PassEndBarrier.srcAccess, PassEndBarrier.dstAccess); CountBarriers(CommandClass::Draw); } if (pass.afterPass) pass.afterPass(open->commands); @@ -1630,6 +1632,7 @@ bool Recorder::closeStoreRun(bool atSubmit) { // A label says the work before it is done: the queued key stores (results of that work) // land ahead of every store of the run, and the pass a draw left open ends. if (!open->keyStores.empty()) recordKeyStores(true); + const bool passEndLast = open->renderPass.open && !open->renderPass.afterPass; if (open->renderPass.open) endOpenRenderPass(); const auto commands = open->commands; run.timing = beginTiming(ClassKey(CommandClass::LabelRun)); @@ -1640,9 +1643,15 @@ bool Recorder::closeStoreRun(bool atSubmit) { } // At Submit every result of the batch reaches the host before the stores (the title's // pollers read results after the label); in place, later work sees the stores as well. - if (atSubmit) recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); - else recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); std::uint32_t barriers = 2; + if (atSubmit) { + recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); + } else if (passEndLast && BarrierBatch() && Covers(PassEndBarrier, LabelLeadingBarrier)) { + CountMerged(CommandClass::LabelRun); + barriers = 1; + } else { + recordBarrier(commands, LabelLeadingBarrier.srcStage, LabelLeadingBarrier.dstStage, LabelLeadingBarrier.srcAccess, LabelLeadingBarrier.dstAccess); + } std::uint64_t storedBytes = 0; const auto update = function(cmdUpdateBuffer, "vkCmdUpdateBuffer"); std::vector writes; @@ -1826,9 +1835,13 @@ bool Recorder::MergeBarriers() { return merge; } +bool Recorder::BarrierBatch() { + static const bool batch = MergeBarriers() && std::getenv("APS5_NO_BARRIER_BATCH") == nullptr; + return batch; +} + bool Recorder::ElideCovered(CommandClass which, const BarrierMask& wanted, VkAccessFlags covered) { - static const bool batch = std::getenv("APS5_NO_BARRIER_BATCH") == nullptr; - if (!batch || !MergeBarriers() || !OrderedByTrailing(covered, wanted)) return false; + if (!BarrierBatch() || !OrderedByTrailing(covered, wanted)) return false; CountMerged(which); return true; } From c3587b7a6e6cba987a41e37409ac946ab835d506 Mon Sep 17 00:00:00 2001 From: Kraken <10617703+UnhealthyKraken@users.noreply.github.com> Date: Thu, 15 Oct 2026 15:32:57 +0100 Subject: [PATCH 047/176] fix(shader): plan image tables through invariant descriptor phis Equivalent descriptor reloads in loops must retain runtime table planning. Reuse the existing invariant proof and cover accepted and varying descriptor cases. (cherry picked from commit d971ed011a5bb7ef812f5ba4903192ffd885657a) --- core/shader/recompiler/Optimization/src/ResourceTracker.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 74a307f3679a..0052ebd85cb3 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -511,7 +511,10 @@ class Tracker { IrValue* heapOffset = nullptr; std::uint32_t immediateOffset = 0; for (std::uint32_t dword = 0; dword < heapReads.size(); dword++) { - heapReads[dword] = handle.Argument(dword)->Resolve(); + heapReads[dword] = ResolveInvariantPhi(m_program.Resources(), handle.Argument(dword)); + if (heapReads[dword] == nullptr) { + return false; + } std::uint32_t memoryIndex = 0; const MemoryInfo* memory = ScalarReadMemory(*heapReads[dword], memoryIndex); if (memory == nullptr || !MemoryIndexBelongsTo(memoryIndex, *heapReads[dword])) { From 45c23dd585cc2e6e521cc8177cc08fee57132ef3 Mon Sep 17 00:00:00 2001 From: Josip Parlov Date: Sun, 18 Oct 2026 09:50:14 +0200 Subject: [PATCH 048/176] perf(agc): wait only for the key bytes' writers when reading DCC keys under a unit shadow readDccKeys publishes the shadowed units over the keys and then waited for every recorded batch. The publish notes its copies as pending writes, so waiting through the keys' newest writer is enough, and nothing needs a wait when neither the publish nor another recorded write covers them (a stale unit's slab is dropped, not copied). (cherry picked from commit a624d8d77492bc7c7f76fd3a6fbd01e0dd1d3f94) --- core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index 8d1ee9507dd4..745aa2e81b67 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -354,9 +354,9 @@ DccKeys readDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized return DccKeys::Mixed; } PublishShadow(metaAddress, count, PublishScope::Whole, PublishReason::Keys); - if (auto* recorder = Recorder::Active()) { + if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->PendingWriteOverlaps(metaAddress, count)) { Recorder::CountSync(2); - recorder->Sync(); + recorder->SyncThrough(metaAddress, count); } } const auto* keys = reinterpret_cast(metaAddress); From 8239463509d78c5aa9bc5bda8d59e1893fb90e47 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 15:47:02 +0000 Subject: [PATCH 049/176] fix(agc): overestimate single-sample draws with centroid inputs A draw with PA_SC_CONSERVATIVE_RASTERIZATION_CNTL 0x6001 and SPI_PS_INPUT_ENA.PERSP_CENTROID or LINEAR_CENTROID threw in DecodeState, whatever its rasterized sample count. With PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES 0 the single rasterized sample is the pixel centre, which is where the recompiler already puts the centroid barycentrics, so such a draw now decodes to overestimation. More rasterized samples, and a single-sample draw raised to a multisampled color target, still throw. Extends agc_driver_graphics and records the inference in the driver's technical debt. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01XJ3RTcWpqBBdVE8QRtKxJD (cherry picked from commit 76cc70c245b1de81fe8e7de0759bb1b8f0f5b61d) --- core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp | 2 +- core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 6 +++++- docs/dev/debt/libSceAgcDriver.md | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index c218281d799f..ed7497cd4d69 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -91,7 +91,7 @@ VkConservativeRasterizationModeEXT decodeConservativeRasterization(const QueueSt const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig); requireConservativeTriangles(primitive == 4u || primitive == 5u || primitive == 6u || primitive == 21u, primitive == 1u ? "points" : primitive == 2u || primitive == 3u ? "lines" : primitive == 7u || primitive == 17u ? "rectangles" : "other primitive types", "VGT_PRIMITIVE_TYPE", primitive); } - Require((read(cx, 0x1b3) & 0x44u) == 0, "conservative rasterization with centroid interpolation is unsupported"); + Require((read(cx, 0x1b3) & 0x44u) == 0 || (read(cx, 0x2f8) & 7u) == 0, "conservative rasterization with centroid interpolation of more than one rasterized sample is unsupported"); return VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 04634d6432b4..95422c659b44 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -5562,7 +5562,11 @@ void ConservativeRasterizationTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(tessellation); }, "conservative rasterization of lines is unsupported (VGT_GS_OUT_PRIM_TYPE=0x1)"); for (const auto inputs : {0x6u, 0x42u}) { queue.context[0x1b3] = inputs; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "conservative rasterization with centroid interpolation"); + Require(AgcDriver::Graphics::DecodeState(queue).conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT, "OVER_RAST_ENABLE rejected centroid inputs " + std::to_string(inputs) + " of a single rasterized sample"); + auto multisampled = queue; + multisampled.context[0x2f8] = 0x00200002; + multisampled.context[0x31d] = (1u << 12u) | (1u << 15u); + expectFailure([&] { AgcDriver::Graphics::DecodeState(multisampled); }, "conservative rasterization with centroid interpolation of more than one rasterized sample is unsupported"); } queue.context[0x1b3] = 2; for (const auto control : {0x6003u, 0x6020u, 0x6401u, 0xe06001u, 0x1u}) { diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index ab62e0cbe0ec..110440d9b077 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -115,7 +115,7 @@ - [DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `DepthLess`/`DepthGreater`. For a pixel shader that breaks the promise, the host driver decides whether its interpolated Z is depth-tested before shading; what the console does then was not measured. - [DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) becomes `FragStencilRefEXT` when available, taken from the second MRTZ channel of the 32_GR and 32_ABGR formats as Mesa's AMD drivers write it ([`emit_ps_mrtz_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L194)); the console was not measured. The host value is the test value of both faces and what `REPLACE_TEST` writes, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw, the UINT16_ABGR layout and `STENCIL_OP_VAL_EXPORT_ENABLE`. A device without the extension uses the bounded replay described above. On such a device a draw first drops the export when the reference cannot change its result (`StencilReferenceUnused`: no stencil test or attachment, or on both faces a compare that ignores it and no reachable `REPLACE` through a non-zero write mask), and its pixel shader is then compiled at its first draw, not when its ABI is resolved. A device with the extension keeps the export, so each such shader keeps one pixel variant. - [DB_SHADER_CONTROL.DEPTH_BEFORE_SHADER](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `EarlyFragmentTests`, and so does `Z_ORDER` `EARLY_Z_THEN_LATE_Z` or `EARLY_Z_THEN_RE_Z` without kill, Z export, mask export or stencil export; with alpha-to-coverage the tests stay late. The meaning of the bit and of `Z_ORDER` 3 follows Mesa (`src/amd/registers/gfx10.json`; RADV sets the bit for `early_fragment_tests` in `src/amd/vulkan/radv_shader.c`). What the console does with `DEPTH_BEFORE_SHADER` and a Z export, a mask export, a stencil export or alpha-to-coverage together was not measured. -- [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. +- [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. With `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` = 0 the `PERSP_CENTROID` and `LINEAR_CENTROID` inputs get the pixel-centre barycentrics, as without conservative rasterization ([shader debt](shader.md)): the one rasterized sample sits at the pixel centre, so every centroid the rasterizer could pick for an overestimated pixel is that point; that it does not move the centroid towards the primitive when the sample is uncovered is inferred, not measured. With more rasterized samples, and when a single-sample draw is raised to a multisampled color target, centroid inputs throw. - [Descriptor pool chain](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - each pool holds two storage buffer descriptors more than a set can take, as a workaround: RADV (Mesa 26.0 and later) lets a `FREE_DESCRIPTOR_SET` pool place a set up to 32 bytes past the pool's buffer ([`util_vma_heap_init`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_descriptor_pool.c) is given `bo_size + RADV_POOL_HEAP_OFFSET` as a size), and with a page-multiple pool size those bytes are an unmapped page - [CMASK fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as `CB_COLOR_CLEAR_WORD` into its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, a cleared tile of a DCC target and a fast clear of a 3D or array color target throw; base mip 0 of a 2D mipmapped color target is supported (offset follows AMD Addrlib `Gfx10Lib::HwlComputeCmaskInfo` reverse mip-chain ordering preceding base mip 0; mipmapped targets with 8 or 16 bpp throw until `dsMipmapHtileFix` mip-tail shrinkage is modelled). A tile with a code other than 0 and 0xF keeps its texels like an expanded tile: PPSA18391 (engine: Clickteam Fusion) fast clears a target whose CMASK it never initialized, so some of its nibbles hold leftover values such as 1 and 10; what the console does with such codes on a single-sample target is unverified on hardware. Single-sample views with `viewMip > 0` skip CMASK fast-clear decoding since CMASK metadata only covers base mip 0 on AMD hardware (Mesa `si_state.c` similarly clears `FAST_CLEAR` for level > 0). - [HTILE depth fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - HTILE is not modeled. Only RADV's `clear_htile_mask` compute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (`user[4]` and `user[5]` low nibbles both 0); the dispatch still runs, and the host depth image is cleared to `DB_DEPTH_CLEAR` the next time the surface with that `DB_HTILE_DATA_BASE` address is bound. The address match is exact, so a clear of a different HTILE range does not hit. HTILE initialization, the stencil-only update, and fast clears made by the SDK's own paths, CP DMA fills or other kernels are not covered and leave the depth image as it was. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, one `buffer_store_format_x` per thread through the stride-4 V# in `user[0..3]`, the value read by `s_buffer_load_dword` through the V# in `user[4..7]`), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) ([`MatchMemoryValueFill`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp), then the whole-HTILE and ZMask rules of `NoteDepthMetadataFill`). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. When the [fill HLE](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp) takes the dispatch instead (an output descriptor of one 32-bit value per element, a value descriptor that covers the loaded dword, a 16-byte aligned range), the value is read again when the fill is recorded, after the labels queued before it, and the HTILE fill is noted with that value. From ed072f7a2b59a10a2635180c9e711f6e5409c331 Mon Sep 17 00:00:00 2001 From: thuanquang Date: Sun, 18 Oct 2026 19:17:42 +0700 Subject: [PATCH 050/176] fix(shader): write a mesh shader's layer through a per-primitive block Co-Authored-By: Claude Opus 5.5 (cherry picked from commit a9cd39045bc4b648ecc34c42628ce4491f2f9c1e) --- .../Graphics/src/ShaderValidation.cpp | 4 +- .../tests/execution/MeshProvokingLayer.cpp | 259 ++++++++++++++++++ .../SpirvBackend/src/SpirvModuleEmitter.cpp | 13 +- docs/dev/debt/shader.md | 1 + 4 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 1d02d8aa1e86..0bd36dd79869 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -36,6 +36,7 @@ struct Module { std::map decorations; std::map, std::uint32_t> builtins; std::map, std::uint32_t> offsets; + std::set> perPrimitiveMembers; std::map variables; std::map constants; std::set interface; @@ -357,6 +358,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi auto& fields = instruction[3] == spv::DecorationBuiltIn ? module.builtins : module.offsets; Require(fields.emplace(std::make_pair(instruction[1], instruction[2]), instruction[4]).second, "duplicate SPIR-V member decoration"); } + if (instruction[3] == spv::DecorationPerPrimitiveEXT) module.perPrimitiveMembers.emplace(instruction[1], instruction[2]); break; case spv::OpTypeBool: case spv::OpTypeInt: @@ -448,7 +450,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi const auto length = module.constants.find(outer[3]); Require(length != module.constants.end() && length->second != 0, "invalid per-vertex interface array length"); if (mesh) { - const bool primitive = decoration.perPrimitive || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT)); + const bool primitive = decoration.perPrimitive || module.perPrimitiveMembers.contains({outer[2], 0u}) || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveLineIndicesEXT || *decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT)); Require(length->second == (primitive ? state.stages.mesh->maxPrimitives : state.stages.mesh->maxVertices), "mesh interface array disagrees with output limits"); } else if (geometry) { Require(length->second == 3u, "geometry interface array must hold the three vertices of a triangle"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp new file mode 100644 index 000000000000..0431a9279e29 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp @@ -0,0 +1,259 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::ColorTileMode; +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 32; +constexpr std::uint32_t Height = 16; +constexpr std::uint32_t Layers = 2; +constexpr std::uint8_t Kept = 0x40; +constexpr std::uint32_t LayerExport = (1u << 18u) | (1u << 21u); +constexpr std::uint32_t ShaderViewportIndexLayerCapability = 5254; + +alignas(256) constexpr std::array GeometryCode{ + 0x8f6a9003, 0x94fe6ac1, 0xbf88000b, 0xd7650006, 0x000100c1, 0xd7660006, 0x00020cc1, 0x93ebff03, + 0x00040018, 0xd7460006, 0x04190c6b, 0x340c0c82, 0xd8340000, 0x00000506, 0xbf8cc07f, 0xbefe04c1, + 0xbf8a0000, 0x938dff02, 0x00090016, 0x9382ff03, 0x00040018, 0x938cff03, 0x00080008, 0xd7650009, + 0x000100c1, 0xd7660009, 0x000212c1, 0xd746000a, 0x04250c02, 0x7da8120c, 0xbf88002e, 0x361600ff, + 0x0000ffff, 0x2c180090, 0x361a02ff, 0x0000ffff, 0xd8d80000, 0x0b00000b, 0xd8d80000, 0x0c00000c, + 0xd8d80000, 0x0d00000d, 0xbf8cc07f, 0xe0382000, 0x8002100b, 0xe0382010, 0x8002140b, 0xe0382000, + 0x8002180c, 0xe0382010, 0x80021c0c, 0xe0382000, 0x8002200d, 0xe0382010, 0x8002240d, 0x161c1483, + 0x161e14ff, 0x00000060, 0xbf8c3f70, 0xdb7c0400, 0x0000100f, 0xdb7c0410, 0x0000140f, 0xdb7c0420, + 0x0000180f, 0xdb7c0430, 0x00001c0f, 0xdb7c0440, 0x0000200f, 0xdb7c0450, 0x0000240f, 0x4a501c81, + 0x4a521c82, 0x3450508a, 0x34525294, 0xd772002a, 0x04a6510e, 0xbf8cc07f, 0xbefe04c1, 0xbf8a0000, + 0x930e830d, 0xbf078002, 0xbf850003, 0x8f0f8c0d, 0x887c0f0e, 0xbf900009, 0x7da8140d, 0xbf880002, + 0xf8000941, 0x0000002a, 0xbefe04c1, 0x7da8140e, 0xbf88000c, 0x34561485, 0xdbfc0400, 0x2c00002b, + 0xdbfc0410, 0x3000002b, 0xbf8cc07f, 0xf80000cf, 0x2f2e2d2c, 0xf80008d4, 0x00330000, 0xf800020f, + 0x33323130, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelCode{ + 0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000, +}; + +alignas(256) constexpr std::array LayerFieldPixelCode{ + 0x2c020090, 0x7e020d01, 0x100202f0, 0xf800180f, 0x01010101, 0xbf810000, +}; + +struct Vertex { + std::array position; + std::array color; + std::uint32_t layer; +}; + +alignas(256) std::array Vertices{}; + +void BuildQuad() { + const std::array, 6> corners{{{-1.0f, -1.0f}, {1.0f, -1.0f}, {-1.0f, 1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f}}}; + for (std::uint32_t k = 0; k < corners.size(); ++k) Vertices[k] = {{corners[k][0], corners[k][1], 0.5f, 1.0f}, {1.0f, 1.0f, 1.0f}, k % 3u == 2u ? 1u : 0u}; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +AgcDriver::Graphics::ColorTarget LayeredTarget(std::byte* memory) { + AgcDriver::Graphics::ColorTarget color{reinterpret_cast(memory), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, 0, 0xe4u}; + color.tileMode = ColorTileMode::RenderTarget; + color.depth = Layers; + color.depthSlice = 0; + color.layers = Layers; + color.bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::SurfaceForTarget(color)).guestBytes; + return color; +} + +class GuestVolume { +public: + explicit GuestVolume(std::size_t bytes) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(65536, bytes)); +#endif + Require(block != nullptr, "cannot allocate the layered target"); + GuestAllocations::Mutation().Add(block, bytes, true, true, true); + std::memset(block, Kept, bytes); + } + + ~GuestVolume() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestVolume(const GuestVolume&) = delete; + GuestVolume& operator=(const GuestVolume&) = delete; + std::byte* Data() const { return block; } + +private: + std::byte* block = nullptr; +}; + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, bool last, std::span pixelCode, bool layerField) { + const auto target = device.Target(); + const auto vertexBuffer = BufferDescriptor(Vertices.data(), sizeof(Vertex), static_cast(Vertices.size())); + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Vertices.size()), 0, 1, 0, false}; + ShaderRecompiler::ShaderVertexStageInfo vertexInfo{}; + vertexInfo.paClVsOutCntl = LayerExport; + std::vector userData(12u, 0u); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), userData.begin() + 8); + const ShaderRecompiler::MeshConfiguration mesh{4u, 4u, 12u, 12u, 4u, 64u, 1024u, last ? 2u : 0u, 4u}; + const std::array geometryMemory{{{reinterpret_cast(GeometryCode.data()), std::as_bytes(std::span(GeometryCode))}}}; + ShaderRecompiler::RecompileRequest geometry{ + {ShaderStage::Mesh, reinterpret_cast(GeometryCode.data()), GeometryCode, 0, {}}, + {64, 0, userData, std::nullopt, std::nullopt, vertexInfo, geometryMemory}, + target, + {0, 0, 0, ShaderRecompiler::MeshDrawPushOffsetBytes} + }; + geometry.graphics = ShaderRecompiler::GraphicsCompileContext{0, {}, mesh, std::nullopt, {draw.indexAddress, draw.indexCount, draw.indexSize, draw.instanceCount}}; + geometry.useCache = false; + const auto geometryResult = ShaderRecompiler::Recompile(geometry); + const auto geometryPush = static_cast(geometryResult.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + if (layerField) { + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); + pixel.ancillary = true; + pixel.layerExported = true; + } + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(geometryPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Mesh, &geometryResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(geometryPush, target)} + }}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, 0x20u, 64, 64, mesh, std::nullopt}; + state.provokingVertexMode = last ? VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT : VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT; + state.color = color; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.renderLayers = Layers; + state.layerExports = LayerExport; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + device.Draw(state, draw, shaders); + device.WaitIdle(); + AgcDriver::Graphics::StorageTexture::FlushAllPending("mesh provoking layer test"); + device.WaitIdle(); +} + +std::uint64_t BlockOffset(const AgcDriver::Graphics::TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t z) { + std::uint64_t offset = 0; + for (std::uint32_t bit = 0; bit < equation.bits.size(); ++bit) { + const auto mask = equation.bits[bit]; + const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12u) & (mask & 0xfff000u)) ^ ((z << 24u) & (mask & 0xff000000u)); + offset |= static_cast(std::popcount(selected) & 1u) << bit; + } + return offset; +} + +void Check(const std::byte* memory, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t drawnLayer, std::uint8_t drawn, const std::string& what) { + using namespace AgcDriver::Graphics; + const auto geometry = DescribeSurface(SurfaceForTarget(color)); + const auto tileMode = ColorTextureTileMode(color.tileMode); + const auto thin = ThinBlockLayout(tileMode, 4); + const auto& mip = geometry.mips.at(0); + const auto* equation = FindTextureSwizzleEquation(static_cast(color.tileMode), 4); + Require(equation != nullptr, "missing the target's swizzle equation"); + for (std::uint32_t layer = 0; layer < Layers; ++layer) { + const auto* base = memory + geometry.GuestLayerOffset(layer); + const auto expected = layer == drawnLayer ? drawn : Kept; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto* texel = base + (static_cast(y / thin[2]) * mip.blocksPerRow + x / thin[1]) * thin[0] + BlockOffset(*equation, x, y, layer); + for (std::uint32_t channel = 0; channel < 4; ++channel) { + const auto actual = std::to_integer(texel[channel]); + Require(actual == expected || (layer == drawnLayer && expected == 128 && actual == 127), what + ": layer " + std::to_string(layer) + " texel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected) + (layer == drawnLayer ? " (drawn)" : " (untouched)")); + } + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto target = device->Target(); + if (!target.mesh.has_value() || std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), ShaderViewportIndexLayerCapability) == target.supportedCapabilities.end()) { + std::puts("skipped, the device has no VK_EXT_mesh_shader or VK_EXT_shader_viewport_index_layer"); + return VulkanTestSkipped; + } + if (device->DeviceName().starts_with("llvmpipe")) { + std::puts("skipped, the device has no mesh shader compiler that handles these programs (llvmpipe)"); + return VulkanTestSkipped; + } + if (!device->ProvokingVertexLast()) { + std::puts("skipped, the device has no last provoking vertex mode"); + return VulkanTestSkipped; + } + BuildQuad(); + const auto bytes = (LayeredTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}; + for (const bool layerField : {false, true}) { + for (const bool last : {false, true}) { + const GuestVolume memory(bytes); + const auto color = LayeredTarget(memory.Data()); + Draw(*device, color, last, layerField ? std::span(LayerFieldPixelCode) : std::span(PixelCode), layerField); + const auto drawn = static_cast(layerField ? (last ? 128u : 0u) : 255u); + Check(memory.Data(), color, last ? 1u : 0u, drawn, std::string(last ? "last provoking vertex: the layer of each triangle's third vertex" : "first provoking vertex: the layer of each triangle's first vertex") + (layerField ? ", read by the pixel shader" : "")); + } + } + std::puts("mesh provoking layer tests passed: first mode draws each triangle into its first vertex's layer and last mode into its third vertex's"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index b91213dfe930..7d33733e2aad 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -666,18 +666,16 @@ void DefineMeshOutputs(SpirvEmitterState& state) { throw std::runtime_error("unsupported mesh output kind"); } const auto type = MeshOutputType(state, output.kind); - output.variableId = MeshArray(state, spv::StorageClassOutput, type, output.kind == StageOutputKind::Layer ? mesh.maxPrimitives : mesh.maxVertices); + const auto layerBlock = output.kind == StageOutputKind::Layer ? state.module.DecoratedType(spv::OpTypeStruct, {{spv::OpMemberDecorate, {0u, spv::DecorationBuiltIn, spv::BuiltInLayer}}, {spv::OpMemberDecorate, {0u, spv::DecorationPerPrimitiveEXT}}, {spv::OpDecorate, {spv::DecorationBlock}}}, type) : 0u; + output.variableId = MeshArray(state, spv::StorageClassOutput, output.kind == StageOutputKind::Layer ? layerBlock : type, output.kind == StageOutputKind::Layer ? mesh.maxPrimitives : mesh.maxVertices); const bool shared = output.kind == StageOutputKind::Layer; output.meshDataVariable = MeshArray(state, shared ? spv::StorageClassWorkgroup : spv::StorageClassPrivate, type, shared ? mesh.maxVertices : state.laneCount); state.interfaceVariables.push_back(output.variableId); state.module.AddName(output.variableId, output.debugName); if (output.kind == StageOutputKind::Parameter) { state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationLocation, output.location); - } else { - state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationBuiltIn, output.kind == StageOutputKind::Layer ? spv::BuiltInLayer : spv::BuiltInPosition); - } - if (output.kind == StageOutputKind::Layer) { - state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationPerPrimitiveEXT); + } else if (output.kind != StageOutputKind::Layer) { + state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationBuiltIn, spv::BuiltInPosition); } if (output.kind == StageOutputKind::Position) { state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationInvariant); @@ -799,7 +797,8 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) { continue; } const auto layer = MeshLoad(state, output.meshDataVariable, spv::StorageClassWorkgroup, TypeU32(state), vertex.at(state.inputInfo.vertex->mesh.provokingVertex)); - const auto pointer = MeshElement(state, output.variableId, spv::StorageClassOutput, TypeU32(state), index); + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeU32(state)), pointer, output.variableId, index, ConstantU32(state, 0u)); state.module.AddFunction(spv::OpStore, pointer, layer); } }); diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index b2917b84776c..b74e5af6b1e9 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -95,6 +95,7 @@ - On a host subgroup narrower than the wave, [`v_readlane_b32`](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) of a constant lane outside it builds only as lane 63 of a half-wave scan (DPP row_shr 1/2/4/8, `v_permlanex16_b32`, EXEC -1) or, in a program whose entry EXEC is every lane (vertex, local and tessellation stages), as 0 when the value is 0 in lanes 32-63; other such reads throw. A value is 0 there when it is built from 0 through selects under a lane index (lane id or `v_mbcnt` of -1) below at most 32, `v_cmp_ne_u32` against 0, OR, DPP and permlanes; the counts in `s3` give such a bound on a 32-lane host. On a 32-lane host a half-wave scan may also run under `s_mov_b64 exec, -1`, use bound_ctrl:1 steps for an OR, add or unsigned max, and take keys that are 0 in lanes 32-63; its lane 31 is then the subgroup reduction of the host's invocations (a shuffle of the scan on a device without subgroup arithmetic): idle invocations add the identity, where the console would add the keys of the lanes `s3` claims. - [NGG mesh entry](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) uses the guest wave size (32 or 64) for wave indices, per-wave vertex and primitive counts, and the workgroup wave count in `s3`. The four-bit wave fields follow Mesa's [get_wave_id_in_tg, get_tgsize and get_thread_id_in_tg](https://chromium.googlesource.com/chromiumos/third_party/mesa/+/refs/heads/stabilize-13982.70.B-chromeos-amd/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c); partial waves and groups exceeding 15 waves throw. Indexed and non-indexed wave32 mesh rendering with 32, 64 and 128 threads was checked by pixel readback on Windows with an NVIDIA RTX 4070 Laptop GPU. The guest register values have not been compared with console hardware. - [Mesh shader grid size](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - every mesh shader loads `NumWorkgroups.x` and declares no vertices when it is 0, which no dispatch produces. The read is a workaround for RADV (Mesa 26.2.4, seen on an RX 9070 XT, GFX12): binding a mesh pipeline whose shader reads neither `NumWorkgroups` nor `DrawIndex` keeps the user SGPR count and draw-id flag of the vertex pipeline bound before it in the same command buffer (`radv_bind_pre_rast_shader` updates them only when the shader has the `AC_UD_VS_BASE_VERTEX_START_INSTANCE` slot), so `radv_emit_userdata_mesh` opens a 3-register `SET_SH_REG` and writes one value, the CP takes the following `DISPATCH_MESH_DIRECT` as register data and the GPU hangs (gfx ring timeout, MODE1 reset). Kena: Bridge of Spirits (PPSA01802) hits it on its layered volume draws. Reading `NumWorkgroups` gives every mesh shader the grid-size slot, so the count and the values match; drop the read once a fixed Mesa is the minimum +- [Mesh layer output](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - a mesh shader writes each primitive's layer through a one-member `Block` struct array whose member is decorated `BuiltIn Layer` and `PerPrimitiveEXT`, as glslang declares `gl_Layer` inside `gl_MeshPerPrimitiveEXT`, not through a standalone `PerPrimitiveEXT` `Layer` array. Both forms pass `spirv-val`, but AMD's Windows driver (32.0.21045.5002, RX 6700 XT) crashes in `amdvlk64.dll` creating a pipeline whose mesh shader writes the standalone form, whether the layer is signed or unsigned and whatever the target, while the block form draws, also with a pixel shader that reads the layer. - [Shader recompilation](../../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../../core/shader/recompiler) was written completely independently from [libs/prx](../../../core/libs/prx). - [v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16](../../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp) read the 16-bit halves of each vertex's parameter through `VK_KHR_fragment_shader_barycentric`, as fixed-function interpolation cannot split a 32-bit attribute. Without the extension the pixel shader takes the [barycentric emulation](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), whose generated geometry stage passes each vertex's parameter to it as a flat input, so the halves arrive bit for bit; a device without geometry shaders throws when that stage is built. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact `P10 * I + P0` once to f32 and v_interp_p2_f16 rounds the exact `P20 * J + S2` once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64, so these instructions need the Float64 capability. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of `IEEE_MODE` and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. Lavapipe has no barycentrics, so the draw test is skipped in CI; `agc_driver_interpolation_modes` checks the arithmetic in every mode against the hardware rows on any device with Float64. - Without `VK_KHR_fragment_shader_barycentric`, a pixel shader whose I/J VGPRs [only feed `v_interp_p1_f32`/`v_interp_p2_f32` of their own pair](../../../core/shader/recompiler/Optimization/src/HostInterpolationChecker.cpp) reads the attribute Vulkan interpolated. Any other read of them or of a raw vertex parameter takes the weights and the three vertex values from a [generated geometry stage](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), which needs `geometryShader` and a vertex shader before it: tessellation, mesh and rect-list pipelines, primitives other than triangles, more than 32 fragment input locations and interfaces beyond the device's geometry limits throw. From d1827c1b5c2940db4b96d8b8cb4016669ea94e19 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Mon, 19 Oct 2026 03:47:33 +0200 Subject: [PATCH 051/176] fix(libSceAgcDriver): zero the overhanging record of a vertex fetch Co-Authored-By: Claude Sonnet 5.5 (cherry picked from commit d01548e81b003aa39417b24f7186ae5d212bf191) --- .../libSceAgcDriver/Graphics/include/Draw.hpp | 2 +- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 14 +++++-- .../prx/libSceAgcDriver/tests/Graphics.cpp | 39 +++++++++++++++++++ docs/dev/debt/libSceAgcDriver.md | 1 + 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 6c4332713100..c67d8f8d9afd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -31,7 +31,7 @@ struct DrawInputCopy { }; DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uint64_t address, std::size_t bytes, std::size_t alignment, Recorder::SnapshotUse use); DrawInputCopy CopyBoundedIndexInput(const Context& context, const Pm4::DrawParameters& draw); -DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes); +DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes, std::size_t stride = 0); void KeepDrawInput(Recorder* recorder, std::uint64_t address, const DrawInputCopy& copy, Recorder::SnapshotUse use, std::uint32_t derived); const HostImport* InPlaceDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t alignment); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 50e54699bab6..72668bb20839 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -967,14 +967,20 @@ DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uin return copy; } -DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes) { +DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes, std::size_t stride) { Require(validBytes <= bytes, "the valid bytes of a vertex fetch exceed the fetch"); GuestMemory::FlushGpuWrites(address, bytes); DrawInputCopy copy; copy.buffer = std::make_shared(context, bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); const auto span = copy.buffer->Bytes(); - GuestMemory::Read(address, span.subspan(0, validBytes), 1); - std::fill(span.begin() + static_cast(validBytes), span.end(), std::byte{0}); + auto readable = validBytes; + if (stride != 0 && !GuestMemory::Accessible(reinterpret_cast(address), validBytes)) { + const auto ranges = GuestMemory::CommittedRanges(address, validBytes); + const std::size_t prefix = !ranges.empty() && ranges.front().first == address ? static_cast(ranges.front().second - address) : 0; + if (validBytes - prefix < stride) readable = prefix; + } + GuestMemory::Read(address, span.subspan(0, readable), 1); + std::fill(span.begin() + static_cast(readable), span.end(), std::byte{0}); return copy; } @@ -1251,7 +1257,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P for (const auto f : soloFetches) { const auto begin = fetches[f].begin; const auto bytes = static_cast(fetches[f].end - begin); - auto copy = CopyZeroPaddedDrawInput(context, begin, bytes, fetchValid[f]); + auto copy = CopyZeroPaddedDrawInput(context, begin, bytes, fetchValid[f], fetches[f].stride); soloHandles[f] = copy.buffer->Handle(); inputs.vertexBuffers.push_back(std::move(copy.buffer)); } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 04634d6432b4..d1b565c88f38 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -23,6 +23,11 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestHeap.hpp" #include +#ifdef _WIN32 +#include +#else +#include +#endif #include #include #include @@ -6471,6 +6476,39 @@ void vertexZeroFillTests() { Require(std::all_of(bytes.begin() + static_cast(valid), bytes.end(), [](std::byte value) { return value == std::byte{0}; }), "a vertex fetch past the record count did not read as zeros"); } +void vertexOverhangTests() { + using AgcDriver::Graphics::CopyZeroPaddedDrawInput; + constexpr std::size_t page = 65536; + constexpr std::size_t stride = 44; + constexpr std::size_t overhang = 8; +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE, PAGE_NOACCESS)); + Require(block != nullptr && VirtualAlloc(block, page, MEM_COMMIT, PAGE_READWRITE) != nullptr, "cannot reserve the guest pages of the overhang test"); +#else + void* mapped = mmap(nullptr, page * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapped != MAP_FAILED && mprotect(mapped, page, PROT_READ | PROT_WRITE) == 0, "cannot reserve the guest pages of the overhang test"); + auto* block = static_cast(mapped); +#endif + constexpr std::size_t offset = 0x650; + const auto readable = page - offset; + std::fill(block + offset, block + page, std::byte{0x7a}); + const auto address = reinterpret_cast(block) + offset; + { + const auto copy = CopyZeroPaddedDrawInput(mockContext(), address, readable + overhang, readable + overhang, stride); + const auto bytes = copy.buffer->Bytes(); + Require(bytes.size() == readable + overhang, "an overhanging vertex fetch changed its size"); + Require(std::all_of(bytes.begin(), bytes.begin() + static_cast(readable), [](std::byte value) { return value == std::byte{0x7a}; }), "an overhanging vertex fetch did not copy the readable bytes"); + Require(std::all_of(bytes.begin() + static_cast(readable), bytes.end(), [](std::byte value) { return value == std::byte{0}; }), "an overhanging vertex fetch did not zero its last record"); + } + expectFailure([&] { CopyZeroPaddedDrawInput(mockContext(), address, readable + stride, readable + stride, stride); }, "is not readable"); + expectFailure([&] { CopyZeroPaddedDrawInput(mockContext(), address, readable + overhang, readable + overhang, 0); }, "is not readable"); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + munmap(block, page * 2); +#endif +} + void vertexZeroFillMergeTests() { using AgcDriver::Graphics::PlanVertexCopies; using AgcDriver::Graphics::SoloZeroPaddedFetchIndices; @@ -6750,6 +6788,7 @@ int main(int argc, char** argv) { storeAtLabelTests(); nullVertexDescriptorTests(); vertexZeroFillTests(); + vertexOverhangTests(); vertexZeroFillMergeTests(); parallelCompareTests(); pixelParameterSlotTests(); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index ab62e0cbe0ec..121c3a948d6d 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -31,6 +31,7 @@ - [Strided buffer overhang](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a strided buffer descriptor that ends less than one stride past the end of the readable registered allocation holding its base is bound only up to that allocation's end, even when another readable allocation starts right there. This assumes the bytes past the end are the rounded-up `num_records` of the last record (RADV rounds it up), which no fetch inside the buffer reads; a guest that really keeps a vertex split across two adjacent allocations would read zeros for the cut tail. Taken from the RetroArch Dolphin core case in #2912, not measured on hardware. - [Buffer descriptor CACHE_SWIZZLE hint](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - bit 30 of word 1 in a buffer resource descriptor (`words[1] & 0x40000000u`) is normalized to zero when binding guest buffers in `ShaderResources::addGuestBuffer`. On GFX10 (gfx1013), bit 30 of V# word 1 is `CACHE_SWIZZLE` (Mesa's `gfx10-rsrc.json`: STRIDE [29:16], CACHE_SWIZZLE [30], SWIZZLE_ENABLE [31]), a cache-bank hint with no effect on the address or data, which is dropped here. - [Buffer descriptors past the range limit](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a buffer V# whose size is over `maxStorageBufferRange` is bound only up to the end of the readable registered allocation holding its base when that allocation ends before the limit, and as the empty placeholder buffer, like a null V#, when no registered allocation holds its base; a V# over an allocation that reaches the limit still throws. Titles leave such words in descriptor slots their shaders do not reach on the path taken (PPSA10528 binds `c8010002 2c6bec20 c0053c00 04000013`, about 33 TB from an address above the user address space). What the console returns for an access past the bound range is not known; here the device's robust buffer access decides. Follows the review of #3202, not measured on hardware. +- [Vertex fetch overhang](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a zero-padded vertex fetch whose valid range (`num_records * stride`) ends less than one stride past the end of the readable memory at its base copies only the readable bytes and reads the rest as zeros, instead of failing the draw; a longer overhang still fails. This extends the strided buffer overhang rule above (the last record is the rounded-up `num_records`) to the staging copy and is not measured on hardware. PPSA26223 draws a fetch of 14858 records of 44 bytes that ends 8 bytes past the end of the 0xa0000-byte mapping holding it - [Zero-stride vertex fetch range](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - for a stride-0 V# with OOB_SELECT 2 the whole fetch reads memory unless NUM_RECORDS is 0; with OOB_SELECT 3 it reads zero when NUM_RECORDS is 0 and throws when the fetch only partly fits. Rules taken from the GFX10 OOB_SELECT table in Mesa [ac_descriptors.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/ac_descriptors.c), not measured on hardware. Whether OOB_SELECT 3 checks each dword of a multi-dword fetch separately is unknown. OOB_SELECT 0 and 1 keep the structured record checks. - [Resource registration](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - registration always fails with `RESOURCE_REGISTRATION_UNAVAILABLE`, so the owner and resource queries, `sceAgcDriverSetResourceUserData`, `sceAgcDriverFindResourcesPublic` and `sceAgcDriverUnregisterAllResourcesForOwner` return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. `sceAgcDriverGetResourceRegistrationMaxNameLength` writes 0xfc and returns 0, as [prosper](https://github.com/mattias800/prosper) does from a title's registration configuration; the `uint32_t*` parameter matches the SDK wrapper `sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*)`. sharpemu writes 256 and KytyPS5 returns `RESOURCE_REGISTRATION_UNAVAILABLE`, so the value is unverified; a null output throws - [sceAgcDriverRegisterMultipleResources](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified. From 1307d35eb9be69f084b4afdb3dc08e5645cefa40 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Thu, 15 Oct 2026 15:22:38 -0700 Subject: [PATCH 052/176] fix(agc): leave the stages of a pipeline ABI whose state does not decode to their first use A title's graphics pipeline ABI call decodes the stage routing and programs from the registered state and the call's own context and primitive registers. When that decode refused the state, the exception left the call and ended the title, although the draws that later use the stages decode their own state. The decode failure now defers every stage of the call the same way a stage whose preparation fails is deferred: each is prepared when a draw uses it. (cherry picked from commit 6bd8647c2799dc0d13dc384d2c553f22c4ad9e1c) --- .../Execution/src/Driver/Shaders/ShaderRegistry.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 38942993feed..cb7482cee7c7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1081,6 +1081,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std QueueState state{}; std::shared_ptr registry; std::shared_ptr owner; + std::vector> selectedStages; { std::lock_guard lock(mutex); registry = shaders; @@ -1091,6 +1092,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std const auto selected = RegisteredHeader(*registry, shader); require(selected != nullptr, "graphics ABI refers to a replaced shader header"); if (owner == nullptr) owner = selected; + selectedStages.push_back(selected); const auto& snapshot = *selected; require(snapshot.registeredState != nullptr, "registered shader state is missing"); const auto registered = DecodeRegisteredState(snapshot, {}); @@ -1123,8 +1125,14 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std return; } DrawDecode decoded{}; - decoded.state.stages = Graphics::DecodeShaderStages(state); - DecodeGraphicsPrograms(decoded, state, *registry, true, false); + try { + decoded.state.stages = Graphics::DecodeShaderStages(state); + DecodeGraphicsPrograms(decoded, state, *registry, true, false); + } catch (const std::exception& error) { + for (const auto& selected : selectedStages) DeferPipelineStage(transaction.Edit(*selected), *selected, error.what()); + transaction.Commit(); + return; + } auto prepared = PrepareGraphicsStages(decoded, localDevice->Target(), true); for (auto& stage : prepared) { if (stage.entry.handle == nullptr) continue; From 102773800f54983faf07861545e71619012d0031 Mon Sep 17 00:00:00 2001 From: fogydes Date: Fri, 16 Oct 2026 12:00:37 +0530 Subject: [PATCH 053/176] feat(libSceAgc): implement CB memory semaphore and unversioned payload address (cherry picked from commit dd3798ddeb16c28dbac165ed654d8e68470c8f0b) --- core/libs/prx/libSceAgc/tests/Semaphore.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libSceAgc/tests/Semaphore.cpp b/core/libs/prx/libSceAgc/tests/Semaphore.cpp index 9f083d6093e9..6a568b9686a8 100644 --- a/core/libs/prx/libSceAgc/tests/Semaphore.cpp +++ b/core/libs/prx/libSceAgc/tests/Semaphore.cpp @@ -173,6 +173,7 @@ void testMemSemaphore() { expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 0, nullptr); }); auto* unalignedLabel = reinterpret_cast(guestAddress + 1); expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 0, unalignedLabel); }); + expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 4, label); }); } } From ca6676cc952663b8a0020a8224346f4c00f8f36f Mon Sep 17 00:00:00 2001 From: Kovalev Vladislav Date: Fri, 16 Oct 2026 04:08:59 +0600 Subject: [PATCH 054/176] fix(agc): resolve sparse indirect index ranges per record (cherry picked from commit 925f9a84b0b8b1bdfe47e037c2886ae3bfaf926a) --- .../Execution/src/Driver/Draw/IndirectDraw.cpp | 2 ++ core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp | 3 --- core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp | 2 +- core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp | 2 +- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 8 +++++++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp index e52db4829bc3..d3049765aa27 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include @@ -28,6 +29,7 @@ std::optional Driver::ClassifyIndirectDraw(const Sha using Path = Graphics::IndirectDrawPath; const auto support = localDevice->DrawIndirectSupport(); const auto fail = [&](Path reason) { if (!indirectCpu) indirectCpu = reason; }; + if (drawParameters.indexed && !GuestMemory::Accessible(reinterpret_cast(drawParameters.indexAddress), static_cast(drawParameters.indexCount) * drawParameters.indexSize)) fail(Path::IndexRange); constexpr std::uint32_t NoLocation = 0x280u; const bool meshPath = graphics.stages.mesh.has_value() && !graphics.stages.tessellation && !graphics.rectList; const bool meshGpuSide = meshPath && drawParameters.indexed && indirect.count == 1 && !indirect.countIndirect && indirect.baseVertexLocation == NoLocation && indirect.startInstanceLocation == NoLocation && (!indirect.drawIndexEnabled || indirect.drawIndexLocation == NoLocation); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 26c8dcc2cb80..61cb01ec34bb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -767,13 +767,10 @@ DrawParameters resolveIndirectDraw(std::span packet, const draw.firstVertex = offset->second; indirect.indxOffset = offset->second; } else { - // The CP clamps firstIndex + indexCount to INDEX_BUFFER_SIZE: the declared range is the - // hardware's view of the index buffer, so that is what gets bound. const auto indexSize = indexSizeOf(queue); require(queue.indexBufferSize != 0, "INDEX_BUFFER_SIZE has not been set"); const auto bytes = static_cast(queue.indexBufferSize) * indexSize; require(bytes <= std::numeric_limits::max() && bytes <= std::numeric_limits::max() - queue.indexBase, "index range size overflow"); - GuestMemory::CheckRange(reinterpret_cast(queue.indexBase), static_cast(bytes), indexSize); draw.indexAddress = queue.indexBase; draw.indexCount = queue.indexBufferSize; draw.indexSize = indexSize; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 6c4332713100..37ac6237516e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -91,7 +91,7 @@ bool DrawRecipes(); // draw index, the vertex ranges, the device features, APS5_NO_GPU_INDIRECT_DRAW) or under the device // lock in Draw (the memory state of the records). `readMs` is the CPU read's time (its sync); // `rewritten` counts a GPU-side draw whose records were copied and patched with a constant. -enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, Count }; +enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, IndexRange, Count }; void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false); std::array(IndirectDrawPath::Count)> IndirectDrawCounts(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 50e54699bab6..152f38270bdb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -382,7 +382,7 @@ constexpr std::array DrawPhaseNames{"validate", "vertex enum SyncReason : std::size_t { SyncNone, SyncNotResident, SyncNoRecorder, SyncDisabled, SyncCopiedWrites, SyncLease, SyncCount }; constexpr std::array SyncReasonNames{"none", "non-resident target", "no recorder", "disabled", "copied writes", "lease"}; constexpr std::size_t IndirectPathCount = static_cast(IndirectDrawPath::Count); -constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled"}; +constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled", "index range not fully readable"}; // What became of one draw, for the totals. struct DrawOutcome { diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 8af80ee42e78..a1936dda40fb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -498,7 +498,13 @@ void testIndexAttributesIndirect() { check(sameDraw(indirectFromRecord, AgcDriver::Pm4::ResolveDraw(indirectDraw, direct)) && indirectFromRecord.indexAddress == indexAddress && indirectFromRecord.indexCount == 12 && indirectFromRecord.indexSize == 4, "an indexed indirect draw after INDEX_ATTRIBUTES_INDIRECT differs from the same draw after the direct packets"); records[6] = 0x7fffffffu; execute(loaded, packet); - expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "guest"); + const auto largeIndirect = AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); + check(largeIndirect.indexAddress == indexAddress && largeIndirect.indexCount == records[6] && largeIndirect.indexSize == 4, "an indirect draw did not preserve its declared index limit for record resolution"); + loaded.indexBase = std::numeric_limits::max() - 3u; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "index range size overflow"); + loaded.indexBase = indexAddress + 1u; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "misaligned index base"); + loaded.indexBase = indexAddress; records[6] = 12; static std::size_t flushed = 0; From 2fcd27719aca5838345e6566e7d2d2fffc685069 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Thu, 15 Oct 2026 22:47:49 +0000 Subject: [PATCH 055/176] test(agc): defer the stage of a pipeline ABI whose geometry state does not decode A fifth ABI deferral mode, abi_deferred_undecodable_state. A vertex shader whose specials route it through geometry (VGT_SHADER_STAGES_EN 0x2030), which registration leaves alone, is resolved through ResolveGraphicsStagesAbi with the context and primitive registers that sceAgcCreatePrimState passes for it. DecodeShaderStages refuses its VGT_GS_OUT_PRIM_TYPE of 1. The call must not throw and must log the stage once with the decode's reason, and a second call must neither prepare nor log it again. A draw that routes the stage through the vertex path then prepares it at its first use without reporting it as missing a registration artifact. (cherry picked from commit 661d3cba8dc38b3b88e8e08295797d291c489c16) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 +- .../tests/execution/PreparedShaders.cpp | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index ea6d03c9e2a4..5bb47882ae49 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1528,7 +1528,7 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") if(preparedMode MATCHES "^abi_deferred_") string(REPLACE "_" "-" preparedArgument ${preparedMode}) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index c36b899cc8f5..51974e862378 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -852,18 +852,45 @@ void AbiDeferredRectangle() { AgcDriverShutdown_nid_postfix(); } +void AbiDeferredUndecodableState() { + static AbiHeader front; + DescribeFront(front); + front.context[0] = {0x2ce, 3}; + front.specials = {{0x25b, 0x8040}, {0x2d5, 0x2030}, 0x8000, {}, {}, {0x29b, 1}, {0x262, 0}}; + AgcDriverRegisterShader_nid_postfix(&front.shader); + const std::array stages{&front.shader}; + const std::array context{front.specials.vgt_shader_stages_en, front.specials.vgt_gs_out_prim_type}; + const std::array primitive{front.specials.ge_cntl, front.specials.ge_user_vgpr_en, ShaderRegister{0x242, 4}}; + const auto before = ShaderRecompiler::FrontEndBuilds(); + auto log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive); }); + Require(Deferrals(log) == 1 && log.find("VGT_SHADER_STAGES_EN=0x2030: unsupported geometry input or output assembly") != std::string::npos, "a graphics stage whose pipeline state does not decode was not deferred with the decode's reason"); + log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive); }); + Require(ShaderRecompiler::FrontEndBuilds() == before && Deferrals(log) == 0, "a graphics stage deferred for a pipeline state that does not decode was prepared or logged again"); + std::vector commands; + for (std::size_t index = 0; index < front.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, front.registers[index].offset, front.registers[index].value}); + commands.insert(commands.end(), {0xc0016900u, 0x2d5, 0x2000u, 0xc0017600u, 0x8, 0, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 4, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + log = Logged([&] { + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw of a graphics stage deferred at its pipeline ABI was not submitted"); + AgcDriverWaitIdle_nid_postfix(); + }); + Require(ShaderRecompiler::FrontEndBuilds() > before && log.find("has no artifact prepared at registration") == std::string::npos, "a graphics stage deferred at its pipeline ABI was not prepared at its first draw, or was reported there as missing a registration artifact"); + AgcDriverShutdown_nid_postfix(); +} + } int main(int argc, char** argv) { try { const std::string_view abiMode = argc == 2 ? argv[1] : ""; - const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle"; + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state"; Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); if (abiDeferred) { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; if (abiMode == "--abi-deferred-compute") AbiDeferredCompute(); else if (abiMode == "--abi-deferred-pixel") AbiDeferredPixel(); else if (abiMode == "--abi-deferred-stages") AbiDeferredStages(); + else if (abiMode == "--abi-deferred-undecodable-state") AbiDeferredUndecodableState(); else AbiDeferredRectangle(); std::cout << "shader ABI deferral tests passed: " << abiMode << '\n'; return 0; From 039d0d9758d5e7df030248f705d32cf4b1a66c9d Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 19 Oct 2026 13:46:46 +0530 Subject: [PATCH 056/176] fix(libSceAgc): align CB memory semaphore with console oracle and document debt (cherry picked from commit 093d6a3d4ec4a43c7a0b423be2e4ff2df7f37e8d) --- core/libs/prx/libSceAgc/Cb/include/Sync.hpp | 2 +- core/libs/prx/libSceAgc/Cb/src/Sync.cpp | 13 ++-- core/libs/prx/libSceAgc/tests/Semaphore.cpp | 70 ++++++++++++--------- docs/dev/debt/libSceAgc.md | 2 + 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/core/libs/prx/libSceAgc/Cb/include/Sync.hpp b/core/libs/prx/libSceAgc/Cb/include/Sync.hpp index e68396419633..a070c6a5fc8e 100644 --- a/core/libs/prx/libSceAgc/Cb/include/Sync.hpp +++ b/core/libs/prx/libSceAgc/Cb/include/Sync.hpp @@ -7,7 +7,7 @@ struct CommandBuffer; extern "C" { -std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address); +std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value); } #endif diff --git a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp index f738b2b56898..c8c3f80ef375 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp @@ -1,6 +1,5 @@ #include "prx/libSceAgc/Cb/include/Sync.hpp" -#include "prx/libSceAgc/Command/include/Control.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include #include @@ -59,11 +58,13 @@ std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t act return packet; } -std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address) { - const std::uint8_t operation = action != 0 ? 7u : 6u; - const std::uint8_t signalType = clientCode & 1u; - const std::uint8_t waitForMailbox = (clientCode >> 1u) & 1u; - return Agc::Command::WriteMemSemaphore(buf, address, waitForMailbox, signalType, operation, __func__); +std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value) { + (void)operation; + (void)modifier; + (void)value; + const auto guestAddress = static_cast(reinterpret_cast(address)); + const auto maskedAddress = guestAddress & ~0xfULL; + return Agc::Command::Emit(buf, 0x39u, {static_cast(maskedAddress), static_cast(maskedAddress >> 32u), 0x80100000u}, __func__); } } diff --git a/core/libs/prx/libSceAgc/tests/Semaphore.cpp b/core/libs/prx/libSceAgc/tests/Semaphore.cpp index 6a568b9686a8..021eb3e7c285 100644 --- a/core/libs/prx/libSceAgc/tests/Semaphore.cpp +++ b/core/libs/prx/libSceAgc/tests/Semaphore.cpp @@ -14,7 +14,7 @@ extern "C" int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes); extern "C" int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label); -extern "C" std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address); +extern "C" std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value); namespace { @@ -145,35 +145,49 @@ void testMemSemaphore() { check(sceAgcGetSemaphoreLabel(0, &label) == 0 && label != nullptr, "failed to get label for memory semaphore test"); const auto guestAddress = reinterpret_cast(label); - for (std::uint8_t action = 0; action <= 1; ++action) { - for (std::uint8_t clientCode = 0; clientCode <= 3; ++clientCode) { - std::array words{}; - words.fill(0xdeadbeefu); - CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; - - auto* packet = sceAgcCbMemSemaphore(&buffer, action, clientCode, label); - check(packet == words.data(), "packet address mismatch"); - check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); - check(packet[1] == static_cast(guestAddress), "MEM_SEMAPHORE address low mismatch"); - check(packet[2] == static_cast(guestAddress >> 32u), "MEM_SEMAPHORE address high mismatch"); - - const std::uint32_t expectedOp = action != 0 ? 7u : 6u; - const std::uint32_t expectedSignal = clientCode & 1u; - const std::uint32_t expectedMailbox = (clientCode >> 1u) & 1u; - const std::uint32_t expectedControl = (expectedOp << 29u) | (expectedSignal << 20u) | (expectedMailbox << 16u); - check(packet[3] == expectedControl, "MEM_SEMAPHORE control word mismatch"); - check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); - check(words[4] == 0xdeadbeefu, "words past packet modified"); - } + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = sceAgcCbMemSemaphore(&buffer, reinterpret_cast(0x1111111122222222ull), 0x33333333u, 0x44444444u, 0); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == 0x22222220u, "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == 0x11111111u, "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0x80100000u, "MEM_SEMAPHORE control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); } - std::array words{}; - CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; - expectFailure([&] { sceAgcCbMemSemaphore(nullptr, 0, 0, label); }); - expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 0, nullptr); }); - auto* unalignedLabel = reinterpret_cast(guestAddress + 1); - expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 0, unalignedLabel); }); - expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 4, label); }); + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = sceAgcCbMemSemaphore(&buffer, reinterpret_cast(1ull), 2, 3, 4); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == 0u, "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == 0u, "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0x80100000u, "MEM_SEMAPHORE control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); + } + + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = sceAgcCbMemSemaphore(&buffer, label, 0x12345678u, 0x9abcdef0u, 0x55aa55aau); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == static_cast(guestAddress & ~static_cast(0xfu)), "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == static_cast(guestAddress >> 32u), "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0x80100000u, "MEM_SEMAPHORE control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); + } + + expectFailure([&] { sceAgcCbMemSemaphore(nullptr, label, 0, 0, 0); }); } } diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index 985c36c8bb1e..75a88e3386b2 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -24,6 +24,8 @@ - [sceAgcWriteDataPatchSetDst, sceAgcWriteDataPatchSetCachePolicy and their Async forms](../../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - the signatures `(packet, uint8 value)` are inferred from the other patch functions, as no public declaration was found; the value is taken in the encoding of the `dst` and `cachePolicy` arguments of `sceAgcDcbWriteData` (graphics) and `sceAgcAcbWriteData` (Async, compute), and the patched packet is the one those calls would write. `sceAgcAsyncWriteDataPatchSetAddressOrOffset` is assumed to patch the address like `sceAgcWriteDataPatchSetAddressOrOffset`, as the compute WRITE_DATA packet keeps it in the same dwords - [sceAgcCreateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`dbOlWdppb4o`); same arguments as `sceAgcCreateInterpolantMapping_0100`; PPSA26344 passes three pointers at each of nine observed call sites (`0x9F914E`, `0x9F91E2`, `0x9F9216`, `0x1604BBD`, `0x16122D7`, `0x1618285`, `0x161838C`, `0x16184EE`, `0x1618607`), consistent with that argument assumption, but does not read the return value there, so its return type is not established. The `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware - [sceAgcGetDataPacketPayloadRange](../../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - signature, the `{base, size in bytes}` output and the type 0 range (header + 1, one dword longer than the type 1 payload) from KytyPS5 only, matching `sceAgcGetDataPacketPayloadAddress_0090`; prosper returns header + 2 for both types from that address function +- [sceAgcGetDataPacketPayloadAddress](../../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - forward to `sceAgcGetDataPacketPayloadAddress_0090` and the inferred (addr, cmd, type) parameters follow SharpProspero and decrypted firmware 3.20 NID mapping; not verified on console hardware +- [sceAgcCbMemSemaphore](../../../core/libs/prx/libSceAgc/Cb/src/Sync.cpp) (libSceAgc) - signature (cb, semaphoreAddr, operation, modifier, value) follows SharpProspero; writes 4-dword PM4 0x39 packet with control word 0x80100000 following console oracle measurements in prosper (cases agcbuild_CbMemSemaphore_a/b/c); operation, modifier and value are unused - [sceAgcAsyncCondExecPatchSetCommandAddress, sceAgcAsyncCondExecPatchSetEnd](../../../core/libs/prx/libSceAgc/Patch/src/CondExec.cpp) (libSceAgc) - no reference has a body for them; they are assumed to patch the COND_EXEC packet like `sceAgcCondExecPatchSetCommandAddress` and `sceAgcCondExecPatchSetEnd`, since `sceAgcAcbCondExec` writes the same packet as `sceAgcDcbCondExec`, as `sceAgcAsyncRewindPatchSetRewindState` does for REWIND - [sceAgcAcbAtomicGds_0900](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - name from its NID (`gQkqkLttcpw`), signature unknown - [sceAgcDcbPrimeUtcl2](../../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime From e0d5847999538a7b7fae6cc7e3140020509ff80a Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:23:30 +0000 Subject: [PATCH 057/176] fix(agc): leave a stage whose registration preparation failed to its first use A failure stored by a shader's background registration preparation was rethrown by every pipeline ABI call that touched the stage. The ShaderPreparationScope that sceAgcLinkShaders, sceAgcCreatePrimState and sceAgcCreateInterpolantMapping open settled the stages and threw first, and ResolveShaderAbi, ResolveGraphicsStagesAbi and ResolveGraphicsAbi threw it again from their reads of the stage. A title that creates its pipelines ahead of drawing ended there, although the draw or dispatch that uses the stage rethrows the same stored failure. Settling a scope no longer rethrows. Each ABI call logs such a stage once with its stored reason, marks it deferred and prepares the other stages of the call as before. ResolveGraphicsStagesAbi keeps its ABI record on the first stage that did not fail, PrepareGraphicsStages skips a failed program, and ResolvePreparedGraphics records no rectangle for a failed stage and never adds a failed fragment to a front stage, where the rectangle draw of another fragment would meet it. The compute mode no longer expects the stored failure from the ABI call. A new mode registers a compute, a pixel and a vertex shader whose registration preparation fails, resolves them through every ABI entry point inside a scope twice, and expects three deferrals with the stored reason the first time and none the second, then the stored reason from the compute shader's dispatch. (cherry picked from commit 19a3e07106ff4778bb578b0c0d66b82908a373c3) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 +- .../include/Driver/Shaders/ShaderRegistry.hpp | 2 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 58 +++++++++++++++---- .../tests/execution/PreparedShaders.cpp | 48 +++++++++++++-- 4 files changed, 93 insertions(+), 17 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 5bb47882ae49..c582a8691899 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1528,7 +1528,7 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") if(preparedMode MATCHES "^abi_deferred_") string(REPLACE "_" "-" preparedArgument ${preparedMode}) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index f7fd525817e9..417591cbcf51 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -218,7 +218,7 @@ void SubmitLibraryPrebuild(std::function task); void AwaitLibraryPrebuilds(); void CloseRegistrationPreparation(); -void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target); +void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared = false); ShaderRecompiler::RectListShaders PreparedRectangle(const ShaderSnapshot& snapshot, std::uint64_t vertexId, std::uint64_t fragmentId); ShaderRecompiler::RectListShaders DrawRectangle(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint64_t vertexId, std::uint64_t fragmentId, const ShaderRecompiler::SpirvTarget& target); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index cb7482cee7c7..0987e6d1b78f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -751,6 +751,24 @@ void DeferPipelineStage(PreparedShaderState& prepared, const ShaderSnapshot& sna prepared.deferred = true; } +std::optional FailedRegistration(const ShaderSnapshot& snapshot) { + std::unique_lock lock(snapshot.prepared->mutex); + try { + AwaitRegisteredPreparation(*snapshot.prepared, lock); + } catch (const std::exception& error) { + return error.what(); + } + return std::nullopt; +} + +bool DeferFailedRegistration(const ShaderSnapshot& snapshot) { + const auto failure = FailedRegistration(snapshot); + if (!failure) return false; + std::lock_guard lock(snapshot.prepared->mutex); + DeferPipelineStage(*snapshot.prepared, snapshot, failure->c_str()); + return true; +} + #ifdef __GLIBC__ void trimFreedMemory() { static std::atomic last{0}; @@ -1037,11 +1055,15 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod const auto& program = decoded.programs[index]; const bool fragment = program.binary.stage == ShaderRecompiler::ShaderStage::Fragment; const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize; - std::optional vertex; - if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr); pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots); const auto offset = pushOffset; const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes; + if (deferUnprepared && DeferFailedRegistration(*program.snapshot)) { + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); + continue; + } + std::optional vertex; + if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr); ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, offset, room}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; std::vector key; ShaderRecompiler::BuildPreparedShaderKey(request, key); @@ -1091,7 +1113,6 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std require(registry != nullptr && registry->contains(address), "graphics ABI refers to an unregistered shader"); const auto selected = RegisteredHeader(*registry, shader); require(selected != nullptr, "graphics ABI refers to a replaced shader header"); - if (owner == nullptr) owner = selected; selectedStages.push_back(selected); const auto& snapshot = *selected; require(snapshot.registeredState != nullptr, "registered shader state is missing"); @@ -1110,6 +1131,13 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std for (const auto reg : primitive) state.userConfig.insert_or_assign(reg.offset, reg.value); const auto localDevice = device.Load(); require(localDevice != nullptr, "graphics ABI device is missing"); + for (const auto& selected : selectedStages) { + if (!DeferFailedRegistration(*selected) && owner == nullptr) owner = selected; + } + if (owner == nullptr) { + transaction.Commit(); + return; + } struct GraphicsAbiKeyStorage {}; auto& key = HostThreadLocal, GraphicsAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); @@ -1119,7 +1147,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std for (const auto& stage : abi.stages) { const auto snapshot = stage.lock(); require(snapshot != nullptr, "prepared graphics ABI lost a registered stage"); - if (snapshot->type != 1) ResolvePreparedGraphics(*snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target()); + if (snapshot->type != 1) ResolvePreparedGraphics(*snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target(), true); } transaction.Commit(); return; @@ -1142,7 +1170,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std } for (const auto& stage : prepared) { if (stage.snapshot->type == 1) continue; - ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target()); + ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target(), true); } PreparedShaderState::GraphicsAbi abi{key, registry, {}}; for (const auto& stage : prepared) abi.stages.push_back(stage.snapshot); @@ -1181,6 +1209,10 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span, RegisteredAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); @@ -1232,7 +1264,7 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std:: require(front != fragment, "rectangle stages refer to the same shader"); const auto localDevice = device.Load(); require(localDevice != nullptr, "shader registration device is missing"); - ResolvePreparedGraphics(*front, fragment, primitiveType, localDevice->Target()); + ResolvePreparedGraphics(*front, fragment, primitiveType, localDevice->Target(), true); transaction.Commit(); } @@ -1246,15 +1278,19 @@ void Driver::SettleShaderPreparations(std::span stages) { if (auto snapshot = RegisteredHeader(*shaders, shader)) snapshots.push_back(std::move(snapshot)); } } - for (const auto& snapshot : snapshots) { - std::unique_lock lock(snapshot->prepared->mutex); - AwaitRegisteredPreparation(*snapshot->prepared, lock); - } + for (const auto& snapshot : snapshots) static_cast(FailedRegistration(*snapshot)); } -void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target) { +void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared) { ShaderPreparationTransaction transaction; require(fragment.get() != &front, "rectangle stages refer to the same shader"); + if (deferUnprepared) { + const bool frontFailed = DeferFailedRegistration(front); + if ((fragment != nullptr && DeferFailedRegistration(*fragment)) || frontFailed) { + transaction.Commit(); + return; + } + } std::vector> fragments; { const auto& current = transaction.Read(front); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 51974e862378..acc038fd170b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -720,6 +720,10 @@ alignas(256) constexpr std::array WaterfallVertexCode{ 0xf0000f08u, 0x00040401u, 0x8a7e087eu, 0xbf89fff7u, 0xbefe0306u, 0xbf8a0000u, 0xbf8c3f70u, 0xf80008cfu, 0x07060504u, 0xbf810000u}; alignas(256) constexpr std::array OtherWaterfallVertexCode = WaterfallVertexCode; +alignas(256) constexpr std::array BarrierFreeWaterfallVertexCode{ + 0x7e020280u, 0x7e040280u, 0xbe86037eu, 0xf40c0404u, 0xfa000000u, 0xd4c10008u, 0x00020083u, 0xbf8cc07fu, + 0xf0000f08u, 0x00040401u, 0x8a7e087eu, 0xbf89fff7u, 0xbefe0306u, 0xbf800000u, 0xbf8c3f70u, 0xf80008cfu, + 0x07060504u, 0xbf810000u}; alignas(256) constexpr std::array FrontCode{0x7e0002ffu, 0, 0x7e0202ffu, 0, 0x7e0402ffu, 0, 0x7e0602ffu, 0x3f800000u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; alignas(256) constexpr std::array UndecodablePixelCode{0xf1949f05u, 0x00060018u, 0xbf810000u}; @@ -765,12 +769,47 @@ void AbiDeferredCompute() { AgcDriverWaitIdle_nid_postfix(); AgcDriver::GuestMemory::FlushGpuWrites(output, sizeof(CallerOutput)); Require(std::ranges::all_of(CallerOutput, [](std::uint32_t lane) { return lane == 3u * (5u + 64u); }), "a compute shader its static ABI deferred did not run its user-data callees at its dispatch"); + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredRegistrationFailure() { static AbiHeader store; DescribeAbi(store, WaterfallStoreCode, 0, {{0x20c, ProgramLow(WaterfallStoreCode.data())}, {0x20d, ProgramHigh(WaterfallStoreCode.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 2u << 1u}}, {}); store.shader.specials = &store.specials; - AgcDriverRegisterShader_nid_postfix(&store.shader); - ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(&store.shader, {}, {}); }, "is not a valid runtime value"); - AgcDriverShutdown_nid_postfix(); + static AbiHeader front; + DescribeFront(front); + static AbiHeader pixel; + DescribeWaterfallPixel(pixel); + pixel.context[0] = {0x1b6, 0}; + static AbiHeader vertex; + DescribeWaterfallVertex(vertex, BarrierFreeWaterfallVertexCode); + static AbiHeader interpolated; + DescribeWaterfallPixel(interpolated); + for (auto* header : {&store, &front, &pixel, &vertex, &interpolated}) AgcDriverRegisterShader_nid_postfix(&header->shader); + const std::array primitive{{{0x242, 4}}}; + const std::array frontStages{&front.shader, &pixel.shader}; + const std::array vertexStages{&vertex.shader, &interpolated.shader}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader, &vertex.shader}); + AgcDriverResolveShaderAbi_nid_postfix(&store.shader, {}, {}); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(frontStages, {}, primitive); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(vertexStages, {}, primitive); + AgcDriverResolveGraphicsAbi_nid_postfix(&vertex.shader, &interpolated.shader, 17); + scope.Commit(); + }; + auto log = Logged(resolve); + Require(Deferrals(log) == 3 && log.find("is not a valid runtime value") != std::string::npos, "stages whose registration preparation failed were not deferred once each, with the stored reason, when the title created their pipelines"); + log = Logged(resolve); + Require(Deferrals(log) == 0, "stages whose registration preparation failed were logged again for the same pipelines"); + std::vector commands; + for (std::size_t index = 0; index < store.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, store.registers[index].offset, store.registers[index].value}); + commands.insert(commands.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + sceAgcDriverSubmitAcb(0x20, &packet); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "is not a valid runtime value"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "is not a valid runtime value"); } void AbiDeferredPixel() { @@ -883,7 +922,7 @@ void AbiDeferredUndecodableState() { int main(int argc, char** argv) { try { const std::string_view abiMode = argc == 2 ? argv[1] : ""; - const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state"; + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure"; Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); if (abiDeferred) { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; @@ -891,6 +930,7 @@ int main(int argc, char** argv) { else if (abiMode == "--abi-deferred-pixel") AbiDeferredPixel(); else if (abiMode == "--abi-deferred-stages") AbiDeferredStages(); else if (abiMode == "--abi-deferred-undecodable-state") AbiDeferredUndecodableState(); + else if (abiMode == "--abi-deferred-registration-failure") AbiDeferredRegistrationFailure(); else AbiDeferredRectangle(); std::cout << "shader ABI deferral tests passed: " << abiMode << '\n'; return 0; From b13f634ae38d9e749b2af4daff7086a13b317932 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:29:01 +0000 Subject: [PATCH 058/176] fix(agc): leave a pipeline ABI stage whose vertex inputs do not decode to its first use PrepareGraphicsStages decodes each front-end stage's vertex inputs from its AGC header before it prepares the stage, outside the catch that defers a stage the recompiler cannot prepare. A header the decode refuses, such as a semantic with a statically bound vertex buffer, threw out of sceAgcDriverResolveGraphicsStagesAbi and ended a title that creates its pipelines ahead of drawing. Registration leaves such a stage alone when it is an NGG vertex shader that uses its workgroup, or a mesh, hull or domain stage, so the pipeline ABI is the first to decode it. A decode failure now defers the stage the same way a preparation failure does. The draw that uses it decodes the same header with the draw's own user data and fails there with the same reason. A new mode registers an NGG vertex shader with vertex buffer and attribute tables and one statically bound semantic, resolves its pipeline ABI twice inside a scope, and expects one deferral with the decode's reason and no front-end build, then the same reason from its first draw. (cherry picked from commit 502085c15c364e84a290b3101a3e05b5f9a49959) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 9 ++- .../tests/execution/PreparedShaders.cpp | 57 ++++++++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index c582a8691899..e8ba873fde3e 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1528,7 +1528,7 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") if(preparedMode MATCHES "^abi_deferred_") string(REPLACE "_" "-" preparedArgument ${preparedMode}) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 0987e6d1b78f..207eeeda9ff6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1063,7 +1063,14 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod continue; } std::optional vertex; - if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr); + try { + if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr); + } catch (const std::exception& error) { + if (!deferUnprepared) throw; + DeferPipelineStage(transaction.Edit(*program.snapshot), *program.snapshot, error.what()); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); + continue; + } ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, offset, room}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; std::vector key; ShaderRecompiler::BuildPreparedShaderKey(request, key); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index acc038fd170b..0557383edb4d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -743,6 +743,28 @@ void DescribeWaterfallVertex(AbiHeader& vertex, const std::array directOffsets{}; + ShaderSemantic semantic{}; +}; + +void DescribeStaticFetch(FetchingHeader& vertex) { + vertex.abi.shader.header_size = sizeof(FetchingHeader); + for (auto& reg : vertex.abi.registers) { + if (reg.offset == 0x8b) reg.value = 4u << 1u; + } + vertex.directOffsets.fill(0xffffu); + vertex.directOffsets[8] = 0; + vertex.directOffsets[10] = 2; + vertex.abi.users.direct_resource_offset = vertex.directOffsets.data(); + vertex.abi.users.direct_resource_count = static_cast(vertex.directOffsets.size()); + vertex.semantic.size_in_elements = 4; + vertex.semantic.static_vb_index = 1; + vertex.abi.shader.input_semantics = &vertex.semantic; + vertex.abi.shader.num_input_semantics = 1; +} + void AbiDeferredCompute() { static AbiHeader caller; DescribeAbi(caller, UserDataCaller, 0, {{0x20c, ProgramLow(UserDataCaller.data())}, {0x20d, ProgramHigh(UserDataCaller.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 8u << 1u}}, {}); @@ -917,12 +939,44 @@ void AbiDeferredUndecodableState() { AgcDriverShutdown_nid_postfix(); } +void AbiDeferredVertexInputs() { + static FetchingHeader fetching; + DescribeWaterfallVertex(fetching.abi, WaterfallVertexCode); + DescribeStaticFetch(fetching); + AgcDriverRegisterShader_nid_postfix(&fetching.abi.shader); + const std::array stages{&fetching.abi.shader}; + const std::array primitive{{{0x242, 4}}}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&fetching.abi.shader}); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); + scope.Commit(); + }; + const auto before = ShaderRecompiler::FrontEndBuilds(); + auto log = Logged(resolve); + Require(Deferrals(log) == 1 && log.find("statically bound vertex attributes are not implemented") != std::string::npos, "a graphics stage whose vertex inputs its pipeline ABI cannot decode was not deferred with the decode's reason"); + log = Logged(resolve); + Require(ShaderRecompiler::FrontEndBuilds() == before && Deferrals(log) == 0, "a graphics stage deferred for vertex inputs that do not decode was prepared or logged again"); + alignas(16) static std::array tables{}; + const auto address = reinterpret_cast(tables.data()); + std::vector commands; + for (std::size_t index = 0; index < fetching.abi.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, fetching.abi.registers[index].offset, fetching.abi.registers[index].value}); + for (std::uint32_t index = 0; index < 4; ++index) commands.insert(commands.end(), {0xc0017600u, 0x8cu + index, static_cast(index % 2u == 0u ? address : address >> 32u)}); + for (std::size_t index = 0; index < fetching.abi.shader.num_cx_registers; ++index) commands.insert(commands.end(), {0xc0016900u, fetching.abi.context[index].offset, fetching.abi.context[index].value}); + for (const auto reg : {fetching.abi.specials.vgt_shader_stages_en, fetching.abi.specials.vgt_gs_out_prim_type}) commands.insert(commands.end(), {0xc0016900u, reg.offset, reg.value}); + for (const auto reg : {fetching.abi.specials.ge_cntl, fetching.abi.specials.ge_user_vgpr_en}) commands.insert(commands.end(), {0xc0017900u, reg.offset, reg.value}); + commands.insert(commands.end(), {0xc0017600u, 0x8, 0, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 4, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw of a graphics stage deferred for its vertex inputs was not submitted"); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "statically bound vertex attributes are not implemented"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "statically bound vertex attributes are not implemented"); +} + } int main(int argc, char** argv) { try { const std::string_view abiMode = argc == 2 ? argv[1] : ""; - const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure"; + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure" || abiMode == "--abi-deferred-vertex-inputs"; Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); if (abiDeferred) { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; @@ -931,6 +985,7 @@ int main(int argc, char** argv) { else if (abiMode == "--abi-deferred-stages") AbiDeferredStages(); else if (abiMode == "--abi-deferred-undecodable-state") AbiDeferredUndecodableState(); else if (abiMode == "--abi-deferred-registration-failure") AbiDeferredRegistrationFailure(); + else if (abiMode == "--abi-deferred-vertex-inputs") AbiDeferredVertexInputs(); else AbiDeferredRectangle(); std::cout << "shader ABI deferral tests passed: " << abiMode << '\n'; return 0; From 18bda404d6200b9dab3569202a93effe4deee51d Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:37:03 +0000 Subject: [PATCH 059/176] fix(agc): leave the rect-list pairs a pipeline ABI cannot build to their first draw sceAgcLinkShaders resolves the rect-list pairing of a pipeline when the title creates it, and three cases ended the title there although the rect-list draw that uses the pipeline builds its pair itself: - a pipeline without a pixel shader reached ResolveGraphicsAbi with a null pixel, which required both stages, although the draw pairs the front stage with the null pixel program; - a stage with no artifact yet, such as a pixel shader that exports stencil on a device without VK_EXT_shader_stencil_export, failed the missing stage artifacts check; - a pair that BuildRectListShaders refuses, such as a pixel input with passthrough interpolation, threw from the builder. The ABI now skips the first two, and logs the third once as a deferral of the pixel shader without recording the pair, so the pair's draw builds it again and fails with the builder's reason. When a draw builds the rectangles of its front stage, only the pair of its own fragment can fail it; the pair of another fragment that has no artifact or that the builder refuses is left to that fragment's draw. A new mode resolves a front stage with a null pixel, with a pixel before it has an artifact, with a pixel that never gets one and with the first pixel's passthrough variant, and expects one deferral with the builder's reason. It then draws the front stage as a rect list with the null pixel program, which succeeds although the other two pairs cannot be built, and with the passthrough pixel, which fails with the builder's reason. (cherry picked from commit e986ee0f7ad315cfdfdfe2025ecbe5a52c91d376) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 39 +++++++++------- .../tests/execution/PreparedShaders.cpp | 44 ++++++++++++++++++- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index e8ba873fde3e..8954051cf179 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1528,7 +1528,7 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs abi_deferred_rectangle_pairs) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") if(preparedMode MATCHES "^abi_deferred_") string(REPLACE "_" "-" preparedArgument ${preparedMode}) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 207eeeda9ff6..6f767c93912a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1249,7 +1249,7 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std:: PerformanceContext timingContext(FrameTiming::Preparation()); PerformanceTimer timing("Shader.ResolveGraphicsAbi"); CheckFailure(); - if (primitiveType != 0 && primitiveType != 7 && primitiveType != 17) return; + if ((primitiveType != 0 && primitiveType != 7 && primitiveType != 17) || pixel == nullptr) return; ShaderPreparationTransaction transaction; require(vertex != nullptr && pixel != nullptr, "rectangle ABI requires vertex and fragment shaders"); std::shared_ptr front; @@ -1324,27 +1324,36 @@ void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr< const auto& current = transaction.Read(front); const auto& pixelPrepared = transaction.Read(*pixel); if ((current.entries.empty() && current.deferred) || (pixelPrepared.entries.empty() && pixelPrepared.deferred)) continue; - require(!current.entries.empty() && !pixelPrepared.entries.empty(), "rectangle ABI has missing stage artifacts"); + if (current.entries.empty() || pixelPrepared.entries.empty()) { + require(deferUnprepared || pixel != fragment, "rectangle ABI has missing stage artifacts"); + continue; + } const auto progress = std::ranges::find_if(current.rectangleProgress, [&](const auto& entry) { return entry.fragment.lock() == pixel; }); const auto vertexCount = progress == current.rectangleProgress.end() ? 0u : progress->vertexCount; const auto fragmentCount = progress == current.rectangleProgress.end() ? 0u : progress->fragmentCount; if (vertexCount == current.entries.size() && fragmentCount == pixelPrepared.entries.size()) continue; auto& prepared = transaction.Edit(front); std::vector rectangles; - for (std::size_t vertexIndex = 0; vertexIndex < prepared.entries.size(); ++vertexIndex) { - const auto& vertexEntry = prepared.entries[vertexIndex]; - for (std::size_t fragmentIndex = vertexIndex < vertexCount ? fragmentCount : 0u; fragmentIndex < pixelPrepared.entries.size(); ++fragmentIndex) { - const auto& fragmentEntry = pixelPrepared.entries[fragmentIndex]; - const auto& vertexArtifact = ShaderRecompiler::GetPreparedArtifact(*vertexEntry.handle); - const auto& fragmentArtifact = ShaderRecompiler::GetPreparedArtifact(*fragmentEntry.handle); - const auto exists = [&](const auto& entry) { return entry.vertexId == vertexArtifact.variantId && entry.fragmentId == fragmentArtifact.variantId; }; - if (std::ranges::any_of(prepared.rectangles, exists) || std::ranges::any_of(rectangles, exists)) continue; - ShaderRecompiler::RecompileResult vertexResult; - ShaderRecompiler::RecompileResult fragmentResult; - static_cast(vertexResult) = vertexArtifact; - static_cast(fragmentResult) = fragmentArtifact; - rectangles.push_back({vertexArtifact.variantId, fragmentArtifact.variantId, std::make_shared(ShaderRecompiler::BuildRectListShaders(vertexResult, fragmentResult, target))}); + try { + for (std::size_t vertexIndex = 0; vertexIndex < prepared.entries.size(); ++vertexIndex) { + const auto& vertexEntry = prepared.entries[vertexIndex]; + for (std::size_t fragmentIndex = vertexIndex < vertexCount ? fragmentCount : 0u; fragmentIndex < pixelPrepared.entries.size(); ++fragmentIndex) { + const auto& fragmentEntry = pixelPrepared.entries[fragmentIndex]; + const auto& vertexArtifact = ShaderRecompiler::GetPreparedArtifact(*vertexEntry.handle); + const auto& fragmentArtifact = ShaderRecompiler::GetPreparedArtifact(*fragmentEntry.handle); + const auto exists = [&](const auto& entry) { return entry.vertexId == vertexArtifact.variantId && entry.fragmentId == fragmentArtifact.variantId; }; + if (std::ranges::any_of(prepared.rectangles, exists) || std::ranges::any_of(rectangles, exists)) continue; + ShaderRecompiler::RecompileResult vertexResult; + ShaderRecompiler::RecompileResult fragmentResult; + static_cast(vertexResult) = vertexArtifact; + static_cast(fragmentResult) = fragmentArtifact; + rectangles.push_back({vertexArtifact.variantId, fragmentArtifact.variantId, std::make_shared(ShaderRecompiler::BuildRectListShaders(vertexResult, fragmentResult, target))}); + } } + } catch (const std::exception& error) { + if (!deferUnprepared && pixel == fragment) throw; + if (deferUnprepared) DeferPipelineStage(transaction.Edit(*pixel), *pixel, error.what()); + continue; } prepared.rectangles.insert(prepared.rectangles.end(), std::make_move_iterator(rectangles.begin()), std::make_move_iterator(rectangles.end())); std::erase_if(prepared.rectangleProgress, [&](const auto& entry) { return entry.fragment.expired() || entry.fragment.lock() == pixel; }); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 0557383edb4d..6ffd06cb0c51 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -726,6 +726,7 @@ alignas(256) constexpr std::array BarrierFreeWaterfallVertexC 0x07060504u, 0xbf810000u}; alignas(256) constexpr std::array FrontCode{0x7e0002ffu, 0, 0x7e0202ffu, 0, 0x7e0402ffu, 0, 0x7e0602ffu, 0x3f800000u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; alignas(256) constexpr std::array UndecodablePixelCode{0xf1949f05u, 0x00060018u, 0xbf810000u}; +alignas(256) constexpr std::array PassthroughPixelCode{0xc8020002u, 0xf800180fu, 0x00000000u, 0xbf810000u}; void DescribeWaterfallPixel(AbiHeader& pixel) { DescribeAbi(pixel, WaterfallPixelCode, 1, {{0x8, ProgramLow(WaterfallPixelCode.data())}, {0x9, ProgramHigh(WaterfallPixelCode.data())}, {0xa, 0}, {0xb, 2u << 1u}}, {{0x1b6, 1}, {0x1b3, 2}, {0x1b4, 2}, {0x191, 0}, {0x1c5, 4}}); @@ -971,12 +972,52 @@ void AbiDeferredVertexInputs() { ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "statically bound vertex attributes are not implemented"); } +void AbiDeferredRectanglePairs() { + static AbiHeader front; + DescribeFront(front); + static AbiHeader pixel; + DescribeAbi(pixel, PassthroughPixelCode, 1, {{0x8, ProgramLow(PassthroughPixelCode.data())}, {0x9, ProgramHigh(PassthroughPixelCode.data())}, {0xa, 0}, {0xb, 0}}, {{0x1b6, 1}, {0x1b3, 2}, {0x1b4, 2}, {0x191, 0x420}, {0x1c5, 4}}); + static AbiHeader unresolved; + DescribeWaterfallPixel(unresolved); + for (auto* header : {&front, &pixel, &unresolved}) AgcDriverRegisterShader_nid_postfix(&header->shader); + const std::array passthrough{pixel.context[3]}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, nullptr, 17); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &unresolved.shader, 17); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, passthrough, {}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + scope.Commit(); + }; + auto log = Logged(resolve); + Require(Deferrals(log) == 1 && log.find("custom per-vertex interpolation is unsupported") != std::string::npos, "a rectangle pair its pipeline ABI cannot build was not deferred once with the builder's reason"); + log = Logged(resolve); + Require(Deferrals(log) == 0, "a rectangle pair deferred at its pipeline ABI was logged again"); + const auto draw = [&](bool withPixel) { + std::vector commands; + for (const auto* header : {&front, &pixel}) { + if (header == &pixel && !withPixel) continue; + for (std::size_t index = 0; index < header->shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, header->registers[index].offset, header->registers[index].value}); + for (std::size_t index = 0; index < header->shader.num_cx_registers; ++index) commands.insert(commands.end(), {0xc0016900u, header->context[index].offset, header->context[index].value}); + } + if (!withPixel) commands.insert(commands.end(), {0xc0017600u, 0x8, 0}); + commands.insert(commands.end(), {0xc0016900u, 0x8e, 0xfu, 0xc0016900u, 0x8f, 0xfu, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 17, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "a rect-list draw was not submitted"); + AgcDriverWaitIdle_nid_postfix(); + }; + draw(false); + ExpectFailure([&] { draw(true); }, "custom per-vertex interpolation is unsupported"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "custom per-vertex interpolation is unsupported"); +} + } int main(int argc, char** argv) { try { const std::string_view abiMode = argc == 2 ? argv[1] : ""; - const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure" || abiMode == "--abi-deferred-vertex-inputs"; + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure" || abiMode == "--abi-deferred-vertex-inputs" || abiMode == "--abi-deferred-rectangle-pairs"; Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); if (abiDeferred) { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; @@ -986,6 +1027,7 @@ int main(int argc, char** argv) { else if (abiMode == "--abi-deferred-undecodable-state") AbiDeferredUndecodableState(); else if (abiMode == "--abi-deferred-registration-failure") AbiDeferredRegistrationFailure(); else if (abiMode == "--abi-deferred-vertex-inputs") AbiDeferredVertexInputs(); + else if (abiMode == "--abi-deferred-rectangle-pairs") AbiDeferredRectanglePairs(); else AbiDeferredRectangle(); std::cout << "shader ABI deferral tests passed: " << abiMode << '\n'; return 0; From 57466a015f9860310a70ad954496183eca3e76d8 Mon Sep 17 00:00:00 2001 From: Kovalev Vladislav Date: Fri, 16 Oct 2026 09:37:44 +0600 Subject: [PATCH 060/176] test(agc): reproduce fixed FMASK image results (cherry picked from commit e8544ef039eadd4c4f88337feb1319fd526b1ace) --- .../tests/execution/FmaskImageReads.cpp | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp new file mode 100644 index 000000000000..5416e5189004 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp @@ -0,0 +1,144 @@ +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace AgcDriver::Graphics; + +constexpr std::uint32_t Width = 128; +constexpr std::uint32_t Height = 64; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Sentinel = 0xdeadbeef; +alignas(4096) std::array TexelStorage{}; +const auto Texels = std::span(TexelStorage).subspan((0u - reinterpret_cast(TexelStorage.data())) & 0xffffu, 3 * 65536); +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +alignas(256) constexpr std::array Code{0x34020084u, 0xe0381000u, 0x80000201u, 0xbf8c3f70u, 0xf0001f08u, 0x00020602u, 0xbf8c3f70u, 0xe0781000u, 0x80010601u, 0xbf810000u}; + +std::array Buffer(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast(address >> 32u) & 0xffffu, bytes, 0x31016facu}; +} + +std::array Image(bool layered, std::uint32_t pipeXor, std::uint32_t swizzle = 4u) { + const auto address = reinterpret_cast(Texels.data()) + pipeXor; + return {static_cast(address >> 8u), static_cast(address >> 40u) | (166u << 20u) | (3u << 30u), 31u | ((Height - 1u) << 14u), swizzle | (24u << 20u) | ((layered ? 13u : 9u) << 28u), layered ? 2u | (1u << 16u) : 0u, 0u, 0u, 0u}; +} + +std::uint32_t Mapping(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t round) { + if (round == 0) return 0u; + if (round == 1) return 0x11111111u; + if (round == 2) return 0x11111100u; + std::uint32_t word = 0; + for (std::uint32_t sample = 0; sample < 8; ++sample) word |= ((x + 3u * y + 5u * layer + sample + round) & 7u) << (sample * 4u); + return word; +} + +std::uint64_t EquationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t layer) { + std::uint64_t offset = 0; + for (std::uint32_t bit = 0; bit < 16; ++bit) { + const auto mask = equation.bits[bit]; + const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12u) & (mask & 0xfff000u)) ^ ((layer << 24u) & (mask & 0xff000000u)); + offset |= static_cast(std::popcount(selected) & 1u) << bit; + } + return offset; +} + +void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) { + auto words = Image(layered, pipeXor); + words[1] = (words[1] & ~(0x1ffu << 20u)) | (20u << 20u); + const auto resource = DecodeTextureResource(words); + const auto geometry = DescribeSurface(resource); + Require(geometry.guestBytes <= Texels.size(), "FMASK fixture exceeds its allocation"); + const auto& mip = geometry.mips.at(0); + const auto block = ThinBlockLayout(resource.tileMode, 4u); + const auto* equation = FindTextureSwizzleEquation(24u, 4u); + Require(equation != nullptr, "FMASK fixture has no Z swizzle equation"); + std::ranges::fill(Texels, std::byte{0}); + for (std::uint32_t layer = 0; layer < geometry.layers; ++layer) { + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + auto offset = geometry.GuestLayerOffset(layer) + mip.tiledOffset; + if (mip.tail) offset += EquationOffset(*equation, x + mip.tailX, y + mip.tailY, layer) ^ pipeXor; + else offset += (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + (EquationOffset(*equation, x, y, layer) ^ pipeXor); + const auto value = Mapping(x, y, layer, round); + Require(offset + sizeof(value) <= Texels.size(), "FMASK fixture addresses outside its allocation"); + std::memcpy(Texels.data() + offset, &value, sizeof(value)); + } + } + } +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code, std::span image, bool registration) { + std::vector userData(16u); + const auto input = Buffer(Input.data(), sizeof(Input)); + const auto output = Buffer(Output.data(), Threads * 16u); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4u); + std::copy(image.begin(), image.end(), userData.begin() + 8u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + auto target = device.Target(); + target.registrationModules = registration; + ShaderRecompiler::RecompileRequest request{{ShaderRecompiler::ShaderStage::Compute, reinterpret_cast(code.data()), code, 0u, {}}, {32u, 0u, userData, compute, std::nullopt, std::nullopt, memory}, target, {0u, 0u, 0u, 128u}}; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, bool layered, bool registration, std::uint32_t pipeXor) { + auto code = Code; + if (layered) code[4] = 0xf0001f28u; + ShaderRecompiler::CompiledShaderArtifact first; + for (std::uint32_t round = 0; round < 5; ++round) { + Fill(layered, pipeXor, round); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + Input[lane * 4u] = (lane * 37u + 11u) % Width; + Input[lane * 4u + 1u] = (lane * 23u + 5u) % Height; + Input[lane * 4u + 2u] = layered ? lane & 1u : 0u; + Input[lane * 4u + 3u] = 0u; + } + Output.fill(Sentinel); + const auto swizzle = round == 4u ? 4u | (1u << 3u) | (4u << 6u) : 4u; + const auto shader = Compile(device, code, Image(layered, pipeXor, swizzle), registration); + if (first.variantId == 0u) first = shader; + else Require(shader.cacheHit && shader.variantId == first.variantId, "FMASK data or channel selectors changed the compiled shader artifact"); + device.Dispatch(shader, 1u, 1u, 1u); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + const auto value = Mapping(Input[lane * 4u], Input[lane * 4u + 1u], layered ? Input[lane * 4u + 2u] + 1u : 0u, round); + const std::array expected{value, round == 4u ? 1u : 0u, round == 4u ? value : 0u, 0u}; + for (std::uint32_t component = 0; component < 4u; ++component) Require(Output[lane * 4u + component] == expected[component], "FMASK load did not read its mapping: round " + std::to_string(round) + ", lane " + std::to_string(lane) + ", component " + std::to_string(component)); + } + Require(std::all_of(Output.begin() + Threads * 4u, Output.end(), [](auto value) { return value == Sentinel; }), "FMASK load wrote outside its output"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const bool registration : {false, true}) { + for (const bool layered : {false, true}) { + for (const auto pipeXor : {0u, 0xf00u}) Run(*device, layered, registration, pipeXor); + } + } + std::cout << "FMASK image reads passed: 40 dispatches, 5120 component checks\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 0f4f41bb19aed9bb87c8fa8b166db3084dca880f Mon Sep 17 00:00:00 2001 From: Yaash Singhh Date: Fri, 16 Oct 2026 00:28:20 +0530 Subject: [PATCH 061/176] feat(shader): read hull inputs at a control point chosen by a select or phi (cherry picked from commit 9f429e65ac7301f1541259516a1af8510b6561bc) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 13 ++ .../tests/TessellationLowering.cpp | 197 ++++++++++++++++++ .../Optimization/src/TessellationLowering.cpp | 30 ++- 3 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 8954051cf179..e4d616911798 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -629,6 +629,19 @@ target_link_libraries(agc_scalar_select_mask_tests PRIVATE shader_recompiler_agc set_target_properties(agc_scalar_select_mask_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_scalar_select_mask_tests) +add_test_executable(agc_tessellation_lowering_tests EXCLUDE_FROM_ALL tests/TessellationLowering.cpp) +target_link_libraries(agc_tessellation_lowering_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_tessellation_lowering_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_tessellation_lowering_tests) +if(BUILD_TESTING) + set_target_properties(agc_tessellation_lowering_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_tessellation_lowering COMMAND agc_tessellation_lowering_tests) + if(WIN32) + set_tests_properties(agc_tessellation_lowering PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_wave_control_decode_tests EXCLUDE_FROM_ALL tests/WaveControlDecode.cpp) target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) diff --git a/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp new file mode 100644 index 000000000000..287ca9bfd85d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp @@ -0,0 +1,197 @@ +#include "IntermediateRepresentation/IrBuilder.hpp" +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/TessellationLowering.hpp" +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +constexpr std::uint32_t InputControlPoints = 3u; +constexpr std::uint32_t LocalStride = 32u; +constexpr std::uint32_t PatchStride = InputControlPoints * LocalStride; +constexpr std::uint32_t ReadPc = 0x40u; + +bool IsKind(const IrValue* value, TessellationAttribute kind) { + return value->HasImmediate() && value->Type() == IrType::U32 && value->ImmediateU32() == static_cast(kind); +} + +struct Hull { + IrProgram program; + IrBuilder builder{program}; + IrBlock* block = nullptr; + + Hull() { + program.Resources().stage = IrShaderStage::TessellationControl; + program.Resources().memoryInfo.push_back(MemoryInfo{.kind = ResourceKind::Lds}); + static_cast(newBlock()); + } + + IrBlock& newBlock() { + auto& created = program.CreateBlock(); + program.BlockOrder().push_back(&created); + BlockInfo info; + info.id = created.Id(); + program.Metadata().blockInfo.push_back(info); + if (block == nullptr) program.SetEntryBlock(created); + block = &created; + builder.SetInsertionPoint(created); + return created; + } + + IrValue& invocation() { + return builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::InvocationId)), &builder.Constant(0u)}); + } + + IrValue& relativePatch() { + return builder.Emit(IrOpcode::TessellationBase, IrType::U32, {&builder.Constant(static_cast(TessellationBaseKind::RelativeIndex))}); + } + + IrValue& computed(std::uint32_t limit, std::uint32_t multiple) { + auto& index = builder.Emit(IrOpcode::UMin32, IrType::U32, {&invocation(), &builder.Constant(limit)}); + return builder.IMul(index, builder.Constant(multiple)); + } + + IrValue& firstInvocation() { + return builder.Emit(IrOpcode::ULessThan32, IrType::Bool, {&invocation(), &builder.Constant(1u)}); + } + + IrValue& select(std::uint32_t firstLimit, std::uint32_t firstMultiple, IrValue& second) { + return builder.Emit(IrOpcode::SelectU32, IrType::U32, {&firstInvocation(), &computed(firstLimit, firstMultiple), &second}); + } + + IrValue& inPatch(IrValue& offset) { + return builder.IAdd(builder.IMul(relativePatch(), builder.Constant(PatchStride)), offset); + } + + IrValue& read(IrValue& address) { + auto& loaded = builder.Emit(IrOpcode::LoadSharedU32, IrType::U32, {&address, &builder.ConstantBool(true)}, MemoryFlags{0u, ReadPc}); + static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&loaded})); + return loaded; + } + + IrValue& merge(std::uint32_t firstMultiple, std::uint32_t secondMultiple) { + IrBlock& entry = *block; + IrBlock& first = newBlock(); + IrBlock& second = newBlock(); + IrBlock& together = newBlock(); + builder.SetInsertionPoint(entry); + builder.BranchConditional(firstInvocation(), first, second); + builder.SetInsertionPoint(first); + auto& left = computed(1u, firstMultiple); + builder.Branch(together); + builder.SetInsertionPoint(second); + auto& right = computed(1u, secondMultiple); + builder.Branch(together); + builder.SetInsertionPoint(together); + auto& merged = builder.Emit(IrOpcode::Phi, IrType::U32, {}); + merged.AddPhiOperand(&first, &left); + merged.AddPhiOperand(&second, &right); + return merged; + } +}; + +struct Outcome { + bool lowered = false; + std::string reason; + const IrValue* emitted = nullptr; +}; + +const ShaderTessellationInputInfo Tessellation{.inputControlPoints = InputControlPoints, .outputControlPoints = InputControlPoints, .patchesPerGroup = 1u}; + +Outcome Lower(IrProgram& program) { + Outcome outcome; + try { + TessellationLowering{}.Lower(program, Tessellation); + } catch (const std::exception& error) { + outcome.reason = error.what(); + return outcome; + } + outcome.lowered = true; + for (IrBlock* block : program.BlockOrder()) { + for (IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::GetTessellationAttribute && IsKind(inst->Argument(0), TessellationAttribute::ControlInput)) { + outcome.emitted = inst->Argument(1)->Resolve(); + } + } + } + return outcome; +} + +bool LoweredAt(IrProgram& program, IrValue& address) { + const Outcome outcome = Lower(program); + if (!outcome.lowered) std::fprintf(stderr, "refused: %s\n", outcome.reason.c_str()); + return outcome.lowered && outcome.emitted == address.Resolve(); +} + +bool RefusedBecause(IrProgram& program, const char* reason) { + const Outcome outcome = Lower(program); + if (outcome.lowered) std::fprintf(stderr, "lowered instead of refusing: %s\n", reason); + return !outcome.lowered && outcome.reason.find(reason) != std::string::npos; +} + +bool run(const char* name, const std::function& body) { + bool passed = false; + try { + passed = body(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s: %s\n", name, error.what()); + } + if (!passed) std::fprintf(stderr, "failed: %s\n", name); + return passed; +} + +} + +int main() { + bool passed = true; + + passed &= run("a hull read at a computed control-point index keeps the program's address", [] { + Hull hull; + auto& address = hull.inPatch(hull.computed(2u, LocalStride)); + hull.read(address); + return LoweredAt(hull.program, address); + }); + + passed &= run("a hull read at a control point selected from data keeps the program's address", [] { + Hull hull; + auto& address = hull.inPatch(hull.select(2u, LocalStride, hull.computed(1u, LocalStride))); + hull.read(address); + return LoweredAt(hull.program, address); + }); + + passed &= run("a hull read at a control point merged by a phi keeps the program's address", [] { + Hull hull; + auto& address = hull.inPatch(hull.merge(LocalStride, LocalStride)); + hull.read(address); + return LoweredAt(hull.program, address); + }); + + passed &= run("a selected control point at half a slot is refused", [] { + Hull hull; + auto& address = hull.inPatch(hull.select(2u, LocalStride, hull.computed(1u, LocalStride / 2u))); + hull.read(address); + return RefusedBecause(hull.program, "not a whole number of control points"); + }); + + passed &= run("a control point merged by a phi at half a slot is refused", [] { + Hull hull; + auto& address = hull.inPatch(hull.merge(LocalStride, LocalStride / 2u)); + hull.read(address); + return RefusedBecause(hull.program, "not a whole number of control points"); + }); + + passed &= run("a selected control point at two different offsets is refused", [] { + Hull hull; + auto& offset = hull.builder.IAdd(hull.computed(1u, LocalStride), hull.builder.Constant(LocalStride)); + auto& address = hull.inPatch(hull.select(2u, LocalStride, offset)); + hull.read(address); + return RefusedBecause(hull.program, "not a whole number of control points"); + }); + + return passed ? 0 : 1; +} diff --git a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp index 67930aa0ecd6..4969fdc75182 100644 --- a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp +++ b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp @@ -39,6 +39,13 @@ bool Same(const Affine& left, const Affine& right) { return !left.dynamic && !right.dynamic && left == right; } +std::optional CommonMultiple(const Affine& left, const Affine& right) { + if (left.vertex != right.vertex || left.relative != right.relative || left.constant != right.constant) return std::nullopt; + const auto multiple = std::gcd(left.dynamic ? left.dynamicScale : 0, right.dynamic ? right.dynamicScale : 0); + if (multiple == 0) return std::nullopt; + return Affine{left.vertex, left.relative, left.constant, true, multiple, nullptr}; +} + [[noreturn]] void Fail(const char* reason, std::uint32_t pc) { char text[256]; std::snprintf(text, sizeof(text), "tessellation lowering: %s at pc 0x%x", reason, pc); @@ -412,7 +419,7 @@ class AddressEvaluator { case IrOpcode::ShiftLeftLogical32: { const auto lhs = argument(0); const auto shift = scalar(1); - if (!lhs || !shift || *shift >= 31) return std::nullopt; + if (!lhs || !shift || *shift < 0 || *shift >= 31) return std::nullopt; return scale(*lhs, 1ll << *shift); } case IrOpcode::ShiftRightLogical32: { @@ -440,7 +447,7 @@ class AddressEvaluator { const auto source = argument(0); const auto offset = scalar(1); const auto count = scalar(2); - if (!source || !offset || !count) return std::nullopt; + if (!source || !offset || !count || *count < 0) return std::nullopt; if (*count == 0) return Affine{}; const auto shifted = shiftRight(*source, *offset); if (!shifted || *count >= 32) return shifted; @@ -450,18 +457,22 @@ class AddressEvaluator { if (active != nullptr && Implies(active, value->Argument(0))) return argument(1); const auto lhs = argument(1); const auto rhs = argument(2); - return lhs && rhs && Same(*lhs, *rhs) ? lhs : std::nullopt; + if (!lhs || !rhs) return std::nullopt; + if (Same(*lhs, *rhs)) return lhs; + return dynamic ? CommonMultiple(*lhs, *rhs) : std::nullopt; } default: break; } if (!value->IsPhi() || value->ArgumentCount() == 0) return std::nullopt; - const auto first = argument(0); - for (std::size_t index = 1; first && index < value->ArgumentCount(); ++index) { + auto result = argument(0); + for (std::size_t index = 1; result && index < value->ArgumentCount(); ++index) { const auto other = argument(index); - if (!other || !Same(*other, *first)) return std::nullopt; + if (!other) return std::nullopt; + if (Same(*result, *other)) continue; + result = dynamic ? CommonMultiple(*result, *other) : std::nullopt; } - return first && !first->dynamic ? first : std::nullopt; + return result && (dynamic || !result->dynamic) ? result : std::nullopt; } std::uint32_t vertexCount; @@ -796,7 +807,10 @@ class Lowering { } IrValue& componentAddress(const Access& access, std::uint32_t component) { - if (access.address.dynamic) return insert(access, IrOpcode::IAdd32, IrType::U32, {access.addressValue, &constant(access.memoryOffset + 4u * component)}); + if (access.address.dynamic) { + const std::uint32_t byteOffset = access.memoryOffset + 4u * component; + return byteOffset == 0u ? *access.addressValue : insert(access, IrOpcode::IAdd32, IrType::U32, {access.addressValue, &constant(byteOffset)}); + } IrValue& offset = constant(static_cast(access.address.constant) + 4u * component); if (access.kind != TessellationAttribute::ControlInput || access.address.vertex == 0) return offset; IrValue& invocation = insert(access, IrOpcode::GetBuiltin, IrType::U32, {&constant(static_cast(StageInputKind::InvocationId)), &constant(0u)}); From f413d18cf230276edfe5d0fbcfe968fc20226ac4 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:31:52 +0200 Subject: [PATCH 062/176] fix(agc): return slab slots without allocating host memory (cherry picked from commit 5001f87bc97e95335dcb45a59ed0818691ec7e0e) --- core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp | 1 + core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index b2b4d09787dd..5e5a16a603c7 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -80,6 +80,7 @@ class BufferPool { }; struct Slab { std::vector available; + std::size_t blocks = 0; std::size_t emptyBlocks = 0; }; static std::uint64_t SlabKey(std::uint32_t memoryType, std::size_t capacity, bool addressable); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp index 3d8a5d55591d..821310e84246 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -103,9 +103,11 @@ SlabSlot BufferPool::TakeSlot(const Context& context, std::uint32_t memoryType, for (std::uint32_t index = slots; index-- != 0;) block->free.push_back(index); std::lock_guard lock(slabMutex); auto& slab = slabs[key]; + if (slab.blocks == slab.available.capacity()) slab.available.reserve(std::max(1, slab.available.capacity() * 2)); auto& inserted = *block; slabBlocks.emplace(inserted.memory, std::move(block)); slab.available.push_back(&inserted); + ++slab.blocks; ++slab.emptyBlocks; return takeFrom(*slab.available.back(), slab); } @@ -131,6 +133,7 @@ void BufferPool::PutSlot(VkDeviceMemory memory, VkDeviceSize offset) noexcept { return; } slab.available.erase(std::find(slab.available.begin(), slab.available.end(), &block)); + --slab.blocks; released = std::move(found->second); slabBlocks.erase(found); } From 85d03c062c19713df8974eabc3632ffa6f56d997 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:16:45 +0200 Subject: [PATCH 063/176] fix(agc): ignore failed fence creation outputs (cherry picked from commit a6fe1d46efcf3c3ba8ad8eda16280f3c9dc4fcb4) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 4 +- .../Graphics/src/Resources.cpp | 4 +- .../prx/libSceAgcDriver/tests/Graphics.cpp | 94 ++++++++++++++++++- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index e4d616911798..2d033e61500a 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1475,12 +1475,14 @@ if(BUILD_TESTING) add_test(NAME agc_driver_pm4_failure COMMAND agc_driver_pm4_tests failure) set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_descriptor_pool_reset_failure COMMAND agc_driver_graphics_tests --descriptor-pool-reset-failure) + add_test(NAME agc_command_batch_fence_failure COMMAND agc_driver_graphics_tests --command-batch-fence-failure) + set_tests_properties(agc_command_batch_fence_failure PROPERTIES TIMEOUT 20) add_test(NAME agc_driver_address_space_teardown COMMAND agc_driver_graphics_tests --address-space-teardown) set_tests_properties(agc_driver_address_space_teardown PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_pm4_failure_inline COMMAND agc_driver_pm4_tests failure) set_tests_properties(agc_driver_pm4_failure_inline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0" SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_driver_pm4_failure agc_driver_pm4_failure_inline agc_descriptor_pool_reset_failure agc_driver_address_space_teardown PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_driver_pm4_failure agc_driver_pm4_failure_inline agc_descriptor_pool_reset_failure agc_command_batch_fence_failure agc_driver_address_space_teardown PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(NOT WIN32) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index bb49ff42688b..26bf4ac81e13 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -333,7 +333,9 @@ CommandBatch::CommandBatch(const Context& context) : context(context) { allocation.commandBufferCount = 1; Check(context.Function("vkAllocateCommandBuffers")(context.device, &allocation, &commands), "vkAllocateCommandBuffers"); VkFenceCreateInfo info{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; - Check(context.Function("vkCreateFence")(context.device, &info, nullptr, &fence), "vkCreateFence"); + VkFence created = VK_NULL_HANDLE; + Check(context.Function("vkCreateFence")(context.device, &info, nullptr, &created), "vkCreateFence"); + fence = created; VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; Check(context.Function("vkBeginCommandBuffer")(commands, &begin), "vkBeginCommandBuffer"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 95422c659b44..a4359387dcf3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2,6 +2,7 @@ #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -2819,6 +2821,15 @@ struct LiveCounter { struct MockVulkan { std::uint64_t next = 1; LiveCounter live; + VkResult commandAllocationResult = VK_SUCCESS; + VkResult fenceCreationResult = VK_SUCCESS; + VkResult commandBeginResult = VK_SUCCESS; + VkFence failedFenceOutput = VK_NULL_HANDLE; + std::uint32_t freedCommandBuffers = 0; + std::uint32_t fenceCreateCalls = 0; + std::uint32_t fenceDestroyCalls = 0; + std::uint32_t invalidFenceDestroys = 0; + std::uint32_t commandBeginCalls = 0; std::map bufferSizes; std::map bufferUsage; std::map allocationFlags; @@ -3108,18 +3119,44 @@ VKAPI_ATTR void VKAPI_CALL mockCmdUpdateBuffer(VkCommandBuffer, VkBuffer buffer, } VKAPI_ATTR VkResult VKAPI_CALL mockAllocateCommandBuffers(VkDevice, const VkCommandBufferAllocateInfo* info, VkCommandBuffer* commands) { + if (mock.commandAllocationResult != VK_SUCCESS) { + std::fill_n(commands, info->commandBufferCount, VK_NULL_HANDLE); + return mock.commandAllocationResult; + } for (std::uint32_t index = 0; index < info->commandBufferCount; ++index) commands[index] = makeHandle(); mock.live += info->commandBufferCount; return VK_SUCCESS; } -VKAPI_ATTR void VKAPI_CALL mockFreeCommandBuffers(VkDevice, VkCommandPool, std::uint32_t count, const VkCommandBuffer*) { mock.live -= count; } -VKAPI_ATTR VkResult VKAPI_CALL mockCreateFence(VkDevice, const VkFenceCreateInfo*, const VkAllocationCallbacks*, VkFence* fence) { *fence = makeHandle(); ++mock.live; return VK_SUCCESS; } -VKAPI_ATTR void VKAPI_CALL mockDestroyFence(VkDevice, VkFence, const VkAllocationCallbacks*) { --mock.live; } +VKAPI_ATTR void VKAPI_CALL mockFreeCommandBuffers(VkDevice, VkCommandPool, std::uint32_t count, const VkCommandBuffer*) { + mock.freedCommandBuffers += count; + mock.live -= count; +} +VKAPI_ATTR VkResult VKAPI_CALL mockCreateFence(VkDevice, const VkFenceCreateInfo*, const VkAllocationCallbacks*, VkFence* fence) { + ++mock.fenceCreateCalls; + if (mock.fenceCreationResult != VK_SUCCESS) { + *fence = mock.failedFenceOutput; + return mock.fenceCreationResult; + } + *fence = makeHandle(); + ++mock.live; + return VK_SUCCESS; +} +VKAPI_ATTR void VKAPI_CALL mockDestroyFence(VkDevice, VkFence fence, const VkAllocationCallbacks*) { + ++mock.fenceDestroyCalls; + if (fence != VK_NULL_HANDLE && fence == mock.failedFenceOutput) { + ++mock.invalidFenceDestroys; + return; + } + --mock.live; +} VKAPI_ATTR VkResult VKAPI_CALL mockGetFenceStatus(VkDevice, VkFence) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockResetFences(VkDevice, std::uint32_t, const VkFence*) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockWaitForFences(VkDevice, std::uint32_t, const VkFence*, VkBool32, std::uint64_t) { return VK_SUCCESS; } -VKAPI_ATTR VkResult VKAPI_CALL mockBeginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { return VK_SUCCESS; } +VKAPI_ATTR VkResult VKAPI_CALL mockBeginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { + ++mock.commandBeginCalls; + return mock.commandBeginResult; +} VKAPI_ATTR VkResult VKAPI_CALL mockEndCommandBuffer(VkCommandBuffer) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockQueueSubmit(VkQueue, std::uint32_t, const VkSubmitInfo*, VkFence) { return VK_SUCCESS; } VKAPI_ATTR void VKAPI_CALL mockCmdPipelineBarrier(VkCommandBuffer, VkPipelineStageFlags, VkPipelineStageFlags, VkDependencyFlags, std::uint32_t, const VkMemoryBarrier*, std::uint32_t, const VkBufferMemoryBarrier*, std::uint32_t, const VkImageMemoryBarrier*) {} @@ -3195,6 +3232,51 @@ AgcDriver::Graphics::Context mockContext() { return context; } +void commandBatchFenceFailureTests() { + using namespace AgcDriver::Graphics; + const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + std::uint32_t invalidDestroys = 0; + for (const auto result : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY}) { + for (const bool nonnull : {false, true}) { + mock = MockVulkan{}; + mock.fenceCreationResult = result; + mock.failedFenceOutput = nonnull ? reinterpret_cast(std::uintptr_t{0xdeadbeef}) : VK_NULL_HANDLE; + bool failed = false; + try { + CommandBatch batch(mockContext()); + } catch (const std::runtime_error& error) { + const std::string message = error.what(); + failed = message.find("vkCreateFence") != std::string::npos && message.find("Vulkan result " + std::to_string(result)) != std::string::npos; + } + Require(failed && mock.fenceCreateCalls == 1 && mock.commandBeginCalls == 0, "fence creation failure lost its error or continued construction"); + Require(mock.live == 0 && mock.freedCommandBuffers == 1, "fence creation failure leaked the allocated command buffer"); + invalidDestroys += mock.invalidFenceDestroys; + std::printf("command batch fence result=%d nonnull=%d invalid destroys=%u\n", result, nonnull, mock.invalidFenceDestroys); + const auto destroyed = mock.fenceDestroyCalls; + const auto invalid = mock.invalidFenceDestroys; + mock.fenceCreationResult = VK_SUCCESS; + { + CommandBatch recovered(mockContext()); + Require(recovered.Handle() != VK_NULL_HANDLE, "command batch did not recover after fence creation failed"); + } + Require(mock.live == 0 && mock.freedCommandBuffers == 2 && mock.fenceDestroyCalls == destroyed + 1 && mock.invalidFenceDestroys == invalid, "recovered command batch mishandled its owned objects"); + } + } + for (const bool allocated : {false, true}) { + mock = MockVulkan{}; + mock.commandAllocationResult = allocated ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY; + mock.commandBeginResult = VK_ERROR_OUT_OF_HOST_MEMORY; + bool failed = false; + try { + CommandBatch batch(mockContext()); + } catch (const std::runtime_error& error) { + failed = std::string(error.what()).find(allocated ? "vkBeginCommandBuffer" : "vkAllocateCommandBuffers") != std::string::npos; + } + Require(failed && mock.live == 0 && mock.freedCommandBuffers == (allocated ? 1u : 0u) && mock.fenceDestroyCalls == (allocated ? 1u : 0u) && mock.invalidFenceDestroys == 0, "command batch constructor failure mishandled its owned objects"); + } + Require(invalidDestroys == 0, "command batch destroyed undefined failed fence outputs"); +} + void addressSpaceTeardownTests() { #ifdef _WIN32 _putenv_s("APS5_NO_LEASE_MIRROR", "1"); @@ -6603,6 +6685,10 @@ int main(int argc, char** argv) { setenv("APS5_HEAP_MIRROR_MIB", "4", 1); #endif try { + if (argc == 2 && std::string_view(argv[1]) == "--command-batch-fence-failure") { + commandBatchFenceFailureTests(); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--unused-stencil-reference") { stencilReferenceUnusedTests(); return 0; From e03dee554b0f3752339025535de8c313493bea26 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:25:16 +0200 Subject: [PATCH 064/176] test(agc): isolate command batch fence failure probes (cherry picked from commit 4b7a31c56c448d445ac06bd58edf32272f16389a) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 13 +- .../prx/libSceAgcDriver/tests/Graphics.cpp | 140 +++++++++++------- 2 files changed, 100 insertions(+), 53 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 2d033e61500a..a8e81e4eac45 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1188,6 +1188,15 @@ target_link_libraries(agc_driver_graphics_tests PRIVATE set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_graphics_tests) +if(BUILD_TESTING) + add_test(NAME agc_command_batch_fence_failure COMMAND agc_driver_graphics_tests --command-batch-fence-failure) + set_tests_properties(agc_command_batch_fence_failure PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(agc_command_batch_fence_failure PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_driver_bda_device_tests EXCLUDE_FROM_ALL tests/BdaDevice.cpp tests/BdaShader.cpp @@ -1475,14 +1484,12 @@ if(BUILD_TESTING) add_test(NAME agc_driver_pm4_failure COMMAND agc_driver_pm4_tests failure) set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_descriptor_pool_reset_failure COMMAND agc_driver_graphics_tests --descriptor-pool-reset-failure) - add_test(NAME agc_command_batch_fence_failure COMMAND agc_driver_graphics_tests --command-batch-fence-failure) - set_tests_properties(agc_command_batch_fence_failure PROPERTIES TIMEOUT 20) add_test(NAME agc_driver_address_space_teardown COMMAND agc_driver_graphics_tests --address-space-teardown) set_tests_properties(agc_driver_address_space_teardown PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_pm4_failure_inline COMMAND agc_driver_pm4_tests failure) set_tests_properties(agc_driver_pm4_failure_inline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0" SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_driver_pm4_failure agc_driver_pm4_failure_inline agc_descriptor_pool_reset_failure agc_command_batch_fence_failure agc_driver_address_space_teardown PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_driver_pm4_failure agc_driver_pm4_failure_inline agc_descriptor_pool_reset_failure agc_driver_address_space_teardown PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(NOT WIN32) diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index a4359387dcf3..97e28ae6a4b1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -3119,44 +3119,18 @@ VKAPI_ATTR void VKAPI_CALL mockCmdUpdateBuffer(VkCommandBuffer, VkBuffer buffer, } VKAPI_ATTR VkResult VKAPI_CALL mockAllocateCommandBuffers(VkDevice, const VkCommandBufferAllocateInfo* info, VkCommandBuffer* commands) { - if (mock.commandAllocationResult != VK_SUCCESS) { - std::fill_n(commands, info->commandBufferCount, VK_NULL_HANDLE); - return mock.commandAllocationResult; - } for (std::uint32_t index = 0; index < info->commandBufferCount; ++index) commands[index] = makeHandle(); mock.live += info->commandBufferCount; return VK_SUCCESS; } -VKAPI_ATTR void VKAPI_CALL mockFreeCommandBuffers(VkDevice, VkCommandPool, std::uint32_t count, const VkCommandBuffer*) { - mock.freedCommandBuffers += count; - mock.live -= count; -} -VKAPI_ATTR VkResult VKAPI_CALL mockCreateFence(VkDevice, const VkFenceCreateInfo*, const VkAllocationCallbacks*, VkFence* fence) { - ++mock.fenceCreateCalls; - if (mock.fenceCreationResult != VK_SUCCESS) { - *fence = mock.failedFenceOutput; - return mock.fenceCreationResult; - } - *fence = makeHandle(); - ++mock.live; - return VK_SUCCESS; -} -VKAPI_ATTR void VKAPI_CALL mockDestroyFence(VkDevice, VkFence fence, const VkAllocationCallbacks*) { - ++mock.fenceDestroyCalls; - if (fence != VK_NULL_HANDLE && fence == mock.failedFenceOutput) { - ++mock.invalidFenceDestroys; - return; - } - --mock.live; -} +VKAPI_ATTR void VKAPI_CALL mockFreeCommandBuffers(VkDevice, VkCommandPool, std::uint32_t count, const VkCommandBuffer*) { mock.live -= count; } +VKAPI_ATTR VkResult VKAPI_CALL mockCreateFence(VkDevice, const VkFenceCreateInfo*, const VkAllocationCallbacks*, VkFence* fence) { *fence = makeHandle(); ++mock.live; return VK_SUCCESS; } +VKAPI_ATTR void VKAPI_CALL mockDestroyFence(VkDevice, VkFence, const VkAllocationCallbacks*) { --mock.live; } VKAPI_ATTR VkResult VKAPI_CALL mockGetFenceStatus(VkDevice, VkFence) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockResetFences(VkDevice, std::uint32_t, const VkFence*) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockWaitForFences(VkDevice, std::uint32_t, const VkFence*, VkBool32, std::uint64_t) { return VK_SUCCESS; } -VKAPI_ATTR VkResult VKAPI_CALL mockBeginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { - ++mock.commandBeginCalls; - return mock.commandBeginResult; -} +VKAPI_ATTR VkResult VKAPI_CALL mockBeginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockEndCommandBuffer(VkCommandBuffer) { return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL mockQueueSubmit(VkQueue, std::uint32_t, const VkSubmitInfo*, VkFence) { return VK_SUCCESS; } VKAPI_ATTR void VKAPI_CALL mockCmdPipelineBarrier(VkCommandBuffer, VkPipelineStageFlags, VkPipelineStageFlags, VkDependencyFlags, std::uint32_t, const VkMemoryBarrier*, std::uint32_t, const VkBufferMemoryBarrier*, std::uint32_t, const VkImageMemoryBarrier*) {} @@ -3232,6 +3206,66 @@ AgcDriver::Graphics::Context mockContext() { return context; } +struct CommandBatchFailureProbe { + VkResult allocation = VK_SUCCESS; + VkResult creation = VK_SUCCESS; + VkResult begin = VK_SUCCESS; + VkFence failedFence = VK_NULL_HANDLE; + std::uint32_t freed = 0; + std::uint32_t creates = 0; + std::uint32_t destroys = 0; + std::uint32_t invalidDestroys = 0; + std::uint32_t begins = 0; +}; + +CommandBatchFailureProbe batchProbe; + +VKAPI_ATTR VkResult VKAPI_CALL batchAllocateCommands(VkDevice device, const VkCommandBufferAllocateInfo* info, VkCommandBuffer* commands) { + if (batchProbe.allocation != VK_SUCCESS) { + std::fill_n(commands, info->commandBufferCount, VK_NULL_HANDLE); + return batchProbe.allocation; + } + return mockAllocateCommandBuffers(device, info, commands); +} + +VKAPI_ATTR VkResult VKAPI_CALL batchCreateFence(VkDevice device, const VkFenceCreateInfo* info, const VkAllocationCallbacks* allocator, VkFence* fence) { + ++batchProbe.creates; + if (batchProbe.creation != VK_SUCCESS) { + *fence = batchProbe.failedFence; + return batchProbe.creation; + } + return mockCreateFence(device, info, allocator, fence); +} + +VKAPI_ATTR void VKAPI_CALL batchFreeCommands(VkDevice device, VkCommandPool pool, std::uint32_t count, const VkCommandBuffer* commands) { + batchProbe.freed += count; + mockFreeCommandBuffers(device, pool, count, commands); +} + +VKAPI_ATTR void VKAPI_CALL batchDestroyFence(VkDevice device, VkFence fence, const VkAllocationCallbacks* allocator) { + ++batchProbe.destroys; + if (fence != VK_NULL_HANDLE && fence == batchProbe.failedFence) { + ++batchProbe.invalidDestroys; + return; + } + mockDestroyFence(device, fence, allocator); +} + +VKAPI_ATTR VkResult VKAPI_CALL batchBeginCommands(VkCommandBuffer commands, const VkCommandBufferBeginInfo* info) { + ++batchProbe.begins; + if (batchProbe.begin != VK_SUCCESS) return batchProbe.begin; + return mockBeginCommandBuffer(commands, info); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL batchFailureProc(VkDevice device, const char* name) { + if (std::strcmp(name, "vkAllocateCommandBuffers") == 0) return reinterpret_cast(batchAllocateCommands); + if (std::strcmp(name, "vkCreateFence") == 0) return reinterpret_cast(batchCreateFence); + if (std::strcmp(name, "vkFreeCommandBuffers") == 0) return reinterpret_cast(batchFreeCommands); + if (std::strcmp(name, "vkDestroyFence") == 0) return reinterpret_cast(batchDestroyFence); + if (std::strcmp(name, "vkBeginCommandBuffer") == 0) return reinterpret_cast(batchBeginCommands); + return mockProc(device, name); +} + void commandBatchFenceFailureTests() { using namespace AgcDriver::Graphics; const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); @@ -3239,40 +3273,46 @@ void commandBatchFenceFailureTests() { for (const auto result : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY}) { for (const bool nonnull : {false, true}) { mock = MockVulkan{}; - mock.fenceCreationResult = result; - mock.failedFenceOutput = nonnull ? reinterpret_cast(std::uintptr_t{0xdeadbeef}) : VK_NULL_HANDLE; + batchProbe = {}; + batchProbe.creation = result; + batchProbe.failedFence = nonnull ? reinterpret_cast(std::uintptr_t{0xdeadbeef}) : VK_NULL_HANDLE; + auto context = mockContext(); + context.deviceProc = batchFailureProc; bool failed = false; try { - CommandBatch batch(mockContext()); + CommandBatch batch(context); } catch (const std::runtime_error& error) { const std::string message = error.what(); failed = message.find("vkCreateFence") != std::string::npos && message.find("Vulkan result " + std::to_string(result)) != std::string::npos; } - Require(failed && mock.fenceCreateCalls == 1 && mock.commandBeginCalls == 0, "fence creation failure lost its error or continued construction"); - Require(mock.live == 0 && mock.freedCommandBuffers == 1, "fence creation failure leaked the allocated command buffer"); - invalidDestroys += mock.invalidFenceDestroys; - std::printf("command batch fence result=%d nonnull=%d invalid destroys=%u\n", result, nonnull, mock.invalidFenceDestroys); - const auto destroyed = mock.fenceDestroyCalls; - const auto invalid = mock.invalidFenceDestroys; - mock.fenceCreationResult = VK_SUCCESS; + Require(failed && batchProbe.creates == 1 && batchProbe.begins == 0, "fence creation failure lost its error or continued construction"); + Require(mock.live == 0 && batchProbe.freed == 1, "fence creation failure leaked the allocated command buffer"); + invalidDestroys += batchProbe.invalidDestroys; + std::printf("command batch fence result=%d nonnull=%d invalid destroys=%u\n", result, nonnull, batchProbe.invalidDestroys); + const auto destroyed = batchProbe.destroys; + const auto invalid = batchProbe.invalidDestroys; + batchProbe.creation = VK_SUCCESS; { - CommandBatch recovered(mockContext()); + CommandBatch recovered(context); Require(recovered.Handle() != VK_NULL_HANDLE, "command batch did not recover after fence creation failed"); } - Require(mock.live == 0 && mock.freedCommandBuffers == 2 && mock.fenceDestroyCalls == destroyed + 1 && mock.invalidFenceDestroys == invalid, "recovered command batch mishandled its owned objects"); + Require(mock.live == 0 && batchProbe.freed == 2 && batchProbe.destroys == destroyed + 1 && batchProbe.invalidDestroys == invalid, "recovered command batch mishandled its owned objects"); } } for (const bool allocated : {false, true}) { mock = MockVulkan{}; - mock.commandAllocationResult = allocated ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY; - mock.commandBeginResult = VK_ERROR_OUT_OF_HOST_MEMORY; + batchProbe = {}; + batchProbe.allocation = allocated ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY; + batchProbe.begin = VK_ERROR_OUT_OF_HOST_MEMORY; + auto context = mockContext(); + context.deviceProc = batchFailureProc; bool failed = false; try { - CommandBatch batch(mockContext()); + CommandBatch batch(context); } catch (const std::runtime_error& error) { failed = std::string(error.what()).find(allocated ? "vkBeginCommandBuffer" : "vkAllocateCommandBuffers") != std::string::npos; } - Require(failed && mock.live == 0 && mock.freedCommandBuffers == (allocated ? 1u : 0u) && mock.fenceDestroyCalls == (allocated ? 1u : 0u) && mock.invalidFenceDestroys == 0, "command batch constructor failure mishandled its owned objects"); + Require(failed && mock.live == 0 && batchProbe.freed == (allocated ? 1u : 0u) && batchProbe.destroys == (allocated ? 1u : 0u) && batchProbe.invalidDestroys == 0, "command batch constructor failure mishandled its owned objects"); } Require(invalidDestroys == 0, "command batch destroyed undefined failed fence outputs"); } @@ -6685,14 +6725,14 @@ int main(int argc, char** argv) { setenv("APS5_HEAP_MIRROR_MIB", "4", 1); #endif try { - if (argc == 2 && std::string_view(argv[1]) == "--command-batch-fence-failure") { - commandBatchFenceFailureTests(); - return 0; - } if (argc == 2 && std::string_view(argv[1]) == "--unused-stencil-reference") { stencilReferenceUnusedTests(); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--command-batch-fence-failure") { + commandBatchFenceFailureTests(); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-clear-waits-for-build") { pipelineLibraryClearWaitsForBuildTests(); std::cout << "pipeline library clear waits for active part builds\n"; From b02f6abdc6ff7b73257802c2788e43f47d48ec8b Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:37:38 +0200 Subject: [PATCH 065/176] test(agc): keep fence test registration independent (cherry picked from commit 19655eaba5c878bfc0bb7c3dc464ee300acaf740) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index a8e81e4eac45..b39a5441d8f7 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1185,9 +1185,6 @@ target_link_libraries(agc_driver_graphics_tests PRIVATE libkernel ) -set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF) -configure_windows_unwind(agc_driver_graphics_tests) - if(BUILD_TESTING) add_test(NAME agc_command_batch_fence_failure COMMAND agc_driver_graphics_tests --command-batch-fence-failure) set_tests_properties(agc_command_batch_fence_failure PROPERTIES TIMEOUT 20) From 75b283630019ded7da2d03fd9dcc1c285a4940cc Mon Sep 17 00:00:00 2001 From: milescoviello Date: Fri, 16 Oct 2026 08:37:08 +0000 Subject: [PATCH 066/176] perf(shader): declare the read-only descriptor tables NonWritable The flattened SRT, the BDA page table and the image table map are filled by the host and only loaded by the shader, but they were declared as writable storage buffers. A program that also stores through device addresses then gives the driver no way to know those loads are not aliased by its stores, so every descriptor read stays a separate load. Crimson Desert's largest compute program reads the flattened SRT 12,362 times; with the decoration NVIDIA 595.99 creates its pipeline in 90 s with a 4.3 GiB peak, against 200 s and 10.6 GiB. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 7e061035b902e062654f21475b9ae90b7fd3ac3d) --- .../prx/libSceAgcDriver/tests/ShaderMemory.cpp | 14 ++++++++++++++ .../src/SpirvMemory/SpirvEmitterHelpers.cpp | 1 + 2 files changed, 15 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index f247546ab42c..5fb457887034 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -4536,6 +4536,20 @@ int main(int argc, char** argv) { expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00400000u, 0x00010600u)); }, "unsupported Vulkan/SPIR-V target", "incompatible target accepted"); expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00405000u, 0x00010600u)); }, "unsupported Vulkan target", "unknown Vulkan target accepted"); #endif + const auto readOnly = [&](const char* name) { + std::optional id; + bool decorated = false; + for (std::size_t at = 5; at < first.spirv.size() && (first.spirv[at] >> 16u) != 0u; at += first.spirv[at] >> 16u) { + const auto opcode = first.spirv[at] & 0xffffu; + if (opcode == spv::OpName && std::strcmp(reinterpret_cast(&first.spirv[at + 2u]), name) == 0) id = first.spirv[at + 1u]; + } + for (std::size_t at = 5; id && at < first.spirv.size() && (first.spirv[at] >> 16u) != 0u; at += first.spirv[at] >> 16u) { + decorated = decorated || ((first.spirv[at] & 0xffffu) == spv::OpDecorate && first.spirv[at + 1u] == *id && first.spirv[at + 2u] == spv::DecorationNonWritable); + } + return std::pair{id.has_value(), decorated}; + }; + require(readOnly("flattened_srt") == std::pair{true, true}, "the flattened SRT is not decorated NonWritable"); + for (const auto* table : {"bda_pagetable", "table_map"}) require(!readOnly(table).first || readOnly(table).second, "a read-only BDA page table or image table map is not decorated NonWritable"); payload = 0x40000000u; AgcDriver::ShaderMemory updatedMemory({}); const auto updatedCapture = updatedMemory.Capture(request); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index 3651ae73900e..744bc6862bf0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -436,6 +436,7 @@ void DefineDescriptors(SpirvEmitterState& state) { break; case DescriptorBindingKind::BdaPagetable: state.bdaPagetableVariable = Define(StorageBufferBlockType(state), "bda_pagetable"); + state.module.AddAnnotation(spv::OpDecorate, state.bdaPagetableVariable, spv::DecorationNonWritable); break; case DescriptorBindingKind::FaultBuffer: state.faultBufferVariable = Define(StorageBufferBlockType(state), "fault_buffer"); From 7cdb5ae39cb96280ef8a4608dcb3892148e56f6e Mon Sep 17 00:00:00 2001 From: JJenkx Date: Thu, 15 Oct 2026 14:22:29 -0400 Subject: [PATCH 067/176] test(agc): cover incomplete GPU timing batch completion (cherry picked from commit e5e603d161f4ab1a0fcd81f0647c4cfd782a9a56) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 6 ++- .../prx/libSceAgcDriver/tests/Recorder.cpp | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index b39a5441d8f7..1bc44709112e 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -964,8 +964,12 @@ configure_windows_unwind(agc_driver_recorder_tests) if(BUILD_TESTING) set_target_properties(agc_driver_recorder_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_recorder_device_selection COMMAND agc_driver_recorder_tests --device-selection-only) + add_test(NAME agc_gpu_timing_complete COMMAND agc_driver_recorder_tests --gpu-timing-complete-only) + add_test(NAME agc_gpu_timing_incomplete COMMAND agc_driver_recorder_tests --gpu-timing-incomplete-only) + set_tests_properties(agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES + ENVIRONMENT "APS5_PROFILE_GPU=1" TIMEOUT 15 SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_recorder_device_selection PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_recorder_device_selection agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_pipeline_cache COMMAND agc_driver_recorder_tests --pipeline-cache-only) diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index b9df0dc7491b..cd71a87f1520 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -353,6 +353,14 @@ class Device { bool NullDescriptors() const { return nullDescriptors; } const Context& GetContext() const { return context; } bool Graphics() const { return graphics; } + bool Timestamps() const { + std::uint32_t count = 0; + const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); + queues(context.physical, &count, nullptr); + std::vector families(count); + queues(context.physical, &count, families.data()); + return families.at(context.queueFamily).timestampValidBits != 0; + } void WaitQueue() const { Check(context.Function("vkQueueWaitIdle")(context.queue), "vkQueueWaitIdle"); } private: @@ -774,6 +782,44 @@ void afterRecordedWorkTests(const Device& device, Recorder& recorder) { Require(seen == 1, "an action ran before the completion label recorded ahead of it"); } +int gpuTimingCompletionTests(const Device& device, bool incomplete) { + const auto& context = device.GetContext(); + if (!device.Timestamps()) { + std::cout << "skipped, queue timestamp queries unavailable\n"; + return 77; + } + Require(Recorder::GpuTimingEnabled(), "GPU timing completion test requires APS5_PROFILE_GPU"); + std::unique_lock gpu(GpuMutex()); + int completed = 0; + Recorder recorder(context); + recorder.Activate(); + for (int round = 0; round < 3; ++round) { + const bool missingEnd = incomplete && round == 1; + const auto first = recorder.BeginGpuTiming(0x12340); + Require(first != Recorder::NoTiming, "first GPU timing range was not allocated"); + if (!missingEnd) recorder.EndGpuTiming(first); + const auto second = recorder.BeginGpuTiming(0x12341); + Require(second != Recorder::NoTiming && second != first, "second GPU timing range was not allocated"); + recorder.EndGpuTiming(second); + recorder.OnComplete([&] { ++completed; }); + const auto serial = recorder.Submissions() + 1; + recorder.Submit(); + recorder.Sync(); + Require(completed == round + 1 && recorder.Idle(), "GPU timing prevented batch completion"); + std::size_t missing = 0; + const auto batches = recorder.CompletedBatches(serial - 1, serial, missing); + Require(missing == 0 && batches.size() == 1 && batches.front().serial == serial, "GPU timing batch completion record is missing"); + const auto& batch = batches.front(); + if (missingEnd) { + Require(batch.gpuStartNs == 0 && batch.gpuEndNs == 0, "incomplete query batch published GPU timings"); + } else { + Require(batch.gpuStartNs > 0 && batch.gpuEndNs >= batch.gpuStartNs, "complete query batch lost GPU timings"); + } + } + std::cout << "GPU timing batch completion checked, incomplete=" << incomplete << '\n'; + return 0; +} + void batchStampTests(Recorder& recorder) { Require(recorder.Idle(), "batch stamps: the recorder is busy"); double previousEnd = 0; @@ -5950,6 +5996,9 @@ int main(int argc, char** argv) { return 77; } Device& device = *created; + if (argc == 2 && (std::string_view(argv[1]) == "--gpu-timing-complete-only" || std::string_view(argv[1]) == "--gpu-timing-incomplete-only")) { + return gpuTimingCompletionTests(device, std::string_view(argv[1]) == "--gpu-timing-incomplete-only"); + } if (argc == 2 && std::string_view(argv[1]) == "--import-watch-only") { importWatchTests(device); return 0; From d3923666acd73c3389f01af76bc17dabe4a56928 Mon Sep 17 00:00:00 2001 From: thibaut Date: Mon, 19 Oct 2026 03:07:05 +0200 Subject: [PATCH 068/176] perf(shader): convert one f16 value instead of an (x, 0.0) pair ConvertF16F32 built an (x, 0.0) f32 pair and packed it, and unary f16 results (v_rcp_f16, v_sqrt_f16, ...) were packed with a 0.0 high half: the constant half went through the whole f32-to-f16 conversion each time, which without native f16 is a bit-level rounding sequence. Both now convert the one value; the high half of the bits is zero as before, so the results are the same. Test: agc_f16_single_convert recompiles four v_rcp_f16 with and without native f16 and requires no (x, 0.0) f32 pair; on main each case emits four. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XBXM4pgkMHYEtqN1EQni2P (cherry picked from commit 96bacb7eb88380902fc4f7832938055e5fd01ab5) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 7 +- .../tests/F16SingleConvert.cpp | 93 +++++++++++++++++++ .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 4 +- .../Translation/src/FloatInstructions.cpp | 3 +- 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 1bc44709112e..dbb85754c84a 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -795,6 +795,11 @@ target_link_libraries(agc_fma_mix_once_tests PRIVATE shader_recompiler_agc_drive set_target_properties(agc_fma_mix_once_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_fma_mix_once_tests) +add_test_executable(agc_f16_single_convert_tests EXCLUDE_FROM_ALL tests/F16SingleConvert.cpp) +target_link_libraries(agc_f16_single_convert_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_f16_single_convert_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_f16_single_convert_tests) + add_test_executable(agc_rect_list_position_invariant_tests EXCLUDE_FROM_ALL tests/RectListPositionInvariant.cpp) target_link_libraries(agc_rect_list_position_invariant_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_rect_list_position_invariant_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -1389,7 +1394,7 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_bindless agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests) + foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests) if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests") continue() endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp b/core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp new file mode 100644 index 000000000000..affffdd32ccf --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp @@ -0,0 +1,93 @@ +#include "BdaAbi.hpp" +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace ShaderRecompiler; + +void Check(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +SpirvTarget Target(bool nativeFloat16) { + static constexpr std::array plain{ + spv::CapabilityInt64, spv::CapabilityFloat64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, + }; + static constexpr std::array native{ + spv::CapabilityInt64, spv::CapabilityFloat64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, + spv::CapabilityFloat16, spv::CapabilityRoundingModeRTE, spv::CapabilityDenormPreserve, + }; + static constexpr std::array extensions{ + "SPV_EXT_descriptor_indexing", "SPV_KHR_8bit_storage", "SPV_KHR_float_controls", "SPV_KHR_physical_storage_buffer", + }; + if (nativeFloat16) return {0x00401000u, 0x00010300u, 32, BdaAbi::Version, native, extensions, false, {1024, 1024, 64}, 1024, 32768, {}, {}}; + return {0x00401000u, 0x00010300u, 32, BdaAbi::Version, plain, extensions, false, {1024, 1024, 64}, 1024, 32768, {}, {}}; +} + +std::size_t ZeroPairs(std::span words) { + Check(words.size() >= 5 && words[0] == spv::MagicNumber, "f16 single convert: missing SPIR-V module"); + std::unordered_set floats; + std::unordered_set pairs; + std::unordered_set zeros; + std::size_t count = 0; + for (std::size_t offset = 5; offset < words.size();) { + const auto length = words[offset] >> spv::WordCountShift; + Check(length != 0 && length <= words.size() - offset, "f16 single convert: malformed SPIR-V instruction"); + const auto opcode = words[offset] & spv::OpCodeMask; + if (opcode == spv::OpTypeFloat && length == 3u && words[offset + 2u] == 32u) floats.insert(words[offset + 1u]); + if (opcode == spv::OpTypeVector && length == 4u && floats.contains(words[offset + 2u]) && words[offset + 3u] == 2u) pairs.insert(words[offset + 1u]); + if (opcode == spv::OpConstant && length == 4u && floats.contains(words[offset + 1u]) && words[offset + 3u] == 0u) zeros.insert(words[offset + 2u]); + if (opcode == spv::OpConstantNull && length == 3u && floats.contains(words[offset + 1u])) zeros.insert(words[offset + 2u]); + if (opcode == spv::OpCompositeConstruct && length == 5u && pairs.contains(words[offset + 1u]) && zeros.contains(words[offset + 4u])) ++count; + offset += length; + } + return count; +} + +std::size_t Compile(bool nativeFloat16) { + std::vector code{0xe0300000u, 0x80010100u, 0xbf8c3f70u}; + for (std::uint32_t op = 0; op < 4u; ++op) code.push_back(0x7e04a900u | (256u + (op == 0u ? 1u : 2u))); + code.insert(code.end(), {0xe0700000u, 0x80010200u, 0xbf810000u}); + const std::span words(code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(words)}}}; + const std::array userData{0u, 0u, 0u, 0u, 0x10000000u, 0u, 0x1000u, 0x31016facu}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, reinterpret_cast(code.data()), words, 0, {}}; + request.target = Target(nativeFloat16); + request.layout = {0, 0, 0, 128}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.context.memory = memory; + request.useCache = false; + return ZeroPairs(Recompile(request).spirv.Words()); +} + +} + +int main() { + try { + for (const bool nativeFloat16 : {false, true}) { + const auto pairs = Compile(nativeFloat16); + Check(pairs == 0u, std::string("f16 single convert: four v_rcp_f16 emitted ") + std::to_string(pairs) + " (x, 0.0) f32 pairs to convert one f16 each" + (nativeFloat16 ? " with native f16" : " without native f16")); + } + std::puts("f16 single convert tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 0f214efbfebe..89c98d82a5fc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -685,9 +685,7 @@ std::uint32_t EmitConvertU8U32(SpirvEmitterState& state, std::uint32_t arg0) { } std::uint32_t EmitConvertF16F32(SpirvEmitterState& state, std::uint32_t arg0) { - const auto pair = state.module.AllocateId(); - state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, ConstantF32(state, 0u)); - return EmitPackHalf2x16(state, pair); + return EmitF32ToF16BitsRte(state, arg0); } std::uint32_t EmitConvertS32F32(SpirvEmitterState& state, std::uint32_t arg0) { diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 012e26a4cfc2..3ff93bb9309d 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -124,7 +124,8 @@ bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opco const RdnaOperand& operand = sourceAt(inst, 0u); const IrF32 argument = flushF16InputValue(readF16AsF32(operand)); const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); - IrU32 bits = packHalf2x16(applyF16ResultModifiers(inst.destination, clampF16Overflow(result, {&argument.Value()})), IrF32(ir.ConstantF32(0.0f))); + const IrF16 rounded(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(inst.destination, clampF16Overflow(result, {&argument.Value()})).Value()})); + IrU32 bits(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&rounded.Value()})})); if (opcode == IrOpcode::FPFract32) { bits = IrU32(ir.Select(ir.IEqual(bits.Value(), ir.Constant(0x3c00u)), ir.Constant(0x3bffu), bits.Value())); } From e91f3fba68de7eda476ea3d290a1503816df8168 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Thu, 15 Oct 2026 22:24:26 +0000 Subject: [PATCH 069/176] fix(shader): emit nothing for a vertex s_barrier that precedes every memory access A vertex-path program with LDS, GDS or memory writes refused any s_barrier. NGG passthrough programs start with the s_barrier that orders GS_ALLOC_REQ, before any memory access, and some write buffers later. A barrier that runs before every load, store, atomic, image or LDS access of the program has nothing to order, so it emits no instruction, like the barrier of a program without memory access. Barriers after a memory access still throw. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 7092dde6ee867f2f678773bac6b816061993674e) --- .../libSceAgcDriver/tests/VertexBarrier.cpp | 10 ++++++++++ .../SpirvBackend/src/SpirvFlowEmitter.cpp | 20 +++++++++++++++++++ docs/dev/debt/shader.md | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp b/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp index 8d591fe05165..a854e742071e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp @@ -11,6 +11,8 @@ namespace { constexpr std::array BarrierExport{0xbf8a0000u, 0xf80008cfu, 0u, 0xbf810000u}; constexpr std::array BarrierLdsExport{0xd8340000u, 0u, 0xbf8a0000u, 0xf80008cfu, 0u, 0xbf810000u}; +constexpr std::array LeadingBarrierLds{0xbf8a0000u, 0xd8340000u, 0u, 0xf80008cfu, 0u, 0xbf810000u}; +constexpr std::array ScalarBarrierLds{0xbe8003c1u, 0xbf8a0000u, 0xd8340000u, 0u, 0xf80008cfu, 0u, 0xbf810000u}; ShaderRecompiler::RecompileRequest vertexRequest(std::span code) { using namespace ShaderRecompiler; @@ -39,6 +41,13 @@ void verifyBarrierWithoutMemory() { if (hasControlBarrier(result)) throw std::runtime_error("a vertex program's s_barrier became a workgroup OpControlBarrier, which the Vertex execution model does not allow"); } +void verifyBarrierBeforeLds() { + for (const auto code : {std::span(LeadingBarrierLds), std::span(ScalarBarrierLds)}) { + const auto result = ShaderRecompiler::Recompile(vertexRequest(code)); + if (hasControlBarrier(result)) throw std::runtime_error("an s_barrier before every memory access of a vertex program became a workgroup OpControlBarrier"); + } +} + void verifyBarrierWithLds() { try { (void)ShaderRecompiler::Recompile(vertexRequest(BarrierLdsExport)); @@ -54,6 +63,7 @@ void verifyBarrierWithLds() { int main() { try { verifyBarrierWithoutMemory(); + verifyBarrierBeforeLds(); verifyBarrierWithLds(); std::cout << "vertex barrier tests passed\n"; return 0; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 8b51facdf1e1..bc2f14664a7a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -899,6 +899,25 @@ bool CommunicatesThroughMemory(IrOpcode opcode) { return SharedAccessOf(opcode) != SharedAccess::None || buffer == BufferAccess::Write || buffer == BufferAccess::Atomic || address == AddressAccess::Write || address == AddressAccess::Atomic || image == ImageAccess::Write || image == ImageAccess::Atomic; } +bool AccessesMemory(IrOpcode opcode) { + return opcode == IrOpcode::ReadConst || opcode == IrOpcode::ReadConstBuffer || SharedAccessOf(opcode) != SharedAccess::None || BufferAccessOf(opcode) != BufferAccess::None || AddressOpcodeInfoOf(opcode).access != AddressAccess::None || ImageOpcodeInfoOf(opcode).access != ImageAccess::None; +} + +bool PrecedesEveryMemoryAccess(const IrProgram& program, const IrValue& barrier) { + const auto& blocks = program.BlockOrder(); + const IrBlock* block = blocks.empty() ? nullptr : blocks.front(); + for (std::size_t steps = 0; block != nullptr && steps < blocks.size(); ++steps) { + for (const auto* instruction : block->Instructions()) { + if (instruction == &barrier) return true; + if (AccessesMemory(instruction->Opcode())) return false; + } + if (block->Successors().size() != 1u) return false; + block = block->Successors().front(); + if (block->Predecessors().size() != 1u) return false; + } + return false; +} + } void EmitControlFlow(SpirvModule& module, const IrProgram& program) { @@ -955,6 +974,7 @@ void EmitVoid(SpirvValueEmitContext&) { void EmitBarrier(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; if (state.program.Resources().stage == IrShaderStage::Vertex) { + if (PrecedesEveryMemoryAccess(state.program, inst)) return; for (const auto* block : state.program.BlockOrder()) { for (const auto* instruction : block->Instructions()) { if (CommunicatesThroughMemory(instruction->Opcode())) ctx.Fail(inst, "s_barrier in a vertex program that accesses LDS or GDS or writes memory is not implemented"); diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index b2917b84776c..2a7c9cbc26da 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -21,7 +21,7 @@ - [s_sethalt](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws - [s_subvector_loop_begin and s_subvector_loop_end](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in [SubvectorLoop.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/SubvectorLoop.cpp); `S0` in the end instruction's `elif(S0 == 0)` is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. Measured on PC RDNA2 (Radeon 610M), not on a PS5. The [specialization pass](../../../core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp) keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled - [Vertex entry merged wave info](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) - `s3` of every vertex-path shader is set to `waveSize | waveSize << 8` (vertex and primitive count of a full wave, wave index 0), or to `32 | 32 << 8` for a wave64 program on a 32-lane host subgroup, which runs it at one lane per invocation and holds no lanes 32-63. On a device whose `subgroupSupportedStages` lacks the vertex stage (lavapipe, MoltenVK), vertex and local programs run as one-lane waves: `s3` is `1 | 1 << 8`, the lane id is 0, a ballot is the invocation's own bit, a shuffle or broadcast returns the invocation's value, an elect is true and a subgroup reduction is the value itself, so no `GroupNonUniform` capability is declared. Layout taken from the prologue Mesa's ACO emits for NGG vertex shaders (`s_bfe_u64 exec` from `s3`), not measured on hardware. The driver gives every graphics program user data from `s8`, so the condition does not tell NGG from legacy vertex shaders: they just never read `s3`. A draw whose last wave is partial still reports a full wave, and so does a host wave with idle invocations (an RTX 5070 ran every vertex warp of a non-indexed triangle list with 30 of its 32 invocations). -- [s_barrier in vertex-path programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another; the `GS_ALLOC_REQ` such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Programs with LDS, GDS or memory writes throw. Not measured on hardware +- [s_barrier in vertex-path programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another, or when the barrier runs before every memory access of the program (it is on the entry's straight-line path with no load, store, atomic, image or LDS access ahead of it), so it orders nothing; the `GS_ALLOC_REQ` such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Other programs with LDS, GDS or memory writes throw. Not measured on hardware - [Vertex clip and cull distances](../../../core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp) (libSceAgcDriver) - `PA_CL_VS_OUT_CNTL` enable bit `s` of `CLIP_DIST_ENA`/`CULL_DIST_ENA` is taken to select component `s` of the `pos1`/`pos2` exports, with the Vulkan array index counted by the enabled bits below it, and `VS_OUT_CCDIST0_VEC_ENA` as bit 22; this follows the recompiler's existing position-export mapping, not hardware or documentation. The guest clip distances share one `gl_ClipDistance` array with the all-zero clip position distance, which takes the index after them. A vertex program whose own code has no `pos1`..`pos3` export is compiled and prepared without these bits (`PositionExportLayout`), so registration and draws agree on its artifact; an export in a subroutine it calls is not seen by that scan - [32_AR color exports](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - with `SPI_SHADER_COL_FORMAT` 3 the red value is export channel 0 and the alpha is export channel 1, the GFX10 layout of Mesa's [`emit_ps_color_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L400-L411), ACO and radeonsi in Mesa 22.0 ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/a82920af6efa4a7a66919885c6477fbd8fdc7b9d/src/amd/compiler/aco_instruction_selection.cpp#L11087-L11096), [si_shader_llvm_ps.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/a82920af6efa4a7a66919885c6477fbd8fdc7b9d/src/gallium/drivers/radeonsi/si_shader_llvm_ps.c#L323-L333)) and AMD's [LLPC](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/FragmentColorExport.cpp#L157-L168); before GFX10 the alpha was channel 3. Channels 2 and 3, which none of these compilers enables, are ignored; green and blue are 0 and the alpha of an export without channel 1 is 1.0, the defaults of the other formats, which no source gives. Not measured on hardware, neither on gfx1013 nor on a PC RDNA 2 GPU; pinned in [Pixel32ArExport.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/Pixel32ArExport.cpp) From 80e40b0c2453cb9473199a32aef29a48821b2267 Mon Sep 17 00:00:00 2001 From: Karim Ibrahim <40270714+karimibrahim07@users.noreply.github.com> Date: Fri, 16 Oct 2026 02:46:25 -0400 Subject: [PATCH 070/176] fix(libc): share the constraint handler across libc source files SonyCrt.hpp kept the constraint handler slot in an unnamed namespace, so every source file that included it had its own slot. A handler installed with set_constraint_handler_s in WideConversion.cpp was not the one qsort_s in FileAndHeap.cpp called, and its violations aborted. The slot and its mutex now have one instance per module, and the handler type uses the guest calling convention, as the handlers a title installs and the default handler it gets back are guest functions. (cherry picked from commit ceec3da0a0515237d6634a6d0f0b79defbeee8bc) --- core/libs/prx/libc/include/SonyCrt.hpp | 8 ++++-- core/libs/tests/GuestQsort.cpp | 38 ++++++++++++++++++++++++++ docs/dev/debt/libc.md | 4 +-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index 0eadd73731ee..dee6a42018b9 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -20,6 +20,8 @@ extern "C" GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t* timer, GuestTm* result); +using SonyConstraintHandler = void (APS5_VABI *)(const char* message, void* pointer, int error); + namespace { template @@ -36,9 +38,9 @@ inline TInteger ParseInteger(const char* text, char** end, int base, TInteger (* #endif } -using SonyConstraintHandler = void (*)(const char* message, void* pointer, int error); +} -inline void SonyDefaultConstraintHandler(const char* message, void* pointer, int error) { +inline void APS5_VABI SonyDefaultConstraintHandler(const char* message, void* pointer, int error) { (void)message; (void)pointer; (void)error; @@ -55,6 +57,8 @@ inline SonyConstraintHandler& SonyConstraintHandlerSlot() { return handler; } +namespace { + inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int error) { SonyConstraintHandler handler = nullptr; { diff --git a/core/libs/tests/GuestQsort.cpp b/core/libs/tests/GuestQsort.cpp index cb5503c66ed8..f27a089c635f 100644 --- a/core/libs/tests/GuestQsort.cpp +++ b/core/libs/tests/GuestQsort.cpp @@ -13,6 +13,7 @@ int APS5_VABI mergesort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_V int* APS5_VABI __error_nid_postfix(); void APS5_VABI qsort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); int APS5_VABI qsort_s_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*, void*), void*); +void* APS5_VABI set_constraint_handler_s_nid_postfix(void*); } namespace { @@ -92,6 +93,42 @@ bool ExpectOrderS(const char* name, const std::vector& keys, const std::vec return true; } +int constraintCalls = 0; +int constraintError = 0; +const char* constraintMessage = nullptr; + +void APS5_VABI CountConstraint(const char* message, void*, int error) { + ++constraintCalls; + constraintMessage = message; + constraintError = error; +} + +bool ExpectConstraint(const char* name, void* base, std::size_t count, std::size_t size, bool comparator) { + int direction = 1; + const int calls = constraintCalls; + const int result = qsort_s_nid_postfix(base, count, size, comparator ? CompareKeysWithDirection : nullptr, &direction); + if (result != 22 || constraintCalls != calls + 1 || constraintError != 22 || !constraintMessage || std::strcmp(constraintMessage, name) != 0) { + std::fprintf(stderr, "qsort_s did not report \"%s\" through the installed handler (result %d, calls %d)\n", name, result, constraintCalls - calls); + return false; + } + return true; +} + +bool ExpectQsortSConstraints() { + void* previous = set_constraint_handler_s_nid_postfix(reinterpret_cast(CountConstraint)); + Element element{}; + constexpr std::size_t huge = (static_cast(-1) >> 1) + 1; + bool correct = ExpectConstraint("qsort_s : n > RSIZE_MAX", &element, huge, sizeof(Element), true); + correct &= ExpectConstraint("qsort_s : es > RSIZE_MAX", &element, 1, huge, true); + correct &= ExpectConstraint("qsort_s : a == NULL", nullptr, 2, sizeof(Element), true); + correct &= ExpectConstraint("qsort_s : cmp == NULL", &element, 2, sizeof(Element), false); + if (set_constraint_handler_s_nid_postfix(previous) != reinterpret_cast(CountConstraint)) { + std::fprintf(stderr, "set_constraint_handler_s did not return the installed handler\n"); + correct = false; + } + return correct; +} + bool ExpectQsortS() { std::vector elements; for (int index = 0; index < 50; ++index) elements.push_back({(index * 13) % 17, index}); @@ -188,6 +225,7 @@ int main() { correct &= ExpectOrderS("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); correct &= ExpectOrderS("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); correct &= ExpectQsortS(); + correct &= ExpectQsortSConstraints(); std::vector keys; for (int index = 0; index < 48; ++index) keys.push_back((index * 7 + 3) % 5); correct &= ExpectOrder("pseudomedian of nine", keys, diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 7288cf712814..16352aeb4214 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -40,8 +40,8 @@ - [snprintf_s and vsnprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - follow C11 K.3.5.3.5 and K.3.5.3.12 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`, as `snwprintf_s` does: a null buffer or format, `size` of 0 or above `RSIZE_MAX`, any `%n` and a null `%s`, `%ls` or `%S` argument return a negative value and set `buffer[0]` to zero when the buffer and size allow it; otherwise the output is truncated to the buffer and the full length is returned. A violation only returns: the constraint handler is not invoked, and the console's error value is unverified. Not confirmed on a PS5 title - [vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Not confirmed on a PS5 title - [localeconv](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, `CHAR_MAX` for the 14 `char` fields, `decimal_point` ".", `_False`/`_True` "false"/"true") is read from the `libc.prx` that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with `setlocale` is not known, and `_Frac_grouping`, `_Frac_sep`, `_No`, `_Yes` are empty strings as in that dump -- [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified -- [qsort_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) sorts as `qsort` does and passes its context as the comparator's third argument, as FreeBSD 13's [`qsort_s`](https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/stdlib/qsort.c#L97-L127) (C11 Annex K) does. It returns `EINVAL` after calling the constraint handler when `n` or `es` exceeds `RSIZE_MAX`, or when `n` is not 0 and `a` or `cmp` is null; FreeBSD 11, which the rest of libc follows, has no `qsort_s`. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The handler is the one `SonyCrt.hpp` keeps per source file, so a handler set with `set_constraint_handler_s` is not the one called here. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its `libOSMesa.so.8` +- [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. Within a module every `_s` function calls the installed handler, with the guest's calling convention. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified +- [qsort_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) sorts as `qsort` does and passes its context as the comparator's third argument, as FreeBSD 13's [`qsort_s`](https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/stdlib/qsort.c#L97-L127) (C11 Annex K) does. It returns `EINVAL` after calling the constraint handler when `n` or `es` exceeds `RSIZE_MAX`, or when `n` is not 0 and `a` or `cmp` is null; FreeBSD 11, which the rest of libc follows, has no `qsort_s`. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its `libOSMesa.so.8` - [__udivti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc, re-exported by libSceAmpr) - the hardware traps on a zero divisor; this throws instead - [__umodti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead From 056d52a5edd3f1b1471db7ad7067719d3b6b626e Mon Sep 17 00:00:00 2001 From: Nithishvelan M Date: Fri, 16 Oct 2026 19:14:09 +0530 Subject: [PATCH 071/176] feat(libkernel): support _SC_ARG_MAX, _SC_OPEN_MAX and _SC_IOV_MAX in sysconf (cherry picked from commit 2e4f63b8161c34070be6477f1ab6f6b1d710c267) --- .../prx/libkernel/System/src/SystemConfiguration.cpp | 9 +++++++++ core/libs/tests/GuestSystemConfiguration.cpp | 3 +++ docs/dev/debt/libkernel.md | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index 895a78c599e5..ec7e77d91407 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -123,12 +123,21 @@ std::int64_t APS5_VABI sysconf_nid_postfix(int name) { const int saved = *__error_nid_postfix(); std::int64_t result = -1; switch (name) { + case 1: // _SC_ARG_MAX + result = 262144; + break; case 3: // _SC_CLK_TCK result = 128; break; + case 5: // _SC_OPEN_MAX + result = 1024; + break; case 47: // _SC_PAGESIZE result = PS5_PAGE_SIZE; break; + case 56: // _SC_IOV_MAX + result = 1024; + break; case 57: // _SC_NPROCESSORS_CONF case 58: // _SC_NPROCESSORS_ONLN #ifdef _WIN32 diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index 32e8dadc1d6b..ef0f64f107d7 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -505,8 +505,11 @@ int main() { CheckVmmapSysctl(); CheckNameToMib(); *__error_nid_postfix() = 13; + Require(sysconf_nid_postfix(1) == 262144); Require(sysconf_nid_postfix(3) == 128); + Require(sysconf_nid_postfix(5) == 1024); Require(sysconf_nid_postfix(47) == 0x4000); + Require(sysconf_nid_postfix(56) == 1024); Require(getpagesize_nid_postfix() == sysconf_nid_postfix(47)); Require(sysconf_nid_postfix(57) > 0); Require(sysconf_nid_postfix(58) > 0); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 6f8a93affe3a..152c6619b303 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -18,7 +18,7 @@ - [getlogin_r](../../../core/libs/prx/libkernel/System/src/Login.cpp) (libkernel) returns the host effective account name: `GetUserNameW` as UTF-8 on Windows and `getpwuid_r(geteuid())` on POSIX. A valid account returns a name even without a login session; host `getlogin_r`, `USER` and `LOGNAME` are not consulted. The console's login name and behaviour without a session are unknown. Account lookup or encoding failures, a missing or empty account name, and POSIX account records exceeding the 16 KiB lookup buffer throw; there is no replacement name or retry. A short guest buffer returns `ERANGE` without modifying it, and a null output returns `EFAULT`. - [sceKernelGetSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports baseline system software version `0x01000000` ("01.000.000") for ProsperoStore (PPSA99000); the console's firmware version is not modeled. Measured on PS5 firmware 13.42: record values 0, 24, 39, 40, 41, 48 and 64 in the leading 64-bit field are accepted and preserved, the 28-byte string at offset 8 and packed version at 0x24 are filled, and bytes from 0x28 onward remain untouched. This API reports "13.590.001" / `0x13590001` in that environment, while the Prospero variant reports "13.420.000" / `0x13420000`; their values must not be assumed identical. A null pointer still returns `SCE_KERNEL_ERROR_EFAULT`, which was not measured; other firmware versions and record values remain unverified. - [kern.rng_pseudo](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` fills the whole output buffer with bytes from the host's `std::random_device` and leaves the length unchanged; a write returns -1 with `EPERM` and a call without an output buffer throws. The only caller seen is mbedTLS's `mbedtls_hardware_poll` in the catalog's ProsperoLight boilerplate, which asks for 64-byte blocks and fails if fewer come back. The console's generator, its per-call maximum and its answer to a size query are unknown -- [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. Names other than `_SC_CLK_TCK`, `_SC_PAGESIZE`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` +- [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. `_SC_ARG_MAX` returns 262144 (FreeBSD's `NCARGS`), `_SC_OPEN_MAX` returns 1024, and `_SC_IOV_MAX` returns 1024 (FreeBSD's `UIO_MAXIOV`). Names other than `_SC_ARG_MAX`, `_SC_CLK_TCK`, `_SC_OPEN_MAX`, `_SC_PAGESIZE`, `_SC_IOV_MAX`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` - [hw.realmem](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_HW`, 12} and `sysctlbyname` report the same 8-byte value as `hw.physmem` (the `_SC_PHYS_PAGES` pages of 16 KiB), read-only with `EPERM` for a write and `ENOMEM` for a short buffer, as FreeBSD's [`sysctl_hw_realmem`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_mib.c#L191-L204) does. FreeBSD reports there the memory the firmware describes, which is at least `hw.physmem`; the console's values are not known. Dolphin reads it as the machine's memory size in [`MemPhysical`](https://github.com/dolphin-emu/dolphin/blob/8d2ad1a662472955497e5bb79d7b9ec6999e1f0d/Source/Core/Common/MemoryUtil.cpp#L253-L273) (RetroArch PPSA99169's `dolphin_libretro` core) - [vm.overcommit](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the 4-byte value 0, the default FreeBSD gives its [`vm.overcommit`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/swap_pager.c#L158-L161) `int`, with `ENOMEM` for a short buffer; the console's value is not known and a write throws. mimalloc reads it in [`unix_detect_overcommit`](https://github.com/microsoft/mimalloc/blob/31d034d94cdb8e22f7d7ed55967f581a2d6e831d/src/prim/unix/prim.c#L129-L153) and takes 0 as no overcommit, as PS5MC (PPSA99420) does at start-up - [kern.sdk_version](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the system software version as the 4-byte value `0x01000000` (1.00), the value `sceKernelGetSystemSwVersion` reports; a write returns -1 with `EPERM`. The major version is byte 3 and the minor version byte 2, in hex digits that read as decimal (13.60 is `0x1360xxxx`), as [Capy Mobile](https://github.com/khalifa007/CapyMobile/blob/2758c016/src/ps5/platform.c#L64-L71) (PPSA92280), [remote_lua_loader](https://github.com/n0llptr/remote_lua_loader/blob/7c2840e475/savedata/misc.lua#L195-L209) and the [lapse payload](https://github.com/NetflixNHack/Netflix-N-Hack/blob/605eb93ba5/PS4/payloads/lapse.js#L436-L450) read it. Capy Mobile only displays it. The console's firmware version is not modeled and the low two bytes are unknown From 264467d0a2fcf8728b1743b2a375734b72c8859f Mon Sep 17 00:00:00 2001 From: al3w0f205 Date: Fri, 16 Oct 2026 09:13:15 -0500 Subject: [PATCH 072/176] feat(libSceAudioPropagation): export missing firmware functions from database (cherry picked from commit 7b7eaa6f0ae65c462a2e4b78ede49e22d5ef5d5d) --- .../prx/libSceAudioPropagation/Export.cpp | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/core/libs/prx/libSceAudioPropagation/Export.cpp b/core/libs/prx/libSceAudioPropagation/Export.cpp index 474117fe6fec..0d5ea8424bff 100644 --- a/core/libs/prx/libSceAudioPropagation/Export.cpp +++ b/core/libs/prx/libSceAudioPropagation/Export.cpp @@ -415,4 +415,104 @@ int APS5_VABI sceAudioPropagationSystemUnlock() { return 0; } +int APS5_VABI sceAudioPropagationPathGetNumPoints() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationReportApi() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationResetAttributes() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSourceQueryInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystemLock() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystemQueryInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationAudioPathInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationAudioPathPointInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationMaterialInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationPortalSettingsInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationRayInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSourceRenderInfoInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSourceSetAudioPathsParamInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystem() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystemMemoryInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystemOptionInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationReportRegisterHandler() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationReportUnregisterHandler() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystemEnumHandles() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAudioPropagationSystemUnlock() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } From db82e139865c48773db0de7e485a3b40508ee8df Mon Sep 17 00:00:00 2001 From: Nithishvelan M Date: Fri, 16 Oct 2026 23:17:43 +0530 Subject: [PATCH 073/176] feat(libkernel): return getrlimit soft limit for _SC_OPEN_MAX in sysconf (cherry picked from commit 301fdbe9230407f5267edd576837c7339a179a2a) --- .../libkernel/System/src/SystemConfiguration.cpp | 13 +++++++++++-- core/libs/tests/GuestSystemConfiguration.cpp | 9 ++++++++- docs/dev/debt/libkernel.md | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index ec7e77d91407..78e89552b220 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -22,6 +22,11 @@ extern "C" int* APS5_VABI __error_nid_postfix(); extern "C" int APS5_VABI getpid_nid_postfix(); extern "C" std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); +struct GuestRlimit { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; +extern "C" int APS5_VABI getrlimit_nid_postfix(int resource, GuestRlimit* limit); struct SceKernelSystemSwVersion { std::uint64_t size; @@ -129,9 +134,13 @@ std::int64_t APS5_VABI sysconf_nid_postfix(int name) { case 3: // _SC_CLK_TCK result = 128; break; - case 5: // _SC_OPEN_MAX - result = 1024; + case 5: { // _SC_OPEN_MAX + GuestRlimit limit{}; + if (getrlimit_nid_postfix(8, &limit) == 0) { + result = limit.rlim_cur; + } break; + } case 47: // _SC_PAGESIZE result = PS5_PAGE_SIZE; break; diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index ef0f64f107d7..bed7ab1cbec9 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -28,6 +28,11 @@ int APS5_VABI getpid_nid_postfix(); void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t); int APS5_VABI munmap_nid_postfix(void*, std::size_t); std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); +struct GuestRlimitTest { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; +int APS5_VABI getrlimit_nid_postfix(int resource, GuestRlimitTest* limit); extern char** environ_nid_postfix; } static void Require(bool value) { if (!value) std::abort(); } @@ -507,7 +512,9 @@ int main() { *__error_nid_postfix() = 13; Require(sysconf_nid_postfix(1) == 262144); Require(sysconf_nid_postfix(3) == 128); - Require(sysconf_nid_postfix(5) == 1024); + GuestRlimitTest nofileLimit{}; + Require(getrlimit_nid_postfix(8, &nofileLimit) == 0); + Require(sysconf_nid_postfix(5) == nofileLimit.rlim_cur); Require(sysconf_nid_postfix(47) == 0x4000); Require(sysconf_nid_postfix(56) == 1024); Require(getpagesize_nid_postfix() == sysconf_nid_postfix(47)); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 152c6619b303..29893f2b803f 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -18,7 +18,7 @@ - [getlogin_r](../../../core/libs/prx/libkernel/System/src/Login.cpp) (libkernel) returns the host effective account name: `GetUserNameW` as UTF-8 on Windows and `getpwuid_r(geteuid())` on POSIX. A valid account returns a name even without a login session; host `getlogin_r`, `USER` and `LOGNAME` are not consulted. The console's login name and behaviour without a session are unknown. Account lookup or encoding failures, a missing or empty account name, and POSIX account records exceeding the 16 KiB lookup buffer throw; there is no replacement name or retry. A short guest buffer returns `ERANGE` without modifying it, and a null output returns `EFAULT`. - [sceKernelGetSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports baseline system software version `0x01000000` ("01.000.000") for ProsperoStore (PPSA99000); the console's firmware version is not modeled. Measured on PS5 firmware 13.42: record values 0, 24, 39, 40, 41, 48 and 64 in the leading 64-bit field are accepted and preserved, the 28-byte string at offset 8 and packed version at 0x24 are filled, and bytes from 0x28 onward remain untouched. This API reports "13.590.001" / `0x13590001` in that environment, while the Prospero variant reports "13.420.000" / `0x13420000`; their values must not be assumed identical. A null pointer still returns `SCE_KERNEL_ERROR_EFAULT`, which was not measured; other firmware versions and record values remain unverified. - [kern.rng_pseudo](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` fills the whole output buffer with bytes from the host's `std::random_device` and leaves the length unchanged; a write returns -1 with `EPERM` and a call without an output buffer throws. The only caller seen is mbedTLS's `mbedtls_hardware_poll` in the catalog's ProsperoLight boilerplate, which asks for 64-byte blocks and fails if fewer come back. The console's generator, its per-call maximum and its answer to a size query are unknown -- [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. `_SC_ARG_MAX` returns 262144 (FreeBSD's `NCARGS`), `_SC_OPEN_MAX` returns 1024, and `_SC_IOV_MAX` returns 1024 (FreeBSD's `UIO_MAXIOV`). Names other than `_SC_ARG_MAX`, `_SC_CLK_TCK`, `_SC_OPEN_MAX`, `_SC_PAGESIZE`, `_SC_IOV_MAX`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` +- [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. `_SC_ARG_MAX` returns 262144 (FreeBSD's `NCARGS`), `_SC_OPEN_MAX` returns the `RLIMIT_NOFILE` soft limit via `getrlimit` ([`sysconf.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L106-L115)), and `_SC_IOV_MAX` returns 1024 (FreeBSD's `UIO_MAXIOV`). Names other than `_SC_ARG_MAX`, `_SC_CLK_TCK`, `_SC_OPEN_MAX`, `_SC_PAGESIZE`, `_SC_IOV_MAX`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` - [hw.realmem](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_HW`, 12} and `sysctlbyname` report the same 8-byte value as `hw.physmem` (the `_SC_PHYS_PAGES` pages of 16 KiB), read-only with `EPERM` for a write and `ENOMEM` for a short buffer, as FreeBSD's [`sysctl_hw_realmem`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_mib.c#L191-L204) does. FreeBSD reports there the memory the firmware describes, which is at least `hw.physmem`; the console's values are not known. Dolphin reads it as the machine's memory size in [`MemPhysical`](https://github.com/dolphin-emu/dolphin/blob/8d2ad1a662472955497e5bb79d7b9ec6999e1f0d/Source/Core/Common/MemoryUtil.cpp#L253-L273) (RetroArch PPSA99169's `dolphin_libretro` core) - [vm.overcommit](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the 4-byte value 0, the default FreeBSD gives its [`vm.overcommit`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/swap_pager.c#L158-L161) `int`, with `ENOMEM` for a short buffer; the console's value is not known and a write throws. mimalloc reads it in [`unix_detect_overcommit`](https://github.com/microsoft/mimalloc/blob/31d034d94cdb8e22f7d7ed55967f581a2d6e831d/src/prim/unix/prim.c#L129-L153) and takes 0 as no overcommit, as PS5MC (PPSA99420) does at start-up - [kern.sdk_version](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the system software version as the 4-byte value `0x01000000` (1.00), the value `sceKernelGetSystemSwVersion` reports; a write returns -1 with `EPERM`. The major version is byte 3 and the minor version byte 2, in hex digits that read as decimal (13.60 is `0x1360xxxx`), as [Capy Mobile](https://github.com/khalifa007/CapyMobile/blob/2758c016/src/ps5/platform.c#L64-L71) (PPSA92280), [remote_lua_loader](https://github.com/n0llptr/remote_lua_loader/blob/7c2840e475/savedata/misc.lua#L195-L209) and the [lapse payload](https://github.com/NetflixNHack/Netflix-N-Hack/blob/605eb93ba5/PS4/payloads/lapse.js#L436-L450) read it. Capy Mobile only displays it. The console's firmware version is not modeled and the low two bytes are unknown From 4b464f48afcb91e9cdc3f285e89e8c08d07b81b7 Mon Sep 17 00:00:00 2001 From: fdfawef324 Date: Wed, 14 Oct 2026 18:18:11 -0400 Subject: [PATCH 074/176] feat(libSceJson2): implement Value::setTypeMismatchHandler and store the element access failure and special float handlers Value::setTypeMismatchHandler, setElementAccessFailureHandler and setSpecialFloatHandler were stubs that threw NotImplemented. PPSA07411 calls all three while it sets up its documents. The title calls them as members of Value: on its root Value before parsing into it, with the handler and its context, and treats an int result of 0 as success. They now store the handler and context on that Value and on the values it holds, and return 0. A Value keeps its handlers when its content changes (set, clear, operator=, Parser::parse), and Parser::parse gives the values it creates the handlers of the Value it parses into, since the title reads the errors its handlers record after accessing parsed members. A copy does not carry them. A typed getter on a non-null value of another type calls the type mismatch handler with the requested type, the value, null and the context, and returns its result when it has the requested type, else the getter's default. The element access failure and special float handlers are stored but not called: the error code the console passes to the first and the FunctionType it passes to the second are unknown. A failed element access on a value carrying the first throws instead of reaching the null-access callback. Serializing a non-finite real already threw; with the second set it throws naming the unknown FunctionType. The technical debt entry records what is inferred. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 7e4b1b7c3da3bdffa629ff59b580c7b68ba9b053) --- core/libs/prx/libSceJson2/Export.cpp | 81 +++++++++++-- core/libs/tests/GuestJson.cpp | 168 +++++++++++++++++++++++++++ docs/dev/debt/libSceJson2.md | 3 +- 3 files changed, 239 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 3b8838bd7523..72547068834c 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -47,6 +47,19 @@ static_assert(sizeof(Value) == 8 && sizeof(String) == 8 && sizeof(Array) == 8 && static_assert(sizeof(ArrayIterator) == 8 && sizeof(ObjectIterator) == 8); static_assert(offsetof(Pair, value) == 0x10); +using TypeMismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementAccessFailureHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); + +struct Handlers { + TypeMismatchHandler typeMismatch = nullptr; + void* typeMismatchContext = nullptr; + SpecialFloatHandler specialFloat = nullptr; + void* specialFloatContext = nullptr; + ElementAccessFailureHandler elementAccessFailure = nullptr; + void* elementAccessFailureContext = nullptr; +}; + struct Node { ValueType type = TypeNull; bool boolean = false; @@ -56,6 +69,7 @@ struct Node { String string{}; Array array{}; Object object{}; + Handlers handlers{}; }; using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); @@ -98,7 +112,9 @@ void Clear(Node& n) { if (n.type == TypeString) Destroy(n.string); if (n.type == TypeArray) Destroy(n.array); if (n.type == TypeObject) Destroy(n.object); + const Handlers handlers = n.handlers; n = Node{}; + n.handlers = handlers; } void Destroy(Value& v) { @@ -174,6 +190,7 @@ void AssignNode(Node& destination, const Node& source) { throw; } Clear(destination); + copy.handlers = destination.handlers; destination = copy; } @@ -218,12 +235,35 @@ const Value& NullAccess(ValueType requested, const Value* parent) { return Defaults(requested); } +const Value& TypeMismatch(ValueType requested, const Value* self) { + const Handlers& handlers = NodeOf(*self).handlers; + const Value& result = handlers.typeMismatch(requested, self, nullptr, handlers.typeMismatchContext); + if (result.node && result.node->type == requested) return result; + return Defaults(requested); +} + const Node& Typed(const Value* self, ValueType requested) { const Node& n = NodeOf(*self); if (n.type == requested) return n; + if (n.type != TypeNull && n.handlers.typeMismatch) return NodeOf(TypeMismatch(requested, self)); return NodeOf(NullAccess(requested, self)); } +const Value& MissingElement(const Value* self) { + if (NodeOf(*self).handlers.elementAccessFailure) + throw std::runtime_error("sce::Json::Value: element access failure handler: the error code the console passes is unverified"); + return NullAccess(TypeNull, self); +} + +template +void SetHandlers(Node& n, const TSetter& setter) { + setter(n.handlers); + if (n.type == TypeArray) + for (auto& item : *n.array.items) SetHandlers(NodeOf(item), setter); + if (n.type == TypeObject) + for (auto& pair : *n.object.items) SetHandlers(NodeOf(pair.value), setter); +} + void AppendEscaped(std::string& out, const std::string& text) { out += '"'; for (const unsigned char c : text) { @@ -260,7 +300,11 @@ void Serialize(std::string& out, const Node& n) { case TypeBoolean: out += n.boolean ? "true" : "false"; return; case TypeInteger: out += std::to_string(n.integer); return; case TypeUInteger: out += std::to_string(n.uinteger); return; - case TypeReal: out += RealText(n.real); return; + case TypeReal: + if (!std::isfinite(n.real) && n.handlers.specialFloat) + throw std::runtime_error("sce::Json::Value: special float handler: the FunctionType the console passes is unverified"); + out += RealText(n.real); + return; case TypeString: AppendEscaped(out, *n.string.text); return; case TypeArray: { out += '['; @@ -292,7 +336,7 @@ void Serialize(std::string& out, const Node& n) { class Parser { public: - Parser(const char* text, std::size_t size) : _text(text), _end(text + size) {} + Parser(const char* text, std::size_t size, const Handlers& handlers) : _text(text), _end(text + size), _handlers(handlers) {} bool Parse(Node& out) { skip(); @@ -306,6 +350,7 @@ class Parser { const char* _text; const char* _end; + Handlers _handlers; std::size_t _depth = 0; void skip() { while (_text < _end && (*_text == ' ' || *_text == '\t' || *_text == '\n' || *_text == '\r')) ++_text; } @@ -441,6 +486,7 @@ class Parser { for (;;) { Value item{}; Construct(item); + item.node->handlers = _handlers; out.array.items->push_back(item); skip(); if (!value(*item.node)) return false; @@ -468,6 +514,7 @@ class Parser { Pair pair{}; Construct(pair.key, key); Construct(pair.value); + pair.value.node->handlers = _handlers; out.object.items->push_back(pair); if (!value(*pair.value.node)) return false; skip(); @@ -778,7 +825,7 @@ const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value* self, const char* if (key != nullptr && n.type == TypeObject) for (const auto& pair : *n.object.items) if (*pair.key.text == key) return &pair.value; - return &NullAccess(TypeNull, self); + return &MissingElement(self); } const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t index) { const Node& n = NodeOf(*self); @@ -787,7 +834,7 @@ const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t in std::advance(it, static_cast(index)); return &*it; } - return &NullAccess(TypeNull, self); + return &MissingElement(self); } void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value* self, ValueType type) { Construct(*self, [type](Node& node) { SetType(node, type); }); @@ -798,7 +845,7 @@ const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* if (key != nullptr && n.type == TypeObject) for (const auto& pair : *n.object.items) if (*pair.key.text == *key->text) return &pair.value; - return &NullAccess(TypeNull, self); + return &MissingElement(self); } Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value* self, std::size_t index) { Node& n = NodeOf(*self); @@ -859,7 +906,8 @@ int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(Value* out, Pars int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size) { if (text == nullptr || size == 0) return JsonErrorParse; Node parsed{}; - Parser parser(text, size); + parsed.handlers = NodeOf(*out).handlers; + Parser parser(text, size, parsed.handlers); bool parsedOk = false; try { parsedOk = parser.Parse(parsed); @@ -949,18 +997,27 @@ void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEP state.context = context; } -int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value* self, ElementAccessFailureHandler handler, void* context) { + SetHandlers(NodeOf(*self), [&](Handlers& handlers) { + handlers.elementAccessFailure = handler; + handlers.elementAccessFailureContext = context; + }); return 0; } -int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value* self, SpecialFloatHandler handler, void* context) { + SetHandlers(NodeOf(*self), [&](Handlers& handlers) { + handlers.specialFloat = handler; + handlers.specialFloatContext = context; + }); return 0; } -int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value* self, TypeMismatchHandler handler, void* context) { + SetHandlers(NodeOf(*self), [&](Handlers& handlers) { + handlers.typeMismatch = handler; + handlers.typeMismatchContext = context; + }); return 0; } diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index 465ba6398da2..5a0b6859bf56 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -24,6 +24,9 @@ struct Pair { }; using NullAccessCallback = const Value& (APS5_VABI*)(std::int32_t, const Value*, void*); using SetNullAccessCallback = int (APS5_VABI*)(void*, NullAccessCallback, void*); +using TypeMismatchHandler = const Value& (APS5_VABI*)(std::int32_t, const Value*, const Value*, void*); +using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementAccessFailureHandler = void (APS5_VABI*)(std::int32_t, const String*, const std::size_t*, const Value*, const Value*, void*); extern "C" { int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void*); @@ -33,6 +36,13 @@ int APS5_VABI _ZN3sce4Json11Initializer9terminateEv(void*); int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void*, NullAccessCallback, void*); int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void*, NullAccessCallback, void*); void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(NullAccessCallback, void*); +int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value*, TypeMismatchHandler, void*); +int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value*, SpecialFloatHandler, void*); +int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value*, ElementAccessFailureHandler, void*); +int APS5_VABI _ZN3sce4Json5Value3setEd(Value*, double); +int APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value*, const Value*); +Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value*, const Value*); +void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value*, const Value*); void APS5_VABI _ZN3sce4Json6StringC1Ev(String*); void APS5_VABI _ZN3sce4Json6StringC1EPKc(String*, const char*); void APS5_VABI _ZN3sce4Json6StringD1Ev(String*); @@ -769,6 +779,161 @@ static void StaticNullAccess() { _ZN3sce4Json5ValueD1Ev(&fallback); } +static Value replacement{}; +static int mismatchCalls = 0; +static std::int32_t mismatchRequested = -1; +static const Value* mismatchValue = nullptr; +static const Value* mismatchSecond = nullptr; +static void* mismatchContext = nullptr; + +static const Value& APS5_VABI OnTypeMismatch(std::int32_t requested, const Value* value, const Value* second, void* context) { + ++mismatchCalls; + mismatchRequested = requested; + mismatchValue = value; + mismatchSecond = second; + mismatchContext = context; + return replacement; +} + +static void RequireMismatch(int calls, std::int32_t requested, const Value* value, void* context) { + Require(mismatchCalls == calls && mismatchRequested == requested && mismatchValue == value); + Require(mismatchSecond == nullptr && mismatchContext == context); +} + +static void ValueTypeMismatchHandler() { + _ZN3sce4Json5ValueC1Ev(&replacement); + Require(_ZN3sce4Json5Value3setEl(&replacement, 42) == 0); + int context = 0; + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, OnTypeMismatch, &context) == 0); + const char text[] = "{\"name\":\"text\",\"none\":null,\"list\":[true]}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); + + const Value& name = Member(root, "name"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&name) == 42); + RequireMismatch(1, TypeInteger, &name, &context); + Require(!*_ZNK3sce4Json5Value10getBooleanEv(&name)); + RequireMismatch(2, TypeBoolean, &name, &context); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&name)), "text") == 0); + Require(mismatchCalls == 2); + Require(_ZNK3sce4Json5Array4sizeEv(_ZNK3sce4Json5Value8getArrayEv(&root)) == 0); + RequireMismatch(3, TypeArray, &root, &context); + const Value* element = _ZNK3sce4Json5ValueixEm(&Member(root, "list"), 0); + Require(*_ZNK3sce4Json5Value7getRealEv(element) == 0.0); + RequireMismatch(4, TypeReal, element, &context); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == 0); + Require(mismatchCalls == 4); + + Value later{}; + _ZN3sce4Json5ValueC1Ev(&later); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&later, text, sizeof(text) - 1) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 0); + Require(mismatchCalls == 4); + int laterContext = 0; + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&later, OnTypeMismatch, &laterContext) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 42); + RequireMismatch(5, TypeInteger, &Member(later, "name"), &laterContext); + + Value other{}; + _ZN3sce4Json5ValueC1Ev(&other); + Require(_ZN3sce4Json5Value3setEPKc(&other, "other") == 0); + Require(_ZN3sce4Json5ValueaSERKS1_(&root, &other) == &root); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(6, TypeInteger, &root, &context); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&other) == 0); + Require(mismatchCalls == 6); + Require(_ZN3sce4Json5Value3setEd(&other, 1.5) == 0); + Require(_ZN3sce4Json5Value3setERKS1_(&root, &other) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(7, TypeInteger, &root, &context); + _ZN3sce4Json5Value5clearEv(&root); + Require(_ZN3sce4Json5Value3setEPKc(&root, "cleared") == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(8, TypeInteger, &root, &context); + Value copy{}; + _ZN3sce4Json5ValueC1ERKS1_(©, &root); + Require(*_ZNK3sce4Json5Value10getIntegerEv(©) == 0); + Require(mismatchCalls == 8); + + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, nullptr, nullptr) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 0); + Require(mismatchCalls == 8); + _ZN3sce4Json5ValueD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&other); + _ZN3sce4Json5ValueD1Ev(&later); + _ZN3sce4Json5ValueD1Ev(&root); + _ZN3sce4Json5ValueD1Ev(&replacement); +} + +static int failureCalls = 0; + +static void APS5_VABI OnElementAccessFailure(std::int32_t, const String*, const std::size_t*, const Value*, const Value*, void*) { + ++failureCalls; +} + +template +static bool ThrowsUnverified(const TAccess& access) { + try { + access(); + } catch (const std::runtime_error& error) { + return std::string(error.what()).find("unverified") != std::string::npos; + } + return false; +} + +static void ValueElementAccessFailureHandler() { + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"list\":[1],\"key\":2}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); + int context = 0; + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, OnElementAccessFailure, &context) == 0); + + const Value& list = Member(root, "list"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "key")) == 2); + Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Value8getValueEm(&list, 0)) == 1); + String missing{}; + _ZN3sce4Json6StringC1EPKc(&missing, "missing"); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5ValueixEPKc(&root, "missing"); })); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missing); })); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5ValueixEm(&root, 0); })); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5Value8getValueEm(&list, 1); })); + Require(failureCalls == 0); + + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, nullptr, nullptr) == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "missing")) == TypeNull); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Value8getValueEm(&list, 1)) == TypeNull); + _ZN3sce4Json6StringD1Ev(&missing); + _ZN3sce4Json5ValueD1Ev(&root); +} + +static int specialFloatCalls = 0; + +static Value* APS5_VABI OnSpecialFloat(Value* result, std::int32_t, double, const Value*, void*, bool*) { + ++specialFloatCalls; + _ZN3sce4Json5ValueC1Ev(result); + return result; +} + +static void ValueSpecialFloatHandler() { + Value real{}; + _ZN3sce4Json5ValueC1Ed(&real, std::numeric_limits::quiet_NaN()); + int context = 0; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&real, OnSpecialFloat, &context) == 0); + String out{}; + _ZN3sce4Json6StringC1Ev(&out); + Require(ThrowsUnverified([&] { _ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out); })); + Require(_ZN3sce4Json5Value3setEd(&real, std::numeric_limits::infinity()) == 0); + Require(ThrowsUnverified([&] { _ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out); })); + _ZN3sce4Json6StringD1Ev(&out); + Require(_ZN3sce4Json5Value3setEd(&real, 2.5) == 0); + Require(Serialize(real) == "2.5"); + Require(specialFloatCalls == 0); + _ZN3sce4Json5ValueD1Ev(&real); +} + + static int NumericLocale() { const std::string previous = std::setlocale(LC_NUMERIC, nullptr); const char* selected = nullptr; @@ -851,6 +1016,9 @@ int main(int argc, char** argv) { NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); StaticNullAccess(); + ValueTypeMismatchHandler(); + ValueElementAccessFailureHandler(); + ValueSpecialFloatHandler(); ValueAccess(); InitParameter2BaseConstructor(); InitParameters(); diff --git a/docs/dev/debt/libSceJson2.md b/docs/dev/debt/libSceJson2.md index 92f10a90079c..94400d6f22e4 100644 --- a/docs/dev/debt/libSceJson2.md +++ b/docs/dev/debt/libSceJson2.md @@ -8,10 +8,11 @@ - [sce::Json::Parser::parse](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a null text pointer or zero size returns `JsonErrorParse` (0x80920101) without modifying the destination Value as observed when PPSA26785 passes a null document after its legal screen; whether the console distinguishes a null pointer from a syntax error with a separate parameter error code is unknown. A number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown - [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - layouts inferred, not known. `InitParameter2` is taken as the 40-byte object of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/1811cef94435db7655209fb39f65ddf258b93fd6/src/SharpEmu.Libs/Json/JsonExports.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/eda6ba37ece127ab0396378404d99182e3e8a530/src/libs/libJson2.cpp): the allocator, its user data, the file buffer size, the 32-bit special float format type at 0x18 and 12 bytes both zero, and both constructors zero all of it. `InitParameterRtti2::setAllocatorRtti` writes the allocation parameters and user data where `setAllocator` writes the allocator, as both references do; whether the console also marks the object as using RTTI allocation is unknown. `InitParameterRtti`'s constructor writes the allocation parameters, user data and file buffer size to the first 24 bytes, an order inferred from its mangled arguments. The values of `SpecialFloatFormatType` and what they select are unknown, so `setSpecialFloatFormatType` stores any value and nothing reads it: `Initializer::initialize(InitParameter2)` ignores every field as `initialize(InitParameter)` does, serializing a non-finite real throws whatever the type, and whether the type lets the parser accept NaN or infinity is unknown. The setters return nothing; whether the console's return `this` is unknown - [sce::Json::Object::Pair and sce::Json::Array::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) imported by PPSA30601 - the signatures follow the mangled names, with `Array::iterator::operator++(int)` returning its copy through a result pointer before `this` as `Array::begin` does, since the iterator has a non-trivial destructor. `Pair(const String&, const Value&)` deep-copies both into the layout `Object` iteration already hands out (key at 0, value at 0x10) and zeroes the 8 bytes between them; what the console keeps there is unknown. The default-constructed iterator is singular +- [sce::Json::Value::setTypeMismatchHandler, setElementAccessFailureHandler and setSpecialFloatHandler](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - members of `Value` that return an int, as PPSA07411 calls them: on its root `Value` before parsing into it, with the handler and its context, taking 0 or 0x80848104 as success; they return 0. The PS Vita library's `Value::setNullAccessCallback` is such a member too ([vitasdk](https://github.com/vitasdk/vita-headers/blob/15605617cbbb23d266f88af28899fdb573e2355e/include/psp2/json.h#L638)). The handler signatures follow the mangled names. A handler applies to the `Value` it is set on and to the values that `Value` holds then, and `Parser::parse` gives the values it creates the handlers of the `Value` it parses into, since the title reads the errors its handlers record after accessing parsed members; copies, `push_back` and `Object::operator[]` do not carry them, and how the console finds the handler of an accessed value is unknown. A typed getter on a non-null value of another type calls the type mismatch handler with the requested type, the value, null and the context, and returns the value the handler returns if it has the requested type, else the getter's default; what the console passes as the second `Value`, and whether the accessed value comes first, are unknown. The element access failure handler is not called: a failed `operator[]` or `getValue` on a value carrying it throws, as the error code the console passes, which the title records as its error, is unknown; without one the access still reaches the null-access callback. The special float handler is not called: serializing a non-finite real throws with or without it, as the `FunctionType` values and the `bool*` output are unknown, and the title's handler returns a different replacement for each of its two types - [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the base class's virtual does nothing. A title's allocator that does not override it calls it through its own vtable; the library never calls the title's allocator, so it never reports an allocation failure. Whether the console's base implementation logs anything is unknown ### Functional -- PPSA07411 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [four sce::Json::Value members (setElementAccessFailureHandler, setSpecialFloatHandler, setTypeMismatchHandler, operator bool)](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceConvertKeycodeGetCharacter](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) and [sceNpSessionSignalingGetGroupFromSessionId](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) +- PPSA07411 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sce::Json::Value::operator bool](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceConvertKeycodeGetCharacter](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) and [sceNpSessionSignalingGetGroupFromSessionId](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - PPSA30601 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sce::Json::Object::insert](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), whose return type, and so whether a result pointer precedes `this`, is unknown - [sce::Json::InitParameter2 base-object constructor](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - forwards to the complete-object constructor and uses its inferred layout and default field values; constructor equivalence and initialization bytes remain unverified on the console From 9069f45848a9b1de21e3b314be0362bd75a9af28 Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Sat, 17 Oct 2026 05:25:03 +0000 Subject: [PATCH 075/176] feat(libs): restore libSceNpCppWebApi for dumps that ship no copy (cherry picked from commit 8cd400aa49bfbf3c9ce0205fd7ed56c76869eacd) --- CONTRIBUTING.md | 2 +- core/libs/CMakeLists.txt | 10 + .../libs/prx/libSceNpCppWebApi/CMakeLists.txt | 21 + core/libs/prx/libSceNpCppWebApi/Export.cpp | 1866 +++++++++++++++++ .../tests/GuestNpCppWebApiIntrusivePtr.cpp | 321 +++ core/libs/tests/GuestNpCppWebApiString.cpp | 191 ++ docs/dev/debt/libSceNpCppWebApi.md | 8 + 7 files changed, 2418 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceNpCppWebApi/CMakeLists.txt create mode 100644 core/libs/prx/libSceNpCppWebApi/Export.cpp create mode 100644 core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp create mode 100644 core/libs/tests/GuestNpCppWebApiString.cpp create mode 100644 docs/dev/debt/libSceNpCppWebApi.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce636dd89e48..ba9b94305e20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ - A silent stub is allowed only when it unblocks a title and only affects the UI; add it to [silent stubs](docs/dev/TechnicalDebt.md#silent-stubs). - Missing imports fail at startup with a clear error; don't replace them with fallbacks that keep running. - Implement the general behaviour of a function, not what one title happens to need. -- Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries (NpCppWebApi over NpWebApi2). The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx). +- Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries. The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx). A host copy may still exist for a wrapped library that some dumps lack entirely, like libSceNpCppWebApi: the title's own module wins whenever one is shipped, so it is only ever consulted by titles that ship nothing. - Avoid non-standard extensions (`__attribute__`, etc.) where standard C++ is enough. Helper symbols that must not become NIDs use the `_nid_no_patch` or `_nid_no_patch_cut` suffix. - The relinker uses only the C++20 standard library. - Third-party code is added as a submodule under `3rdparty/` and built from source, not found on the system. With `ANYPS5_USE_SYSTEM_DEPS=ON` the packages packaged by the distributions (SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools) come from the system instead; that resolution lives in [cmake/SystemDeps.cmake](cmake/SystemDeps.cmake) and the default build never reads the system. diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 1ce5b610446b..4139cf7fbedb 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -429,6 +429,16 @@ if(BUILD_TESTING) add_test(NAME host_thread_local COMMAND host_thread_local_tests) set_tests_properties(host_thread_local PROPERTIES TIMEOUT 15) + add_test_executable(guest_np_cpp_web_api_intrusive_ptr_tests tests/GuestNpCppWebApiIntrusivePtr.cpp) + target_include_directories(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_intrusive_ptr_tests) + add_test(NAME guest_np_cpp_web_api_intrusive_ptr COMMAND guest_np_cpp_web_api_intrusive_ptr_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_intrusive_ptr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(savedata_core_write_replacement_tests PRIVATE libSceSaveData) diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt new file mode 100644 index 000000000000..2519e8cc8101 --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceNpCppWebApi SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpCppWebApi PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpCppWebApi) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiString.cpp) + target_include_directories(guest_np_cpp_web_api_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_string_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_string_tests) + add_test(NAME guest_np_cpp_web_api_string COMMAND guest_np_cpp_web_api_string_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_string PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp new file mode 100644 index 000000000000..d65be3d1b5de --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -0,0 +1,1866 @@ +#include +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_WEBAPI_ERROR_UNAVAILABLE = static_cast(0x80552901); + +// Offline PSN: request entry points (API calls, transaction start/readData, factories) fail with +// SCE_NP_WEBAPI_ERROR_UNAVAILABLE; parameter bookkeeping succeeds; response accessors stay unimplemented +// because a failed request never produces a response. +extern "C" { + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody18setStartSerialRankERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setGroupERKNS3_5GroupE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setLimitERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setUsersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_4UserEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody9setOffsetERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122GetRankingResponseBody10getEntriesEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody10setCommentEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody12setSmallDataEPKvm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody15setNeedsTmpRankERKb() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody19setComparedDateTimeERK10SceRtcTick() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody7setPcIdERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V128GetRankingRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_21GetRankingRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V129RecordScoreRequestBodyFactory6createEPNS1_6Common10LibContextElPNS5_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User12setAccountIdERKm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User7setPcIdERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi10getRankingEiRKNS4_21ParameterToGetRankingERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22GetRankingResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking10initializeEPNS1_6Common10LibContextEi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking24setgetRankingRequestBodyENS1_6Common12IntrusivePtrINS3_21GetRankingRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRankingC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRankingD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi22getLargeDataByObjectIdEiRKNS4_33ParameterToGetLargeDataByObjectIdERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_37GetLargeDataByObjectIdResponseHeadersEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectIdC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectIdD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi18getBoardDefinitionEiRKNS4_29ParameterToGetBoardDefinitionERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30GetBoardDefinitionResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition10initializeEPNS1_6Common10LibContextEi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinitionC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinitionD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi11recordScoreEiRKNS4_22ParameterToRecordScoreERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23RecordScoreResponseBodyEEENSA_INS4_26RecordScoreResponseHeadersEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi15recordLargeDataEiRKNS4_26ParameterToRecordLargeDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_27RecordLargeDataResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore10initializeEPNS1_6Common10LibContextEiNS6_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScoreC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScoreD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeData10initializeEPNS1_6Common10LibContextEiNS5_19XPsnAtomicOperationEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeData9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeDataC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeDataD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_27DownloadDataResponseHeadersEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData10initializeEPNS1_6Common10LibContextEPKci() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadDataC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadDataD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10LibContextC1Ev(uint64_t* self) { + *self = 0; + return 0; +} + +// Initialization succeeds as on an offline console (see libSceJson2); individual requests fail. +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10initializeERKNS2_10InitParamsERNS2_10LibContextE(const void* params, uint64_t* context) { + (void)params; + if (context) *context = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +struct IntrusivePtr { + void* object; + void (APS5_VABI* deleter)(void*); + void* libContext; +}; +static_assert(sizeof(IntrusivePtr) == 0x18); + +static constexpr std::size_t ReferenceCountOffset = 0; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +static std::atomic_ref ReferenceCount(void* object, const std::size_t offset) { + return std::atomic_ref(*reinterpret_cast(static_cast(object) + offset)); +} + +static void CopyIntrusivePtr(IntrusivePtr* self, const IntrusivePtr* source, const std::size_t referenceCountOffset) { + *self = *source; + if (self->object) ReferenceCount(self->object, referenceCountOffset).fetch_add(1); +} + +static void ReleaseIntrusivePtr(IntrusivePtr* self, const std::size_t referenceCountOffset, const char* function) { + if (!self->object) return; + if (ReferenceCount(self->object, referenceCountOffset).fetch_sub(1) != 1) return; + if (!self->deleter) NotImplemented_nid_no_patch(function); + self->deleter(self->object); + self->object = nullptr; + self->deleter = nullptr; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, VectorReferenceCountOffset, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextEm() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE8sendDataEPKvm() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE8readDataEPcm() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE8readDataEPcm() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEED1Ev(void* self) { + (void)self; + return 0; +} + +struct CommonString { + std::int32_t referenceCount; + char* buffer; + std::uint64_t bufferSize; + void* libContext; +}; +static_assert(sizeof(CommonString) == 0x20 && offsetof(CommonString, buffer) == 0x08); +static_assert(offsetof(CommonString, bufferSize) == 0x10 && offsetof(CommonString, libContext) == 0x18); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonString* self, void* libContext) { + self->referenceCount = 0; + self->buffer = nullptr; + self->bufferSize = 0; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(CommonString* self) { + std::free(self->buffer); + self->buffer = nullptr; + self->bufferSize = 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEE8pushBackERKS8_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEEC1EPNS2_10LibContextE(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common9terminateERNS2_10LibContextE(uint64_t* context) { + *context = 0; + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V122GetRankingResponseBody22getLastUpdatedDateTimeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V123RecordScoreResponseBody10getTmpRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V123RecordScoreResponseBody16getTmpSerialRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody11getSortModeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13getEntryLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13getUpdateModeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13sortModeIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody15entryLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody15updateModeIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody16getMaxScoreLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody16getMinScoreLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody18maxScoreLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody18minScoreLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody20getLargeDataNumLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody21getLargeDataSizeLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody22largeDataNumLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody23largeDataSizeLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry10getCommentEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry11getObjectIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry11getOnlineIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12commentIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12getAccountIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12getSmallDataEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry13getSerialRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry13objectIdIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry14getHighestRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry14smallDataIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry20getHighestSerialRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry7getPcIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry7getRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry8getScoreEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V19RecordApi26RecordScoreResponseHeaders24getXPsnAtomicOperationIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonString* self) { + return self->buffer != nullptr ? self->buffer : ""; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +// Shared vector bodies. A Vector handle is an IntrusivePtr whose object is a body; +// bodies adopted from a title through the pointer/deleter constructor keep their +// 32-bit reference count at 0x30, so bodies created here do the same. +static constexpr std::uint64_t VectorBodyMagic = 0x434e507056656342ULL; +struct VectorBody { + std::uint64_t magic; + std::uint8_t reserved[0x28]; + std::int32_t referenceCount; + std::int32_t elementKind; // 1 = IntrusivePtr elements, 2 = String elements + void* elements; + std::uint64_t size; + std::uint64_t capacity; +}; +static_assert(sizeof(VectorBody) == 0x50 && offsetof(VectorBody, referenceCount) == 0x30); + +static void APS5_VABI DestroyVectorBody(void* object) { + auto* body = static_cast(object); + if (body->elementKind == 1) { + auto* elements = static_cast(body->elements); + for (std::uint64_t index = 0; index < body->size; ++index) { + ReleaseIntrusivePtr(&elements[index], 0x00, __func__); + } + } else if (body->elementKind == 2) { + auto* elements = static_cast(body->elements); + for (std::uint64_t index = 0; index < body->size; ++index) { + std::free(elements[index].buffer); + } + } + std::free(body->elements); + std::free(body); +} + +static VectorBody* VectorBodyFor(IntrusivePtr* self, const std::int32_t elementKind, const char* function) { + auto* body = static_cast(self->object); + if (body == nullptr) { + body = static_cast(std::calloc(1, sizeof(VectorBody))); + if (body == nullptr) return nullptr; + body->magic = VectorBodyMagic; + body->referenceCount = 1; + body->elementKind = elementKind; + self->object = body; + self->deleter = &DestroyVectorBody; + return body; + } + if (body->magic != VectorBodyMagic || body->elementKind != elementKind) { + // appending to a title-provided body needs its element layout, which is unknown + NotImplemented_nid_no_patch(function); + return nullptr; + } + return body; +} + +static bool VectorGrow(VectorBody* body, const std::size_t elementSize) { + if (body->size < body->capacity) return true; + const std::uint64_t capacity = body->capacity != 0 ? body->capacity * 2 : 4; + void* elements = std::realloc(body->elements, static_cast(capacity) * elementSize); + if (elements == nullptr) return false; + body->elements = elements; + body->capacity = capacity; + return true; +} + +static int VectorPushBackIntrusivePtr(IntrusivePtr* self, const IntrusivePtr* element) { + VectorBody* body = VectorBodyFor(self, 1, __func__); + if (body == nullptr) return -1; + if (!VectorGrow(body, sizeof(IntrusivePtr))) return -1; + auto* elements = static_cast(body->elements); + elements[body->size] = *element; + if (element->object != nullptr) ReferenceCount(element->object, ReferenceCountOffset).fetch_add(1); + body->size += 1; + return 0; +} + +static int VectorPushBackString(IntrusivePtr* self, const CommonString* element) { + VectorBody* body = VectorBodyFor(self, 2, __func__); + if (body == nullptr) return -1; + if (!VectorGrow(body, sizeof(CommonString))) return -1; + CommonString copy = *element; + copy.referenceCount = 0; + copy.buffer = nullptr; + copy.bufferSize = 0; + if (element->buffer != nullptr) { + const std::size_t length = std::strlen(element->buffer) + 1; + copy.buffer = static_cast(std::malloc(length)); + if (copy.buffer == nullptr) return -1; + std::memcpy(copy.buffer, element->buffer, length); + copy.bufferSize = length; + } + static_cast(body->elements)[body->size] = copy; + body->size += 1; + return 0; +} +// -------------------------------------------------------------------------- +// SessionManager2V1 and extra Common symbols, from a title dump whose modules lack +// libSceNpCppWebApi.prx. +// -------------------------------------------------------------------------- + + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession16setMaxSpectatorsERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestPlayerSessionMemberPlayerFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_32RequestCreatePlayerSessionPlayerEEEEEPNS9_INS3_32RequestPlayerSessionMemberPlayerEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V110FromMember11getOnlineIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V122LocalizedStringFactory6createEPNS1_6Common10LibContextEPKcNS_4Json6ObjectEPNS5_12IntrusivePtrINS3_15LocalizedStringEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEED2Ev(void* self) { + (void)self; + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession20setInvitableUserTypeERKNS3_17InvitableUserTypeE() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25joinPlayerSessionAsPlayerEiRKNS4_36ParameterToJoinPlayerSessionAsPlayerERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_52PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(CommonString* self, const char* text) { + if (text == nullptr) return 0; + const std::size_t textLength = std::strlen(text); + const std::size_t oldLength = self->buffer != nullptr ? std::strlen(self->buffer) : 0; + const std::size_t needed = oldLength + textLength + 1; + if (needed > self->bufferSize) { + std::size_t capacity = self->bufferSize != 0 ? static_cast(self->bufferSize) : 16; + while (capacity < needed) capacity *= 2; + char* buffer = static_cast(std::malloc(capacity)); + if (buffer == nullptr) return -1; + if (oldLength != 0) std::memcpy(buffer, self->buffer, oldLength); + std::free(self->buffer); + self->buffer = buffer; + self->bufferSize = capacity; + } + std::memcpy(self->buffer + oldLength, text, textLength + 1); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE() { + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead12getSessionIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead7getFromEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi18leavePlayerSessionEiRKNS4_29ParameterToLeavePlayerSessionERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi27getPlayerSessionInvitationsEiRKNS4_38ParameterToGetPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_54GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession19setJoinableUserTypeERKNS3_16JoinableUserTypeE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead14sessionIdIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead17invitationIdIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody16invitationsIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEEEixEm() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x30); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseD1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr* self, void* body, void (APS5_VABI* deleter)(void*), void* libContext) { + self->object = body; + self->deleter = deleter; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133RequestPlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi20createPlayerSessionsEiRKNS4_31ParameterToCreatePlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30PostPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead9fromIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V126RequestPlayerSessionPlayer15setPushContextsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V127RequestPlayerSessionFactory6createEPNS1_6Common10LibContextEiNS5_12IntrusivePtrINS3_32RequestPlayerSessionMemberPlayerEEERKNS5_6VectorINS5_6StringEEENS8_INS3_15LocalizedStringEEEPNS8_INS3_20RequestPlayerSessionEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V131PlayerSessionPushContextFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(IntrusivePtr* self, const void* element) { + return VectorPushBackString(self, static_cast(element)); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody14getInvitationsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead22receivedTimestampIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11getResponseERS4_() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestCreatePlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_RKNS5_6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEEPNSB_INS3_32RequestCreatePlayerSessionPlayerEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession12getSessionIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V158PostPlayerSessionsSessionIdMemberPlayersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEEEEPNS9_INS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V136PostPlayerSessionsRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_20RequestPlayerSessionEEEEEPNS9_INS3_29PostPlayerSessionsRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +APS5_EXPORT("+gSMGXHgrPU", sceNpCppWebApiUnknown00); +int APS5_VABI sceNpCppWebApiUnknown00(void) { + NotImplemented_nid_no_patch("+gSMGXHgrPU"); + return 0; +} + +APS5_EXPORT("-XNfwZTFkWw", sceNpCppWebApiUnknown01); +int APS5_VABI sceNpCppWebApiUnknown01(void) { + NotImplemented_nid_no_patch("-XNfwZTFkWw"); + return 0; +} + +APS5_EXPORT("0f5+eBcgB14", sceNpCppWebApiUnknown02); +int APS5_VABI sceNpCppWebApiUnknown02(void) { + NotImplemented_nid_no_patch("0f5+eBcgB14"); + return 0; +} + +APS5_EXPORT("1iq5Jtw4jVs", sceNpCppWebApiUnknown03); +int APS5_VABI sceNpCppWebApiUnknown03(void) { + NotImplemented_nid_no_patch("1iq5Jtw4jVs"); + return 0; +} + +APS5_EXPORT("1wpCXuFzH10", sceNpCppWebApiUnknown04); +int APS5_VABI sceNpCppWebApiUnknown04(void) { + NotImplemented_nid_no_patch("1wpCXuFzH10"); + return 0; +} + +APS5_EXPORT("4AFThLW28xQ", sceNpCppWebApiUnknown05); +int APS5_VABI sceNpCppWebApiUnknown05(void) { + NotImplemented_nid_no_patch("4AFThLW28xQ"); + return 0; +} + +APS5_EXPORT("8-9Y4oS+OuE", sceNpCppWebApiUnknown06); +int APS5_VABI sceNpCppWebApiUnknown06(void) { + NotImplemented_nid_no_patch("8-9Y4oS+OuE"); + return 0; +} + +APS5_EXPORT("8dWkWjo5EeE", sceNpCppWebApiUnknown07); +int APS5_VABI sceNpCppWebApiUnknown07(void) { + NotImplemented_nid_no_patch("8dWkWjo5EeE"); + return 0; +} + +APS5_EXPORT("BHvfIGdarBQ", sceNpCppWebApiUnknown08); +int APS5_VABI sceNpCppWebApiUnknown08(void) { + NotImplemented_nid_no_patch("BHvfIGdarBQ"); + return 0; +} + +APS5_EXPORT("Fxxr5lYBfl4", sceNpCppWebApiUnknown09); +int APS5_VABI sceNpCppWebApiUnknown09(void) { + NotImplemented_nid_no_patch("Fxxr5lYBfl4"); + return 0; +} + +APS5_EXPORT("M1LnjRscx-w", sceNpCppWebApiUnknown10); +int APS5_VABI sceNpCppWebApiUnknown10(void) { + NotImplemented_nid_no_patch("M1LnjRscx-w"); + return 0; +} + +APS5_EXPORT("PsX657reZeo", sceNpCppWebApiUnknown11); +int APS5_VABI sceNpCppWebApiUnknown11(void) { + NotImplemented_nid_no_patch("PsX657reZeo"); + return 0; +} + +APS5_EXPORT("RvH9AZ5rCkQ", sceNpCppWebApiUnknown12); +int APS5_VABI sceNpCppWebApiUnknown12(void) { + NotImplemented_nid_no_patch("RvH9AZ5rCkQ"); + return 0; +} + +APS5_EXPORT("SR3BKonD8yk", sceNpCppWebApiUnknown13); +int APS5_VABI sceNpCppWebApiUnknown13(void) { + NotImplemented_nid_no_patch("SR3BKonD8yk"); + return 0; +} + +APS5_EXPORT("TSoQhYKyq5g", sceNpCppWebApiUnknown14); +int APS5_VABI sceNpCppWebApiUnknown14(void) { + NotImplemented_nid_no_patch("TSoQhYKyq5g"); + return 0; +} + +APS5_EXPORT("TgsJjQqp+2k", sceNpCppWebApiUnknown15); +int APS5_VABI sceNpCppWebApiUnknown15(void) { + NotImplemented_nid_no_patch("TgsJjQqp+2k"); + return 0; +} + +APS5_EXPORT("UHQxE3HhTXA", sceNpCppWebApiUnknown16); +int APS5_VABI sceNpCppWebApiUnknown16(void) { + NotImplemented_nid_no_patch("UHQxE3HhTXA"); + return 0; +} + +APS5_EXPORT("ZN-glpk0Rug", sceNpCppWebApiUnknown17); +int APS5_VABI sceNpCppWebApiUnknown17(void) { + NotImplemented_nid_no_patch("ZN-glpk0Rug"); + return 0; +} + +APS5_EXPORT("ZQzXwokCnUE", sceNpCppWebApiUnknown18); +int APS5_VABI sceNpCppWebApiUnknown18(void) { + NotImplemented_nid_no_patch("ZQzXwokCnUE"); + return 0; +} + +APS5_EXPORT("ZwjmbSr4Cxc", sceNpCppWebApiUnknown19); +int APS5_VABI sceNpCppWebApiUnknown19(void) { + NotImplemented_nid_no_patch("ZwjmbSr4Cxc"); + return 0; +} + +APS5_EXPORT("b7leY-LNVnI", sceNpCppWebApiUnknown20); +int APS5_VABI sceNpCppWebApiUnknown20(void) { + NotImplemented_nid_no_patch("b7leY-LNVnI"); + return 0; +} + +APS5_EXPORT("c3J9K9XbQqE", sceNpCppWebApiUnknown21); +int APS5_VABI sceNpCppWebApiUnknown21(void) { + NotImplemented_nid_no_patch("c3J9K9XbQqE"); + return 0; +} + +APS5_EXPORT("cSLbQUQO2Ns", sceNpCppWebApiUnknown22); +int APS5_VABI sceNpCppWebApiUnknown22(void) { + NotImplemented_nid_no_patch("cSLbQUQO2Ns"); + return 0; +} + +APS5_EXPORT("cqOxjL0ZfFA", sceNpCppWebApiUnknown23); +int APS5_VABI sceNpCppWebApiUnknown23(void) { + NotImplemented_nid_no_patch("cqOxjL0ZfFA"); + return 0; +} + +APS5_EXPORT("d1eEjWR60wk", sceNpCppWebApiUnknown24); +int APS5_VABI sceNpCppWebApiUnknown24(void) { + NotImplemented_nid_no_patch("d1eEjWR60wk"); + return 0; +} + +APS5_EXPORT("epwr+cBCIFs", sceNpCppWebApiUnknown25); +int APS5_VABI sceNpCppWebApiUnknown25(void) { + NotImplemented_nid_no_patch("epwr+cBCIFs"); + return 0; +} + +APS5_EXPORT("eruduJ0KrT0", sceNpCppWebApiUnknown26); +int APS5_VABI sceNpCppWebApiUnknown26(void) { + NotImplemented_nid_no_patch("eruduJ0KrT0"); + return 0; +} + +APS5_EXPORT("fG06a38iao8", sceNpCppWebApiUnknown27); +int APS5_VABI sceNpCppWebApiUnknown27(void) { + NotImplemented_nid_no_patch("fG06a38iao8"); + return 0; +} + +APS5_EXPORT("jfpSx14AeLc", sceNpCppWebApiUnknown28); +int APS5_VABI sceNpCppWebApiUnknown28(void) { + NotImplemented_nid_no_patch("jfpSx14AeLc"); + return 0; +} + +APS5_EXPORT("kBxwE4YfbIM", sceNpCppWebApiUnknown29); +int APS5_VABI sceNpCppWebApiUnknown29(void) { + NotImplemented_nid_no_patch("kBxwE4YfbIM"); + return 0; +} + +APS5_EXPORT("kz2Z38yTLq0", sceNpCppWebApiUnknown30); +int APS5_VABI sceNpCppWebApiUnknown30(void) { + NotImplemented_nid_no_patch("kz2Z38yTLq0"); + return 0; +} + +APS5_EXPORT("lB8driFKaoU", sceNpCppWebApiUnknown31); +int APS5_VABI sceNpCppWebApiUnknown31(void) { + NotImplemented_nid_no_patch("lB8driFKaoU"); + return 0; +} + +APS5_EXPORT("mCsV7izOkjY", sceNpCppWebApiUnknown32); +int APS5_VABI sceNpCppWebApiUnknown32(void) { + NotImplemented_nid_no_patch("mCsV7izOkjY"); + return 0; +} + +APS5_EXPORT("n1yCLMxpseQ", sceNpCppWebApiUnknown33); +int APS5_VABI sceNpCppWebApiUnknown33(void) { + NotImplemented_nid_no_patch("n1yCLMxpseQ"); + return 0; +} + +APS5_EXPORT("nL9vSVq-29s", sceNpCppWebApiUnknown34); +int APS5_VABI sceNpCppWebApiUnknown34(void) { + NotImplemented_nid_no_patch("nL9vSVq-29s"); + return 0; +} + +APS5_EXPORT("nmz5JYKcMfY", sceNpCppWebApiUnknown35); +int APS5_VABI sceNpCppWebApiUnknown35(void) { + NotImplemented_nid_no_patch("nmz5JYKcMfY"); + return 0; +} + +APS5_EXPORT("oHRl7a+zdMU", sceNpCppWebApiUnknown36); +int APS5_VABI sceNpCppWebApiUnknown36(void) { + NotImplemented_nid_no_patch("oHRl7a+zdMU"); + return 0; +} + +APS5_EXPORT("pSpO3hPNv64", sceNpCppWebApiUnknown37); +int APS5_VABI sceNpCppWebApiUnknown37(void) { + NotImplemented_nid_no_patch("pSpO3hPNv64"); + return 0; +} + +APS5_EXPORT("q4UFICay6Hk", sceNpCppWebApiUnknown38); +int APS5_VABI sceNpCppWebApiUnknown38(void) { + NotImplemented_nid_no_patch("q4UFICay6Hk"); + return 0; +} + +APS5_EXPORT("qiD2PU2Jstc", sceNpCppWebApiUnknown39); +int APS5_VABI sceNpCppWebApiUnknown39(void) { + NotImplemented_nid_no_patch("qiD2PU2Jstc"); + return 0; +} + +APS5_EXPORT("tFe964qzGEM", sceNpCppWebApiUnknown40); +int APS5_VABI sceNpCppWebApiUnknown40(void) { + NotImplemented_nid_no_patch("tFe964qzGEM"); + return 0; +} + +APS5_EXPORT("tHaO36kincQ", sceNpCppWebApiUnknown41); +int APS5_VABI sceNpCppWebApiUnknown41(void) { + NotImplemented_nid_no_patch("tHaO36kincQ"); + return 0; +} + +APS5_EXPORT("umm5m+mXiZs", sceNpCppWebApiUnknown42); +int APS5_VABI sceNpCppWebApiUnknown42(void) { + NotImplemented_nid_no_patch("umm5m+mXiZs"); + return 0; +} + +APS5_EXPORT("uwhztB49KOQ", sceNpCppWebApiUnknown43); +int APS5_VABI sceNpCppWebApiUnknown43(void) { + NotImplemented_nid_no_patch("uwhztB49KOQ"); + return 0; +} + +APS5_EXPORT("w4K4nTYwhVE", sceNpCppWebApiUnknown44); +int APS5_VABI sceNpCppWebApiUnknown44(void) { + NotImplemented_nid_no_patch("w4K4nTYwhVE"); + return 0; +} + +APS5_EXPORT("wFcm6bgWB9Q", sceNpCppWebApiUnknown45); +int APS5_VABI sceNpCppWebApiUnknown45(void) { + NotImplemented_nid_no_patch("wFcm6bgWB9Q"); + return 0; +} + +APS5_EXPORT("wp8+c84G5Xw", sceNpCppWebApiUnknown46); +int APS5_VABI sceNpCppWebApiUnknown46(void) { + NotImplemented_nid_no_patch("wp8+c84G5Xw"); + return 0; +} + +APS5_EXPORT("zjqTmP0ST9A", sceNpCppWebApiUnknown47); +int APS5_VABI sceNpCppWebApiUnknown47(void) { + NotImplemented_nid_no_patch("zjqTmP0ST9A"); + return 0; +} + +APS5_EXPORT("zzO8ZGJ74ng", sceNpCppWebApiUnknown48); +int APS5_VABI sceNpCppWebApiUnknown48(void) { + NotImplemented_nid_no_patch("zzO8ZGJ74ng"); + return 0; +} + +APS5_EXPORT("12wY179+CE8", sceNpCppWebApiUnknown49); +int APS5_VABI sceNpCppWebApiUnknown49(void) { + NotImplemented_nid_no_patch("12wY179+CE8"); + return 0; +} + +APS5_EXPORT("3bjEFf8OhTs", sceNpCppWebApiUnknown50); +int APS5_VABI sceNpCppWebApiUnknown50(void) { + NotImplemented_nid_no_patch("3bjEFf8OhTs"); + return 0; +} + +APS5_EXPORT("6FK4IOnANkc", sceNpCppWebApiUnknown51); +int APS5_VABI sceNpCppWebApiUnknown51(void) { + NotImplemented_nid_no_patch("6FK4IOnANkc"); + return 0; +} + +APS5_EXPORT("EJAOHYWUki4", sceNpCppWebApiUnknown52); +int APS5_VABI sceNpCppWebApiUnknown52(void) { + NotImplemented_nid_no_patch("EJAOHYWUki4"); + return 0; +} + +APS5_EXPORT("FlagLhjAEmc", sceNpCppWebApiUnknown53); +int APS5_VABI sceNpCppWebApiUnknown53(void) { + NotImplemented_nid_no_patch("FlagLhjAEmc"); + return 0; +} + +APS5_EXPORT("G3h-NDHnyW4", sceNpCppWebApiUnknown54); +int APS5_VABI sceNpCppWebApiUnknown54(void) { + NotImplemented_nid_no_patch("G3h-NDHnyW4"); + return 0; +} + +APS5_EXPORT("H0XjNhflED4", sceNpCppWebApiUnknown55); +int APS5_VABI sceNpCppWebApiUnknown55(void) { + NotImplemented_nid_no_patch("H0XjNhflED4"); + return 0; +} + +APS5_EXPORT("IbWx007Acn4", sceNpCppWebApiUnknown56); +int APS5_VABI sceNpCppWebApiUnknown56(void) { + NotImplemented_nid_no_patch("IbWx007Acn4"); + return 0; +} + +APS5_EXPORT("Is+nI7Hq9jU", sceNpCppWebApiUnknown57); +int APS5_VABI sceNpCppWebApiUnknown57(void) { + NotImplemented_nid_no_patch("Is+nI7Hq9jU"); + return 0; +} + +APS5_EXPORT("Kc4x2uy0FFk", sceNpCppWebApiUnknown58); +int APS5_VABI sceNpCppWebApiUnknown58(void) { + NotImplemented_nid_no_patch("Kc4x2uy0FFk"); + return 0; +} + +APS5_EXPORT("OK+Ggpve7B0", sceNpCppWebApiUnknown59); +int APS5_VABI sceNpCppWebApiUnknown59(void) { + NotImplemented_nid_no_patch("OK+Ggpve7B0"); + return 0; +} + +APS5_EXPORT("P3daBLFFREw", sceNpCppWebApiUnknown60); +int APS5_VABI sceNpCppWebApiUnknown60(void) { + NotImplemented_nid_no_patch("P3daBLFFREw"); + return 0; +} + +APS5_EXPORT("RZcseJ3THfY", sceNpCppWebApiUnknown61); +int APS5_VABI sceNpCppWebApiUnknown61(void) { + NotImplemented_nid_no_patch("RZcseJ3THfY"); + return 0; +} + +APS5_EXPORT("RbvteD35X-8", sceNpCppWebApiUnknown62); +int APS5_VABI sceNpCppWebApiUnknown62(void) { + NotImplemented_nid_no_patch("RbvteD35X-8"); + return 0; +} + +APS5_EXPORT("XCtf+23QT0k", sceNpCppWebApiUnknown63); +int APS5_VABI sceNpCppWebApiUnknown63(void) { + NotImplemented_nid_no_patch("XCtf+23QT0k"); + return 0; +} + +APS5_EXPORT("a4q15LI1a4E", sceNpCppWebApiUnknown64); +int APS5_VABI sceNpCppWebApiUnknown64(void) { + NotImplemented_nid_no_patch("a4q15LI1a4E"); + return 0; +} + +APS5_EXPORT("eT4TQB7OsLA", sceNpCppWebApiUnknown65); +int APS5_VABI sceNpCppWebApiUnknown65(void) { + NotImplemented_nid_no_patch("eT4TQB7OsLA"); + return 0; +} + +APS5_EXPORT("g2dEqFnhFuw", sceNpCppWebApiUnknown66); +int APS5_VABI sceNpCppWebApiUnknown66(void) { + NotImplemented_nid_no_patch("g2dEqFnhFuw"); + return 0; +} + +APS5_EXPORT("lPAMjFZEpt8", sceNpCppWebApiUnknown67); +int APS5_VABI sceNpCppWebApiUnknown67(void) { + NotImplemented_nid_no_patch("lPAMjFZEpt8"); + return 0; +} + +APS5_EXPORT("mBagn+lW-iM", sceNpCppWebApiUnknown68); +int APS5_VABI sceNpCppWebApiUnknown68(void) { + NotImplemented_nid_no_patch("mBagn+lW-iM"); + return 0; +} + +APS5_EXPORT("mkWsKEh0h0o", sceNpCppWebApiUnknown69); +int APS5_VABI sceNpCppWebApiUnknown69(void) { + NotImplemented_nid_no_patch("mkWsKEh0h0o"); + return 0; +} + +APS5_EXPORT("nE0ooeCMRm8", sceNpCppWebApiUnknown70); +int APS5_VABI sceNpCppWebApiUnknown70(void) { + NotImplemented_nid_no_patch("nE0ooeCMRm8"); + return 0; +} + +APS5_EXPORT("qf2I9BlKXis", sceNpCppWebApiUnknown71); +int APS5_VABI sceNpCppWebApiUnknown71(void) { + NotImplemented_nid_no_patch("qf2I9BlKXis"); + return 0; +} + +APS5_EXPORT("xwy1I52dE8o", sceNpCppWebApiUnknown72); +int APS5_VABI sceNpCppWebApiUnknown72(void) { + NotImplemented_nid_no_patch("xwy1I52dE8o"); + return 0; +} +} diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp new file mode 100644 index 000000000000..99f3f83152fd --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -0,0 +1,321 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +using Deleter = void (APS5_VABI*)(void*); + +struct IntrusivePtr { + void* object; + Deleter deleter; + void* libContext; +}; + +using Construct = void (APS5_VABI*)(IntrusivePtr*); +using Copy = void (APS5_VABI*)(IntrusivePtr*, const IntrusivePtr*); +using Destroy = void (APS5_VABI*)(IntrusivePtr*); +using Access = void* (APS5_VABI*)(const IntrusivePtr*); + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr*); + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr*, void*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr*, const IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr*, void*, Deleter, void*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static constexpr std::size_t ObjectSize = 0x40; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +struct DestroyCase { + Destroy destroy; + std::size_t referenceCountOffset; +}; + +static void* deletedObject = nullptr; +static int deleterCalls = 0; + +static void APS5_VABI RecordDelete(void* object) { + deletedObject = object; + ++deleterCalls; +} + +static std::int32_t ReadCount(const unsigned char* object, const std::size_t offset) { + std::int32_t value; + std::memcpy(&value, object + offset, sizeof(value)); + return value; +} + +static void WriteCount(unsigned char* object, const std::size_t offset, const std::int32_t value) { + std::memcpy(object + offset, &value, sizeof(value)); +} + +static bool OnlyCountChanged(const unsigned char* object, const std::size_t offset) { + for (std::size_t index = 0; index < ObjectSize; ++index) { + if (index >= offset && index < offset + sizeof(std::int32_t)) continue; + if (object[index] != 0x5a) return false; + } + return true; +} + +static void TestConstruct(const Construct construct) { + IntrusivePtr pointer; + std::memset(&pointer, 0xa5, sizeof(pointer)); + construct(&pointer); + Require(pointer.object == nullptr); + Require(pointer.deleter == nullptr); + Require(pointer.libContext == nullptr); +} + +static void TestAccess(const Access access) { + int object = 0; + int context = 0; + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + Require(access(&empty) == nullptr); + const IntrusivePtr filled{&object, nullptr, nullptr}; + Require(access(&filled) == &object); +} + +static void TestCopy(const Copy copy) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + int context = 0; + + const IntrusivePtr source{object, RecordDelete, &context}; + IntrusivePtr target; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &source); + Require(target.object == object); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); + Require(source.object == object); + Require(ReadCount(object, 0) == 2); + Require(OnlyCountChanged(object, 0)); + + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &empty); + Require(target.object == nullptr); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); +} + +static void TestDestroy(const DestroyCase& testCase) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + int context = 0; + const std::size_t offset = testCase.referenceCountOffset; + + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr empty{nullptr, RecordDelete, &context}; + testCase.destroy(&empty); + Require(empty.object == nullptr); + Require(empty.deleter == RecordDelete); + Require(empty.libContext == &context); + Require(deleterCalls == 0); + + WriteCount(object, offset, 2); + IntrusivePtr shared{object, RecordDelete, &context}; + testCase.destroy(&shared); + Require(ReadCount(object, offset) == 1); + Require(shared.object == object); + Require(shared.deleter == RecordDelete); + Require(shared.libContext == &context); + Require(deleterCalls == 0); + + IntrusivePtr last{object, RecordDelete, &context}; + testCase.destroy(&last); + Require(ReadCount(object, offset) == 0); + Require(OnlyCountChanged(object, offset)); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(last.object == nullptr); + Require(last.deleter == nullptr); + Require(last.libContext == &context); + + WriteCount(object, offset, 1); + IntrusivePtr pooled{object, nullptr, &context}; + bool thrown = false; + try { + testCase.destroy(&pooled); + } catch (const std::runtime_error&) { + thrown = true; + } + Require(thrown); + Require(deleterCalls == 1); +} + +static void TestSharedOwnership() { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr owner; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(&owner); + owner.object = object; + owner.deleter = RecordDelete; + + IntrusivePtr argument; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(&argument, &owner); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&argument) == object); + Require(ReadCount(object, 0) == 2); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&argument); + Require(ReadCount(object, 0) == 1); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&owner); + Require(ReadCount(object, 0) == 0); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&owner) == nullptr); +} + +static void TestVectorPushBackReleasesElements() { + int context = 0; + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(&vector, &context); + Require(vector.object == nullptr); + Require(vector.deleter == nullptr); + Require(vector.libContext == &context); + + const IntrusivePtr empty{nullptr, nullptr, &context}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &empty) == 0); + Require(vector.object != nullptr); + Require(vector.deleter != nullptr); + + IntrusivePtr filled{object, RecordDelete, &context}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &filled) == 0); + Require(ReadCount(object, 0) == 2); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(&vector); + Require(vector.object == nullptr); + Require(vector.deleter == nullptr); + Require(ReadCount(object, 0) == 1); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(&filled); + Require(ReadCount(object, 0) == 0); + Require(deleterCalls == 1); + Require(deletedObject == object); +} + +static void TestAdoptedVectorBodyDestroyedThroughDeleter() { + int context = 0; + alignas(8) unsigned char body[ObjectSize]; + std::memset(body, 0x5a, sizeof(body)); + WriteCount(body, VectorReferenceCountOffset, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(&vector, body, RecordDelete, &context); + Require(vector.object == body); + Require(vector.deleter == RecordDelete); + Require(vector.libContext == &context); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(&vector); + Require(ReadCount(body, VectorReferenceCountOffset) == 0); + Require(deleterCalls == 1); + Require(deletedObject == body); + Require(vector.object == nullptr); +} + +int main() { + Require(sizeof(IntrusivePtr) == 0x18); + + const Construct constructors[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev, + }; + for (const auto construct : constructors) TestConstruct(construct); + + const Access accessors[] = { + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv, + }; + for (const auto access : accessors) TestAccess(access); + + const Copy copies[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_, + }; + for (const auto copy : copies) TestCopy(copy); + + const DestroyCase destructors[] = { + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev, VectorReferenceCountOffset}, + }; + for (const auto& testCase : destructors) TestDestroy(testCase); + + TestSharedOwnership(); + TestVectorPushBackReleasesElements(); + TestAdoptedVectorBodyDestroyedThroughDeleter(); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp new file mode 100644 index 000000000000..18a4dd25a621 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -0,0 +1,191 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(void* self, const char* text); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(void* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(void* self, const void* element); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::size_t kStringSize = 0x20; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kBufferOffset = 0x08; +constexpr std::size_t kBufferSizeOffset = 0x10; +constexpr std::size_t kLibContextOffset = 0x18; +constexpr unsigned char kFill = 0xA5; + +constexpr std::size_t kVectorSize = 0x18; +constexpr std::size_t kObjectOffset = 0x00; +constexpr std::size_t kBodyMagicOffset = 0x00; +constexpr std::size_t kBodyReferenceCountOffset = 0x30; +constexpr std::size_t kBodyElementKindOffset = 0x34; +constexpr std::size_t kBodyElementsOffset = 0x38; +constexpr std::size_t kBodySizeOffset = 0x40; + +struct Storage { + alignas(8) unsigned char bytes[kStringSize + kGuardSize]; +}; + +struct VectorStorage { + alignas(8) unsigned char bytes[kVectorSize + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +template +void Store(Storage& storage, std::size_t offset, TValue value) { + std::memcpy(storage.bytes + offset, &value, sizeof(value)); +} + +template +TValue LoadBody(const void* body, std::size_t offset) { + TValue value; + std::memcpy(&value, static_cast(body) + offset, sizeof(value)); + return value; +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t i = kStringSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +bool VectorGuardIntact(const VectorStorage& storage) { + for (std::size_t i = kVectorSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +void TestConstructedStringIsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(Load(storage, kLibContextOffset) == &libContext); + Require(GuardIntact(storage)); + + const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes); + Require(text != nullptr && text[0] == '\0'); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(GuardIntact(storage)); +} + +void TestNullLibContextIsStored() { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, nullptr); + + Require(Load(storage, kLibContextOffset) == nullptr); + Require(Load(storage, kBufferOffset) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); +} + +void TestAppendBuildsAndClearsBuffer() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, "rank") == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes), "rank") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, "ing") == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes), "ranking") == 0); + Require(Load(storage, kBufferOffset) != nullptr); + Require(GuardIntact(storage)); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(GuardIntact(storage)); +} + +void TestAppendNullKeepsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, nullptr) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); +} + +void TestVectorPushBackCopiesStringContent() { + std::uint64_t libContext = 1; + VectorStorage vector; + Storage element; + std::memset(vector.bytes, kFill, sizeof(vector.bytes)); + std::memset(element.bytes, kFill, sizeof(element.bytes)); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(vector.bytes, &libContext); + Require(VectorGuardIntact(vector)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(element.bytes, &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(element.bytes, "privilege") == 0); + char* elementBuffer = Load(element, kBufferOffset); + Require(elementBuffer != nullptr); + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(vector.bytes, element.bytes) == 0); + + void* body; + std::memcpy(&body, vector.bytes + kObjectOffset, sizeof(body)); + Require(body != nullptr); + Require(LoadBody(body, kBodyReferenceCountOffset) == 1); + Require(LoadBody(body, kBodyElementKindOffset) == 2); + Require(LoadBody(body, kBodySizeOffset) == 1); + + void* elements = LoadBody(body, kBodyElementsOffset); + char* storedBuffer; + std::memcpy(&storedBuffer, static_cast(elements) + kBufferOffset, sizeof(storedBuffer)); + Require(storedBuffer != nullptr); + Require(storedBuffer != elementBuffer); + Require(std::strcmp(storedBuffer, "privilege") == 0); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(element.bytes); + Require(std::strcmp(storedBuffer, "privilege") == 0); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(vector.bytes); + Require(VectorGuardIntact(vector)); +} + +} + +int main() { + TestConstructedStringIsEmpty(); + TestNullLibContextIsStored(); + TestAppendBuildsAndClearsBuffer(); + TestAppendNullKeepsEmpty(); + TestVectorPushBackCopiesStringContent(); +} diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md new file mode 100644 index 000000000000..bdcbf5fe4ac3 --- /dev/null +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -0,0 +1,8 @@ +# libSceNpCppWebApi technical debt + +### Unknown function info + +- [sce::Np::CppWebApi::Common::IntrusivePtr](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented +- [sce::Np::CppWebApi::Common::String](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. `append` grows the buffer with the host allocator and the destructor frees it, instead of going through the `LibContext` allocator. `pushBack` into `Vector` stores a deep copy, so a destroyed temporary cannot invalidate the stored element; a title that byte-copies a `String` with a live buffer would still double-free, as nothing increments the reference count +- [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `operator[]` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one +- [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_UNAVAILABLE`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object From 2ba0d6671cce48738e231fa3f724517fa9138f60 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sat, 17 Oct 2026 05:14:12 +0000 Subject: [PATCH 076/176] fix(libkernel): keep the cached host stack range in two scalar thread-locals HostStackLimits cached the queried stack range in one thread_local std::pair. GCC gives that 16-byte object 16-byte alignment and stores it with movaps. With CI's wild 0.10.0 link, libkernel.prx's PT_TLS segment starts at an address that is 8 mod 16 (p_align 16), so the pair sits at TLS offset 0x38. When libkernel.prx is loaded by dlopen its TLS block is allocated dynamically and the pair ends up 8 bytes off a 16-byte boundary. The first store then faults: guest_memory_lifetime's child, which loads libkernel through its module and calls DoMprotect after static destruction, died with SIGSEGV in HostStackLimits. Two 8-byte thread-locals carry no 16-byte alignment assumption, so the cache works with either linker. (cherry picked from commit fabc89ad6a06ab846f5f3f96197db36fb3f06dc3) --- .../libs/prx/libkernel/Pthread/src/Thread.cpp | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 0f6b251a9b38..3cdc574104f7 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -188,17 +188,23 @@ static bool HostStackLimits(std::uintptr_t* low, std::uintptr_t* high) { *high = reinterpret_cast(pthread_get_stackaddr_np(self)); *low = *high - pthread_get_stacksize_np(self); #else - pthread_attr_t attr; - if (pthread_getattr_np(pthread_self(), &attr) != 0) - return false; - void* address = nullptr; - std::size_t size = 0; - const bool queried = pthread_attr_getstack(&attr, &address, &size) == 0; - pthread_attr_destroy(&attr); - if (!queried) - return false; - *low = reinterpret_cast(address); - *high = *low + size; + static thread_local std::uintptr_t stackLow = 0; + static thread_local std::uintptr_t stackHigh = 0; + if (stackHigh == 0) { + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) != 0) + return false; + void* address = nullptr; + std::size_t size = 0; + const bool queried = pthread_attr_getstack(&attr, &address, &size) == 0; + pthread_attr_destroy(&attr); + if (!queried || size == 0) + return false; + stackLow = reinterpret_cast(address); + stackHigh = stackLow + size; + } + *low = stackLow; + *high = stackHigh; #endif return *high > *low; } From b4a8c137d8ba5ce3763e55cba584491ced6056d8 Mon Sep 17 00:00:00 2001 From: sebastian Date: Fri, 16 Oct 2026 15:59:24 +0200 Subject: [PATCH 077/176] feat(ajm): resample interleaved s16 PCM through codec 23 Broken Sword - Shadow of the Templars: Reforged (PPSA21480) decodes its audio itself and uses AJM codec 23 only to resample interleaved s16 PCM from 44.1 to 48 kHz: every block initializes the instance with 0, 2, 44100, sets the ratio 0.91875 and decodes 235 frames into a 256-frame output. The run threw "AJM: decoding codec 23 is not implemented". A codec 23 run now consumes its whole input and emits every output frame whose position lies inside it, with the Catmull-Rom of the existing resampler. Cases the title does not show throw, among them a run after a resampled run without a new initialize. (cherry picked from commit 3f5f84996a5f9c0589dcbff86e1429a33db1fde2) --- core/libs/prx/libSceAjm.native/src/Ajm.cpp | 74 +++++++++++++- core/libs/tests/GuestAjm.cpp | 107 +++++++++++++++++++++ docs/dev/debt/libSceAjm.native.md | 2 +- 3 files changed, 178 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 105587e0aef9..10d7437a9a3d 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -46,6 +46,7 @@ constexpr std::int32_t AJM_RESULT_FATAL = static_cast(0x80000000u) constexpr std::uint32_t CODEC_MP3 = 0; constexpr std::uint32_t CODEC_AT9 = 1; constexpr std::uint32_t CODEC_M4AAC = 2; +constexpr std::uint32_t CODEC_PCM = 23; constexpr std::uint32_t CODEC_OPUS = 24; constexpr std::uint32_t M4AAC_CONFIG_ADTS = 1; @@ -162,6 +163,9 @@ struct Instance { std::uint32_t aacFrameSamples = 0; std::uint32_t aacDelayFrames = 0; bool aacHeaac = false; + std::uint32_t pcmChannels = 0; + std::uint32_t pcmSampleRate = 0; + bool pcmResampled = false; Resampler resampler; ~Instance() { @@ -444,8 +448,25 @@ void ClearResampler(Instance& instance) { instance.resampler.position = 1.0; } +std::int32_t InitializePcm(Instance& instance, const std::uint8_t* parameters, std::uint64_t size) { + if (size != 12) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (codec 23 parameters other than 12 bytes)"); + std::uint32_t words[3] = {}; + std::memcpy(words, parameters, sizeof(words)); + if (words[0] != 0) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (codec 23 input format other than 0, interleaved s16)"); + if (words[1] == 0 || words[1] > 8 || words[2] == 0) return AJM_RESULT_INVALID_PARAMETER; + instance.pcmChannels = words[1]; + instance.pcmSampleRate = words[2]; + instance.pcmResampled = false; + instance.initialized = true; + instance.totalDecodedSamples = 0; + instance.segmentSamples = 0; + instance.gapless = {}; + return 0; +} + std::int32_t InitializeInstance(Instance& instance, const std::uint8_t* parameters, std::uint64_t size) { ClearResampler(instance); + if (instance.codec == CODEC_PCM) return InitializePcm(instance, parameters, size); if (instance.codec == CODEC_OPUS) { if (size < 8) return AJM_RESULT_INVALID_PARAMETER; std::uint32_t channels = 0; @@ -616,6 +637,10 @@ void ResamplerPush(Instance& instance, const double* samples, std::size_t frames stored.insert(stored.end(), samples, samples + frames * channels); } +double CatmullRom(double p0, double p1, double p2, double p3, double t) { + return p1 + 0.5 * t * (p2 - p0 + t * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + t * (3.0 * (p1 - p2) + p3 - p0))); +} + void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t channels, std::uint32_t encoding, std::size_t sampleBytes) { auto& resampler = instance.resampler; if (resampler.frames.empty() || channels == 0) return; @@ -631,8 +656,7 @@ void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t chann const double p1 = resampler.frames[index * channels + channel]; const double p2 = resampler.frames[(index + 1) * channels + channel]; const double p3 = resampler.frames[(index + 2) * channels + channel]; - const double value = p1 + 0.5 * t * (p2 - p0 + t * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + t * (3.0 * (p1 - p2) + p3 - p0))); - StorePcm(value, frame.data() + channel * sampleBytes, encoding); + StorePcm(CatmullRom(p0, p1, p2, p3, t), frame.data() + channel * sampleBytes, encoding); } outputs.Emit(frame.data(), frameBytes); resampler.position += resampler.ratio; @@ -679,6 +703,7 @@ SidebandFormat CurrentFormat(const Instance& instance) { case CODEC_MP3: return {instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, PcmEncoding(instance), instance.mp3Bitrate, 0}; case CODEC_OPUS: return {instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, PcmEncoding(instance), 0, 0}; case CODEC_M4AAC: return {instance.aacChannels, ChannelMask(instance.aacChannels), instance.aacSampleRate, PcmEncoding(instance), instance.aacBitrate, 0}; + case CODEC_PCM: return {instance.pcmChannels, ChannelMask(instance.pcmChannels), instance.pcmSampleRate, PcmEncoding(instance), 0, 0}; default: throw std::runtime_error("AJM: the format of codec " + std::to_string(instance.codec) + " is not implemented"); } } @@ -1099,6 +1124,44 @@ std::size_t OpusPacketSamples(const std::uint8_t* packet, std::size_t size) { return frames * frameSamples; } +void RunPcm(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { + if (job.flags & ~SIDEBAND_STREAM) NotImplemented_nid_no_patch("AJM codec 23 run with flags other than SIDEBAND_STREAM"); + const auto encoding = PcmEncoding(instance); + if (encoding > 2) throw std::runtime_error("AJM codec 23: PCM encoding " + std::to_string(encoding) + " is not implemented"); + if (instance.resampler.change != 0.0f) NotImplemented_nid_no_patch("AJM codec 23 run with a resample ratio change per sample"); + if (instance.gapless.totalSamples != 0 || instance.gapless.skipSamples != 0) NotImplemented_nid_no_patch("AJM codec 23 run with gapless decode counts"); + if (instance.pcmResampled) NotImplemented_nid_no_patch("AJM codec 23 run after a resampled run without an initialize or context clear in between"); + const JobInput input(job, inputs); + PcmOutputs pcmOutputs(outputs, job.outputCount); + const std::size_t channels = instance.pcmChannels; + const std::size_t inputFrameBytes = channels * sizeof(std::int16_t); + const std::size_t sampleBytes = encoding == 0 ? sizeof(std::int16_t) : sizeof(std::int32_t); + if (input.empty()) NotImplemented_nid_no_patch("AJM codec 23 run with an empty input"); + if (input.size() % inputFrameBytes != 0) NotImplemented_nid_no_patch("AJM codec 23 run with an input that is not a whole number of frames"); + const std::size_t frames = input.size() / inputFrameBytes; + const double ratio = instance.resampler.ratio; + const std::size_t room = pcmOutputs.Room() / (channels * sampleBytes); + std::size_t outputFrames = 0; + while (outputFrames < room && static_cast(outputFrames) * ratio < static_cast(frames)) ++outputFrames; + if (static_cast(outputFrames) * ratio < static_cast(frames)) NotImplemented_nid_no_patch("AJM codec 23 run whose output cannot hold the whole resampled input"); + const auto sample = [&](std::size_t index, std::size_t channel) { return PcmValue(input.data() + (std::min(index, frames - 1) * channels + channel) * sizeof(std::int16_t), 0); }; + std::vector frame(channels * sampleBytes); + for (std::size_t output = 0; output < outputFrames; ++output) { + const double position = static_cast(output) * ratio; + const auto index = static_cast(position); + const double t = position - static_cast(index); + for (std::size_t channel = 0; channel < channels; ++channel) { + const double value = CatmullRom(sample(index == 0 ? 0 : index - 1, channel), sample(index, channel), sample(index + 1, channel), sample(index + 2, channel), t); + StorePcm(value, frame.data() + channel * sampleBytes, encoding); + } + pcmOutputs.Emit(frame.data(), frame.size()); + } + if (ratio != 1.0) instance.pcmResampled = true; + instance.totalDecodedSamples += frames; + AJM_TRACE("[ajm] instance %u pcm run flags 0x%llx: %zu input bytes consumed, %zu output bytes, ratio %f -> produced %zu, %zu channels, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, ratio, pcmOutputs.produced, channels, static_cast(instance.totalDecodedSamples)); + WriteRunSideband(job, instance, 0, input.size(), pcmOutputs.produced, 1, CurrentFormat(instance)); +} + void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { const JobInput input(job, inputs); PcmOutputs pcmOutputs(outputs, job.outputCount); @@ -1424,6 +1487,7 @@ void ClearContext(Instance& instance) { instance.aacDelayFrames = AacDelayFrames(instance); } instance.opusPending.clear(); + instance.pcmResampled = false; ClearResampler(instance); } @@ -1461,7 +1525,7 @@ void Control(Instance& instance, const JobHeader& job, const AjmBuffer* inputs) } std::size_t HeldSamples(const Instance& instance) { - const std::size_t channels = instance.codec == CODEC_OPUS ? instance.opusChannels : instance.codec == CODEC_MP3 ? instance.mp3Channels : instance.codec == CODEC_M4AAC ? instance.aacChannels : static_cast(std::max(0, instance.info.channels)); + const std::size_t channels = instance.codec == CODEC_PCM ? instance.pcmChannels : instance.codec == CODEC_OPUS ? instance.opusChannels : instance.codec == CODEC_MP3 ? instance.mp3Channels : instance.codec == CODEC_M4AAC ? instance.aacChannels : static_cast(std::max(0, instance.info.channels)); if (Resampling(instance)) return ResamplerHeld(instance, channels); const std::size_t sampleBytes = PcmEncoding(instance) == 0 ? sizeof(std::int16_t) : sizeof(std::int32_t); return channels == 0 ? 0 : instance.opusPending.size() / (channels * sampleBytes); @@ -1527,7 +1591,7 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out if (!instance->initialized) { AJM_TRACE("[ajm] instance %u run before initialize\n", job.instance); WriteResult(job.sideband, job.sidebandSize, AJM_RESULT_NOT_INITIALIZED); - } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_M4AAC && instance->codec != CODEC_OPUS) { + } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_M4AAC && instance->codec != CODEC_PCM && instance->codec != CODEC_OPUS) { throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); } else if ((job.flags & RUN_GET_CODEC_INFO) && (job.flags & RUN_MULTIPLE_FRAMES)) { NotImplemented_nid_no_patch("AJM run job with both RUN_GET_CODEC_INFO and RUN_MULTIPLE_FRAMES (sideband order)"); @@ -1542,6 +1606,8 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out RunMp3(*instance, job, inputs, outputs); } else if (instance->codec == CODEC_M4AAC) { RunAac(*instance, job, inputs, outputs); + } else if (instance->codec == CODEC_PCM) { + RunPcm(*instance, job, inputs, outputs); } else { RunOpus(*instance, job, inputs, outputs); } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index c1970c1f2d45..2ed644c11c0a 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -1584,6 +1584,112 @@ void TestOpusGaplessReset(std::uint32_t context, std::size_t outputFrames, float Require(sceAjmInstanceDestroy(context, reference) == 0); } +struct PcmResults { + std::int64_t initialize[2] = {-1, -1}; + std::int64_t resample[2] = {-1, -1}; + DecodeSideband decode{}; +}; + +AjmBatchInfo PcmJobs(std::vector& batch, std::uint32_t instance, const std::uint32_t* parameters, float ratio, const std::vector& input, std::vector& output, PcmResults& results) { + AjmBatchInfo info{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + if (parameters) Require(sceAjmBatchJobInitialize(&info, instance, parameters, 3 * sizeof(std::uint32_t), results.initialize) == 0); + if (ratio > 0) Require(sceAjmBatchJobSetResampleParameters(&info, instance, ratio, 1, results.resample) == 0); + Require(sceAjmBatchJobDecode(&info, instance, input.data(), input.size() * sizeof(std::int16_t), output.data(), output.size() * sizeof(std::int16_t), &results.decode) == 0); + return info; +} + +void TestPcmResample(std::uint32_t context) { + constexpr std::uint64_t flags = 0x1002; + constexpr std::int16_t untouched = 0x7FFF; + const std::uint32_t parameters[3] = {0, 2, 44100}; + constexpr std::size_t inputFrames = 235; + constexpr float ratio = 0.91875f; + std::vector input(inputFrames * 2); + for (std::size_t frame = 0; frame < inputFrames; ++frame) { + input[frame * 2] = static_cast(frame * 100); + input[frame * 2 + 1] = static_cast((static_cast(frame * 7919 % 2001) - 1000) * 20); + } + const auto at = [&](std::ptrdiff_t frame, std::size_t channel) { + const auto clamped = std::clamp(frame, 0, static_cast(inputFrames) - 1); + return static_cast(input[static_cast(clamped) * 2 + channel]); + }; + std::vector batch(4096); + + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 23, flags, &instance) == 0); + for (int block = 0; block < 2; ++block) { + std::vector output(300 * 2, untouched); + PcmResults results; + Submit(context, PcmJobs(batch, instance, parameters, ratio, input, output, results)); + Require(results.initialize[0] == 0 && results.resample[0] == 0 && results.decode.result == 0); + Require(results.decode.inputConsumed == 940 && results.decode.outputWritten == 1024 && results.decode.totalDecodedSamples == 235); + for (std::size_t frame = 0; frame < 256; ++frame) { + const double position = static_cast(frame) * static_cast(ratio); + const auto index = static_cast(position); + const double t = position - static_cast(index); + for (std::size_t channel = 0; channel < 2; ++channel) { + const double p0 = at(index - 1, channel), p1 = at(index, channel), p2 = at(index + 1, channel), p3 = at(index + 2, channel); + const double expected = std::clamp(p1 + 0.5 * t * (p2 - p0 + t * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + t * (3.0 * (p1 - p2) + p3 - p0))), -32768.0, 32767.0); + Require(std::fabs(output[frame * 2 + channel] - expected) <= 1.0); + } + } + Require(std::all_of(output.begin() + 256 * 2, output.end(), [&](std::int16_t value) { return value == untouched; })); + } + { + std::vector output(256 * 2); + PcmResults results; + Require(Refused(context, PcmJobs(batch, instance, nullptr, ratio, input, output, results))); + } + { + std::vector output(255 * 2); + PcmResults results; + Require(Refused(context, PcmJobs(batch, instance, parameters, ratio, input, output, results))); + } + { + const GaplessDecode gapless{100, 0, 0}; + std::int32_t gaplessResult[2] = {-1, -1}; + std::vector output(256 * 2); + PcmResults results; + AjmBatchInfo info{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobInitialize(&info, instance, parameters, sizeof(parameters), results.initialize) == 0); + Require(sceAjmBatchJobSetGaplessDecode(&info, instance, &gapless, 1, gaplessResult) == 0); + Require(sceAjmBatchJobDecode(&info, instance, input.data(), input.size() * sizeof(std::int16_t), output.data(), output.size() * sizeof(std::int16_t), &results.decode) == 0); + Require(Refused(context, info)); + } + Require(sceAjmInstanceDestroy(context, instance) == 0); + + std::uint32_t plain = 0; + Require(sceAjmInstanceCreate(context, 23, flags, &plain) == 0); + for (int run = 0; run < 2; ++run) { + std::vector output(input.size() + 4, untouched); + PcmResults results; + Submit(context, PcmJobs(batch, plain, run == 0 ? parameters : nullptr, 0, input, output, results)); + Require(results.decode.result == 0 && results.decode.inputConsumed == 940 && results.decode.outputWritten == 940); + Require(results.decode.totalDecodedSamples == 235u * static_cast(run + 1)); + Require(std::equal(input.begin(), input.end(), output.begin()) && output[input.size()] == untouched); + } + Require(sceAjmInstanceDestroy(context, plain) == 0); + + std::uint32_t invalid = 0; + Require(sceAjmInstanceCreate(context, 23, flags, &invalid) == 0); + { + const std::uint32_t noChannels[3] = {0, 0, 44100}; + std::vector output(256 * 2); + PcmResults results; + Submit(context, PcmJobs(batch, invalid, noChannels, 0, input, output, results)); + Require(results.initialize[0] == 4 && results.decode.result == 1); + } + { + const std::uint32_t otherFormat[3] = {1, 2, 44100}; + std::vector output(256 * 2); + PcmResults results; + Require(Refused(context, PcmJobs(batch, invalid, otherFormat, 0, input, output, results))); + } + Require(sceAjmInstanceDestroy(context, invalid) == 0); +} + } int main() { @@ -1641,5 +1747,6 @@ int main() { TestResampleAt9(context); TestAt9GaplessSegments(context); TestAt9RunDecodesWholeInput(context); + TestPcmResample(context); Require(sceAjmFinalize(context) == 0); } diff --git a/docs/dev/debt/libSceAjm.native.md b/docs/dev/debt/libSceAjm.native.md index bcdfde9b1e48..ca2298edef0d 100644 --- a/docs/dev/debt/libSceAjm.native.md +++ b/docs/dev/debt/libSceAjm.native.md @@ -8,7 +8,7 @@ - [sceAjmDecMp3ParseFrame](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps; the original file length (`parse_ofl`) layout (Xing/Info + LAME tag, VBRI, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout and little-endian `u16` packet byte count come from The Smurfs Dreams (`2, 48000, 0`) and Lost Soul Aside (`4, 48000, 1`). The third word is interpreted as the mapping family, as in [KytyPS5's initialize structure](https://github.com/KytyPS5/KytyPS5/blob/984cfcebd141b211aa9e0a8cde63b0ebe652d07a/src/libs/ajm/opus_decoder.h#L11-L15). Family 1 uses the conventional stream counts, coupled counts and channel map from [libopus 1.5.2's surround layout](https://github.com/xiph/opus/blob/v1.5.2/src/opus_multistream_encoder.c#L47-L55), passed to FFmpeg through an [RFC 7845 section 5.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1) header with zero pre-skip and gain; gapless skipping remains controlled by AJM sideband parameters. FFmpeg returns PCM in its native speaker order. The implicit stream layout, PCM channel order and invalid family-0 channel count result are not verified on a console. NHL 27 (PPSA34063) passes `1, 48000, 255`. Family 255 with one channel is decoded as one mono stream, the same as family 0; this is assumed, since the stream count, coupled stream count and channel mapping that a family-255 Opus header carries ([RFC 7845 section 5.1.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1.1)) are not passed. Only 48000 Hz, mapping families 0 and 1, and family 255 with one channel are implemented; other rates and families, and family 255 with more channels, throw -- [AJM decoders other than MP3, ATRAC9, M4AAC and Opus](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - decoding throws for every other codec id. Broken Sword - Shadow of the Templars: Reforged (PPSA21480) stops at codec 23 (`AJM: decoding codec 23 is not implemented`). The ids known here are MP3 0, ATRAC9 1, M4AAC 2 and Opus 24; id 23 is not among them and which codec it is, is unknown. +- [AJM codec 23](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - taken as interleaved `s16` PCM that only passes through the resampler, from Broken Sword - Shadow of the Templars: Reforged (PPSA21480); no source names a codec 23 ([shadPS4's PS4 header](https://github.com/shadps4-emu/shadPS4/blob/5aa7ffa3f4f21bba827d7865ed8f69f98f993542/src/core/libraries/ajm/ajm.h#L19-L25) ends its codec list with `Max = 23`) and the name `CODEC_PCM` is ours. The title decodes its Ogg Opus and Vorbis audio itself and, for every block, initializes the instance (flags 0x1002) with `0, 2, 44100`, sets the ratio 0.91875 (44100/48000) and decodes 235 stereo frames into a 256-frame output. The 12 initialize bytes are taken as input format (0 is `s16`, other values throw), channel count and sample rate; the rate is only reported in the format sideband; other sizes throw; zero or more than 8 channels or a zero rate return the invalid-parameter result as for Opus, not verified. A run consumes its whole input and emits every output frame whose position (output index × ratio) is below the input frame count, with the 4-point Catmull-Rom of the other codecs' resampler and the first and last input frames repeated past the edges, so each run restarts at position 0; `totalDecodedSamples` counts input frames; `s32` and float outputs are accepted although only `s16` was seen. Throwing because the console's behaviour is unknown: a run after a resampled run without an initialize or context clear in between (whether the console carries the resampler position across runs), a ratio change per sample, gapless counts, an empty input or one that is not a whole number of frames, an output that cannot hold the resampled input, run flags other than `SIDEBAND_STREAM` and initialization through `sceAjmBatchJobControl` - [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`; with a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there. - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); other flags throw - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET is assumed to clear the context as sceAjmBatchJobClearContext does From bed98082910b5ae0b37dbf23be0d731e781a309f Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:56:38 +0200 Subject: [PATCH 078/176] fix(libkernel): share guest rename semantics with sceKernelRename (cherry picked from commit de2e12148eab57c83bf80fa54b6fd53317385239) --- core/libs/prx/libkernel/File/src/Stdio.cpp | 20 ++----------- core/libs/tests/GuestFilesystem.cpp | 35 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index b1c486aa1449..f147221fd04c 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1293,24 +1293,8 @@ int64_t APS5_VABI pwritev_nid_postfix(int d, const KernelIovec* iov, int iovcnt, int APS5_VABI sceKernelRename(const char* from, const char* to) { if (!from || !to) return SceErrorFromErrno(GUEST_EFAULT); - if (*from == '\0' || *to == '\0') return SceErrorFromErrno(GUEST_ENOENT); - std::filesystem::path source; - if (const int error = ResolvePathError_nid_no_patch(from, source)) return SceKernelError(error); - std::error_code error; - const auto sourceStatus = std::filesystem::status(source, error); - if (!std::filesystem::exists(sourceStatus)) return SceErrorFromErrno(GUEST_ENOENT); - std::filesystem::path destination; - if (const int result = ResolvePathError_nid_no_patch(to, destination)) return SceKernelError(result); - bool replacesFile = false; -#ifdef _WIN32 - const auto destinationStatus = std::filesystem::symlink_status(destination, error); - replacesFile = !std::filesystem::is_directory(sourceStatus) && std::filesystem::exists(destinationStatus) && !std::filesystem::is_directory(destinationStatus); -#endif - error = RenameNative_nid_no_patch(source, destination, replacesFile); - if (error) return SceErrorFromErrno(GUEST_EIO); - RecordWrittenPath_nid_no_patch(source); - RecordWrittenPath_nid_no_patch(destination); - return 0; + const int result = RenameGuestPath_nid_no_patch(from, to); + return result == 0 ? 0 : SceKernelError(errno); } int APS5_VABI sceKernelRmdir(const char* path) { diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index dbfe47bf187b..49e491835bb4 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -580,6 +580,41 @@ int main() { Require(rename_nid_postfix(name(targetFile).c_str(), name(targetFile).c_str()) == 0); Require(rename_nid_postfix(name(sourceDirectory).c_str(), name(emptyDirectory).c_str()) == 0); Require(!std::filesystem::exists(sourceDirectory) && std::filesystem::is_directory(emptyDirectory / "child")); + const auto kernelSource = area / "kernel_source"; + const auto kernelTarget = area / "kernel_target"; + Require(std::filesystem::create_directories(kernelSource / "child")); + Require(std::filesystem::create_directory(kernelTarget)); + { std::ofstream stream(kernelSource / "marker"); stream << "kernel directory contents"; } + const int directoryResult = sceKernelRename(name(kernelSource).c_str(), name(kernelTarget).c_str()); + if (directoryResult != 0) + std::fprintf(stderr, "kernel empty-directory replacement result=0x%08x, expected=0\n", static_cast(directoryResult)); + Require(directoryResult == 0); + Require(!std::filesystem::exists(kernelSource) && std::filesystem::is_directory(kernelTarget / "child")); + { std::ifstream stream(kernelTarget / "marker"); std::string contents; std::getline(stream, contents); + Require(contents == "kernel directory contents"); } + const int parentResult = sceKernelRename(name(targetFile).c_str(), absentParent.c_str()); + Require(parentResult == static_cast(0x80020002u)); + Require(std::filesystem::is_regular_file(targetFile) && !std::filesystem::exists(area / "absent")); + const int prefixResult = sceKernelRename(name(targetFile).c_str(), name(targetFile / "target").c_str()); + Require(prefixResult == static_cast(0x80020014u)); + Require(std::filesystem::is_regular_file(targetFile)); + const int fileDirectoryResult = sceKernelRename(name(targetFile).c_str(), name(fullDirectory).c_str()); + Require(fileDirectoryResult == static_cast(0x80020015u)); + Require(std::filesystem::is_regular_file(targetFile) && std::filesystem::is_regular_file(fullDirectory / "entry")); + const int directoryFileResult = sceKernelRename(name(kernelTarget).c_str(), name(targetFile).c_str()); + Require(directoryFileResult == static_cast(0x80020014u)); + Require(std::filesystem::is_regular_file(kernelTarget / "marker") && std::filesystem::is_regular_file(targetFile)); + const int nonemptyResult = sceKernelRename(name(kernelTarget).c_str(), name(fullDirectory).c_str()); + Require(nonemptyResult == static_cast(0x80020042u)); + Require(std::filesystem::is_regular_file(kernelTarget / "marker") && std::filesystem::is_regular_file(fullDirectory / "entry")); + const auto descendant = kernelTarget / "child" / "moved"; + const int descendantResult = sceKernelRename(name(kernelTarget).c_str(), name(descendant).c_str()); + Require(descendantResult == static_cast(0x80020016u)); + Require(std::filesystem::is_directory(kernelTarget / "child") && !std::filesystem::exists(descendant)); + { std::ifstream stream(targetFile); std::string contents; std::getline(stream, contents); Require(contents == "source"); } + { std::ifstream stream(fullDirectory / "entry"); std::string contents; std::getline(stream, contents); Require(contents == "entry"); } + { std::ifstream stream(kernelTarget / "marker"); std::string contents; std::getline(stream, contents); + Require(contents == "kernel directory contents"); } for (const bool kernel : {false, true}) { const auto incoming = name(area / "incoming.txt"); const auto occupied = name(area / "occupied.txt"); From 58f86355f4f6c775c3f3425ce96bbef0a9a98bdd Mon Sep 17 00:00:00 2001 From: JJenkx Date: Thu, 15 Oct 2026 09:21:14 -0400 Subject: [PATCH 079/176] fix(ngs2): return PS5 errors for invalid voice handles (cherry picked from commit b17406a33ace372f0c031cb97c8e34945b4b77d2) --- .../libs/prx/libSceNgs2.native/CMakeLists.txt | 2 +- .../libSceNgs2.native/include/Ngs2Types.hpp | 2 + core/libs/prx/libSceNgs2.native/src/Voice.cpp | 35 ++++++---- .../tests/Ngs2VoiceHandles.cpp | 68 +++++++++++++++++++ docs/dev/debt/libSceNgs2.native.md | 1 + 5 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp diff --git a/core/libs/prx/libSceNgs2.native/CMakeLists.txt b/core/libs/prx/libSceNgs2.native/CMakeLists.txt index ca10bd5243b9..4737448b07bd 100644 --- a/core/libs/prx/libSceNgs2.native/CMakeLists.txt +++ b/core/libs/prx/libSceNgs2.native/CMakeLists.txt @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/Ngs2Library.cmake) add_sce_ngs2_library(libSceNgs2.native) if(BUILD_TESTING) - foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom) + foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom VoiceHandles) string(TOLOWER ${ngs2Test} testName) add_test_executable(ngs2_${testName}_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Ngs2${ngs2Test}.cpp) target_include_directories(ngs2_${testName}_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src) diff --git a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp index 19e7cd543de3..a76406244b2b 100644 --- a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp +++ b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp @@ -14,6 +14,8 @@ static constexpr int SCE_NGS2_ERROR_INVALID_RACK_HANDLE = static_cast(0x804 static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA = static_cast(0x804A8430); static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT = static_cast(0x804A8431); static constexpr int SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT = static_cast(0x804A8432); +static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_HANDLE = static_cast(0x804A8203); +static constexpr int SCE_NGS2_ERROR_NULL_PARAM_LIST = static_cast(0x804A8360); static constexpr std::uint32_t SCE_NGS2_RACK_ID_SAMPLER = 0x1000; static constexpr std::uint32_t SCE_NGS2_RACK_ID_SUBMIXER = 0x2000; diff --git a/core/libs/prx/libSceNgs2.native/src/Voice.cpp b/core/libs/prx/libSceNgs2.native/src/Voice.cpp index 4eab58bb6e22..7aa4ea0ef6a1 100644 --- a/core/libs/prx/libSceNgs2.native/src/Voice.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Voice.cpp @@ -390,20 +390,16 @@ static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { throw std::runtime_error("NGS2: voice param " + Ngs2Hex(param.id) + " is not implemented"); } -static Ngs2Voice& CheckedVoice(Ngs2Handle handle) { - auto* voice = Ngs2FindVoice(handle); - if (voice == nullptr) throw std::invalid_argument("NGS2: invalid voice handle"); - return *voice; -} - #pragma GCC visibility push(default) extern "C" { int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { + if (param_list == nullptr) return SCE_NGS2_ERROR_NULL_PARAM_LIST; std::lock_guard lock(Ngs2Mutex()); - auto& voice = CheckedVoice(voice_handle); - if (param_list == nullptr) APS5_INVALID_ARG_EX; + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + auto& voice = *found; for (const auto* param = param_list;; param = reinterpret_cast(reinterpret_cast(param) + param->next)) { ApplyParam(voice, *param); if (param->next == 0) break; @@ -413,7 +409,9 @@ int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHe int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCommand* commands, size_t num_commands) { std::lock_guard lock(Ngs2Mutex()); - auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + auto& voice = *found; if (commands == nullptr && num_commands != 0) APS5_INVALID_ARG_EX; for (std::size_t i = 0; i < num_commands; i++) { const auto& command = commands[i]; @@ -434,9 +432,12 @@ int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCom } int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size) { - if (state == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); - const auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + if (state_size < sizeof(Ngs2VoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + if (state == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const auto& voice = *found; switch (voice.rack->rackId) { case SCE_NGS2_RACK_ID_SAMPLER: { if (state_size != sizeof(Ngs2SamplerVoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; @@ -479,7 +480,9 @@ int APS5_VABI sceNgs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, Ngs if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; if (info_size != sizeof(Ngs2VoicePortInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; std::lock_guard lock(Ngs2Mutex()); - auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + auto& voice = *found; const auto& source = PortAt(voice, port); *info = {}; info->matrix_id = source.matrix; @@ -491,7 +494,9 @@ int APS5_VABI sceNgs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, Ngs int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t info_id, void* info, size_t info_size) { if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); - const auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + const auto& voice = *found; switch (info_id) { case SCE_NGS2_VOICE_INFO_CHANNELS: { if (info_size != sizeof(Ngs2VoiceChannelsInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; @@ -505,7 +510,9 @@ int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t info_id, vo int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags) { if (state_flags == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); - *state_flags = CheckedVoice(voice_handle).stateFlags; + const auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + *state_flags = found->stateFlags; return SCE_NGS2_OK; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp new file mode 100644 index 000000000000..5937b3f76362 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp @@ -0,0 +1,68 @@ +#include "Ngs2Test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +static constexpr std::array Names{"control", "commands", "state", "port", "query", "flags"}; +static constexpr int InvalidVoice = static_cast(0x804A8203u); +static constexpr int NullList = static_cast(0x804A8360u); + +static void Status(int actual, int expected, const std::string& message) { + if (actual != expected) throw std::runtime_error(message); +} + +static void Invalid(std::string_view api, uintptr_t handle) { + alignas(16) std::array output; + output.fill(0xa5); + const Ngs2VoiceEventParam event{{sizeof(Ngs2VoiceEventParam), 0, SCE_NGS2_VOICE_PARAM_EVENT}, SCE_NGS2_VOICE_EVENT_STOP_IMM}; + const Ngs2VoiceCommand command{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_STOP_IMM}}; + int result = 0; + if (api == "control") result = sceNgs2VoiceControl(handle, &event.header); + else if (api == "commands") result = sceNgs2VoiceRunCommands(handle, &command, 1); + else if (api == "state") result = sceNgs2VoiceGetState(handle, reinterpret_cast(output.data()), sizeof(Ngs2SamplerVoiceState)); + else if (api == "port") result = sceNgs2VoiceGetPortInfo(handle, 0, reinterpret_cast(output.data()), sizeof(Ngs2VoicePortInfo)); + else if (api == "query") result = sceNgs2VoiceQueryInfo(handle, SCE_NGS2_VOICE_INFO_CHANNELS, output.data(), sizeof(Ngs2VoiceChannelsInfo)); + else if (api == "flags") result = sceNgs2VoiceGetStateFlags(handle, reinterpret_cast(output.data())); + else throw std::runtime_error("unknown voice API test selection"); + Status(result, InvalidVoice, std::string(api) + " returned the wrong invalid-voice error"); + Require(std::all_of(output.begin(), output.end(), [](auto byte) { return byte == 0xa5; })); +} + +int main(int argc, char** argv) { + try { + if (argc > 2 || (argc == 2 && std::find(Names.begin(), Names.end(), std::string_view(argv[1])) == Names.end())) throw std::runtime_error("unknown voice API test selection"); + const auto selected = [&](auto name) { return argc == 1 || std::string_view(argv[1]) == name; }; + for (auto name : Names) if (selected(name)) { Invalid(name, 0); Invalid(name, 0x1234); } + Status(sceNgs2VoiceControl(0x1234, nullptr), NullList, "Control parameter-list precedence differs from PS5"); + Status(sceNgs2VoiceGetState(0x1234, nullptr, 0), InvalidVoice, "GetState checked output before the voice"); + const auto system = CreateSystem(); + const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER); + const auto voice = Voice(rack); + for (auto name : Names) if (selected(name)) { Invalid(name, system); Invalid(name, rack); } + Status(sceNgs2VoiceControl(voice, nullptr), NullList, "live voice lost the null parameter-list error"); + Status(sceNgs2VoiceGetState(voice, nullptr, 0), SCE_NGS2_ERROR_INVALID_OUT_SIZE, "GetState checked null output before its minimum size"); + Status(sceNgs2VoiceGetState(voice, nullptr, sizeof(Ngs2VoiceState) - 1), SCE_NGS2_ERROR_INVALID_OUT_SIZE, "GetState lost its minimum-size boundary"); + Status(sceNgs2VoiceGetState(voice, nullptr, sizeof(Ngs2VoiceState)), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetState rejected its minimum-size boundary before null output"); + Status(sceNgs2VoiceGetState(voice, nullptr, sizeof(Ngs2SamplerVoiceState)), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetState lost null-output validation"); + Status(sceNgs2VoiceGetStateFlags(voice, nullptr), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetStateFlags lost null-output validation"); + std::uint32_t flags = 0; + Status(sceNgs2VoiceGetStateFlags(voice, &flags), SCE_NGS2_OK, "valid state-flags lookup failed"); + Ngs2SamplerVoiceState state{}; + Status(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)), SCE_NGS2_OK, "valid sampler state lookup failed"); + const Ngs2VoiceCommand stop{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_STOP_IMM}}; + Status(sceNgs2VoiceRunCommands(voice, &stop, 1), SCE_NGS2_OK, "valid voice command failed"); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + for (auto name : Names) if (selected(name)) Invalid(name, voice); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + std::cout << "NGS2 invalid voice errors, outputs and precedence checked\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index f32a66cf47ce..77b9ef628263 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -2,6 +2,7 @@ ### Unknown function info +- [NGS2 voice validation](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) returns `0x804A8203` for an invalid voice in Control, RunCommands, GetState, GetStateFlags, GetPortInfo and QueryInfo, following the type-4 handle error in [prosper's PS5 module analysis](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp#L4478). Control checks a null parameter list first (`0x804A8360`); GetState resolves the voice before output validation, and GetState rejects sizes below 8 before a null output. Existing rack-specific exact state sizes, other argument precedence and command layouts are retained; arbitrary-sized state copies and full console behavior remain unverified. These values follow module analysis, not new console execution. - [NGS2 render buffer capacity](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors. - [NGS2 ATRAC9 waveform loops](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) accepts one integer forward `smpl` loop. Inclusive RIFF endpoints and encoder-delay adjustment follow [vgmstream riff.c](https://github.com/vgmstream/vgmstream/blob/7dc938fa2f210943b37c7b6511852b516ef432ab/src/meta/riff.c). PPSA03747 supplies a 60-byte sampler chunk with one loop, `samplerData` 24, start 256, inclusive end 299224 and delay 256 for 298969 audible samples. The chunk size and loop count bound the records; `samplerData` is not interpreted. Output positions are assumed to use audible samples and an exclusive end. Blocks split into an optional intro, a repeated range and an optional tail; zero play count means infinite repeats, positive play count includes the first pass. ATRAC9 ranges include a preceding codec frame for decoder history. The output block layout, repeat-count mapping and error codes are unverified on hardware. Multiple, reverse, alternating and fractional loops throw. - [NGS2 submixer UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (control `0x20000004`) invokes the title's callback after input mixing on every playing grain, including silence. The 40-byte control and 48-byte process context follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/ngs2/ngs2_submixer.h). PPSA03747 installs a peak-meter callback; its code confirms the channel pointers, user data 0, channel count, grain size and sample-rate offsets and returns 0. Other user data words are preserved. Flags are assumed to be 1 after registration/restart and 0 thereafter, following UserFx2's first-process convention; this callback does not inspect them. Null handlers disable the effect, setup clears it, and nonzero callback results throw. These lifecycle and error semantics are unverified on console hardware. From 8dd0b5d0d2a83d2fe8975fa00c1b9bb8fa5710a6 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Fri, 16 Oct 2026 14:03:31 +0000 Subject: [PATCH 080/176] fix(libc): give the scripting heap hooks blocks from libc's own heap scriptingGetMem and scriptingFreeMem went through the title's registered allocator. A title that ships its own libc.prx has already initialized that allocator, and the application heap's lazy initialization then ran its initializer a second time: Metal Gear Solid Master Collection (PPSA18194) started a second Unity heap and aborted when it freed a block of the first. The hooks now allocate and free through libc's guest heap, so they never initialize the registered allocator; sizes that overflow return null. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit b4e129ff800826015a5b0df120225382bb99c0fd) --- core/libs/prx/libc/CMakeLists.txt | 2 +- core/libs/prx/libc/src/ApplicationHeap.cpp | 6 ++- core/libs/tests/ApplicationHeap.cpp | 45 ++++++++++++++++++---- docs/dev/debt/libc.md | 2 +- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 1be99fdb8652..5bb224d9f62d 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -774,7 +774,7 @@ if(BUILD_TESTING) add_test(NAME application_heap_lazy COMMAND application_heap_tests lazy) endif() if(WIN32) - set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order application_heap_lazy + set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order application_heap_scripting application_heap_lazy PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_allocator_failures_tests ${LIBS_INCLUDE_DIR}/tests/GuestAllocatorFailures.cpp) diff --git a/core/libs/prx/libc/src/ApplicationHeap.cpp b/core/libs/prx/libc/src/ApplicationHeap.cpp index 76d72e26ed24..26224bd98d73 100644 --- a/core/libs/prx/libc/src/ApplicationHeap.cpp +++ b/core/libs/prx/libc/src/ApplicationHeap.cpp @@ -209,14 +209,16 @@ extern "C" void* APS5_VABI scriptingGetMem_nid_postfix(std::size_t alignment, st alignment = std::max(alignment, 16); if ((alignment & (alignment - 1)) != 0) return nullptr; try { - return ApplicationHeapAlign_nid_no_patch(alignment, bytes); + return GuestHeap::GuestHeapAlign_nid_postfix(alignment, bytes); } catch (const std::bad_alloc&) { return nullptr; + } catch (const std::length_error&) { + return nullptr; } } extern "C" void APS5_VABI scriptingFreeMem_nid_postfix(void* pointer) { - ApplicationHeapFree_nid_no_patch(pointer); + GuestHeap::GuestHeapFree_nid_postfix(pointer); } void* ApplicationHeapRealign_nid_no_patch(void* pointer, std::size_t bytes, std::size_t alignment) { diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 7477d42fd027..e30453224341 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -201,6 +201,28 @@ int main(int argc, char** argv) { require(initializes == 1); return 0; } + if (argc > 1 && std::strcmp(argv[1], "scripting") == 0) { + installProcessParameters(); + constexpr std::size_t blockBytes = 0x40000; + auto* first = static_cast(scriptingGetMem_nid_postfix(0x1000, blockBytes)); + require(initializes == 0 && lastSize == 0); + auto* second = static_cast(scriptingGetMem_nid_postfix(0x1000, blockBytes)); + require(initializes == 0 && lastSize == 0); + require(first != nullptr && second != nullptr && reinterpret_cast(first) % 0x1000 == 0 && reinterpret_cast(second) % 0x1000 == 0); + require(first + blockBytes <= second || second + blockBytes <= first); + std::memset(first, 0x5a, blockBytes); + std::memset(second, 0xa5, blockBytes); + require(first[blockBytes - 1] == std::byte{0x5a} && second[0] == std::byte{0xa5}); + scriptingFreeMem_nid_postfix(first); + scriptingFreeMem_nid_postfix(second); + require(initializes == 0 && frees == 0); + _init_env_nid_postfix(); + require(initializes == 1); + require(_Znwm_nid_postfix(40) == storage.data() && lastSize == 40); + _ZdlPv_nid_postfix(storage.data()); + require(frees == 1); + return 0; + } if (argc > 1 && std::strcmp(argv[1], "exit-order") == 0) { require(atexit_nid_postfix(exitCallbackAllocates) == 0); ApplicationHeapInitialize_nid_no_patch(process.data()); @@ -392,14 +414,23 @@ int main(int argc, char** argv) { require(getcwd_nid_postfix(nullptr, 0) == nullptr && *__error_nid_postfix() == 12); fail = false; const auto beforeScripting = frees; + const auto sizeBeforeScripting = lastSize; scriptingFreeMem_nid_postfix(nullptr); - require(frees == beforeScripting); - require(scriptingGetMem_nid_postfix(0, 31) == storage.data() && lastAlignment == 16 && lastSize == 31); - require(scriptingGetMem_nid_postfix(64, 37) == storage.data() && lastAlignment == 64 && lastSize == 37); - scriptingFreeMem_nid_postfix(storage.data()); - require(frees == beforeScripting + 1); - require(scriptingGetMem_nid_postfix(24, 80) == nullptr && lastSize == 37); + auto* small = static_cast(scriptingGetMem_nid_postfix(0, 31)); + auto* aligned = static_cast(scriptingGetMem_nid_postfix(64, 37)); + require(small != nullptr && small != storage.data() && reinterpret_cast(small) % 16 == 0); + require(aligned != nullptr && aligned != storage.data() && reinterpret_cast(aligned) % 64 == 0); + std::memset(small, 0x5a, 31); + std::memset(aligned, 0xa5, 37); + require(small + 31 <= aligned || aligned + 37 <= small); + scriptingFreeMem_nid_postfix(small); + scriptingFreeMem_nid_postfix(aligned); + require(scriptingGetMem_nid_postfix(24, 80) == nullptr); + require(scriptingGetMem_nid_postfix(16, std::numeric_limits::max()) == nullptr); fail = true; - require(scriptingGetMem_nid_postfix(16, 64) == nullptr); + auto* unaffected = scriptingGetMem_nid_postfix(16, 64); + require(unaffected != nullptr); + scriptingFreeMem_nid_postfix(unaffected); fail = false; + require(frees == beforeScripting && lastSize == sizeBeforeScripting); } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 16352aeb4214..6ff6985aa83b 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -3,7 +3,7 @@ ### Unknown function info - [fdopen](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) validates descriptor access modes as FreeBSD 11.4 [fdopen.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/fdopen.c) does: a requested mode incompatible with a descriptor not opened for both reading and writing returns null with `EINVAL`, leaving the descriptor open. Windows reads the handle's granted access through the same host helper as `fcntl(F_GETFL)`; a closed descriptor returns null with `EBADF`. Console behavior is unverified. -- [scriptingGetMem, scriptingFreeMem](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - runtime allocator hooks for Unity titles. The allocation signature `(alignment, size)`, minimum alignment 16 and null result for a non-power-of-two alignment follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libKernel.cpp); the free hook's one-pointer signature follows [Raeen's recorded contract](https://github.com/Whoisraeen/Raeen/blob/2db56923204b89719916b95e29c2f3c4e1d9f90b/docs/unity-dlsym-blocker.md). Both use the registered application allocator. No console measurement; null allocation returns null and other allocator errors throw. +- [scriptingGetMem, scriptingFreeMem](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - runtime allocator hooks for Unity titles. The allocation signature `(alignment, size)`, minimum alignment 16 and null result for a non-power-of-two alignment follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libKernel.cpp); the free hook's one-pointer signature follows [Raeen's recorded contract](https://github.com/Whoisraeen/Raeen/blob/2db56923204b89719916b95e29c2f3c4e1d9f90b/docs/unity-dlsym-blocker.md). Both use libc's own guest heap, not the title's registered allocator. A title that ships its own `libc.prx` (Metal Gear Solid Master Collection, PPSA18194) has already initialized that allocator, and reaching it from here would run its initializer a second time. That title's IL2CPP runtime looks up only `scriptingGetMem`, by name from `Il2cppUserAssemblies.prx`, and asks it for 256 KiB blocks aligned to 4 KiB. Which console library exports the hooks is unknown, and there is no console measurement. Allocation failure and sizes that overflow return null. - [_Mtx_trylock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `k6pGNMwJB08`) - returns busy for a non-recursive mutex already owned by the caller, assumed to match `_Mtx_lock`'s ownership handling; exact console behavior is unverified. - [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified. From 231f96a77fdac4ea351bf2385d3143d514dac827 Mon Sep 17 00:00:00 2001 From: klosixgaming-arch Date: Thu, 15 Oct 2026 07:07:22 -0500 Subject: [PATCH 081/176] feat(libkernel): accept a kevent read event on descriptor 0 PPSA34015 (EA SPORTS FC 27) registers EVFILT_READ for descriptor 0 in a new kqueue at start-up, ignores the result, and stopped with "kevent: EVFILT_READ on a non-socket descriptor not implemented". A read event on the host stream on descriptor 0 is now accepted and never triggers, as select never reports descriptor 0 readable. (cherry picked from commit ce5b0c395ba1399af4ccb8c7fe4b9d65670ea230) --- core/libs/prx/libkernel/Equeue/src/Kqueue.cpp | 15 +++++++++++++ core/libs/tests/GuestKqueue.cpp | 22 +++++++++++++++++++ docs/dev/debt/libkernel.md | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp index 20859f0b23d6..d03afadb9e0b 100644 --- a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp +++ b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/HostStreams.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" @@ -140,6 +141,17 @@ std::int64_t DescriptorLowWater(const KernelEvent& change) { return change.data; } +int AddStandardInputKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) { + KernelEqueueEvent event{}; + event.event = change; + event.event.flags = flags; + event.event.fflags = 0; + event.event.data = 0; + change.fflags = 0; + change.data = 0; + return GuestErrno(queue->AddEvent(event)); +} + int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) { const int descriptor = static_cast(change.ident); if (IsKqueueDescriptor(descriptor)) { @@ -148,6 +160,9 @@ int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::u } NotImplemented_nid_no_patch("kevent: EVFILT_READ on a kqueue descriptor"); } + if (descriptor == 0 && change.filter == EVFILT_READ && File::IsHostStream(descriptor)) { + return AddStandardInputKnote(queue, change, flags); + } const bool socket = GuestSockets::IsOpen(descriptor); std::int64_t data = 0; bool eof = false; diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index b3d463f9d86d..06b594a01da0 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -639,7 +639,29 @@ static void Timeout() { Require(close_nid_postfix(kq) == 0); } +static void StandardInput() { + const int copy = dup_nid_postfix(0); + if (copy < 0) { + Require(*__error_nid_postfix() == GuestEbadf); + return; + } + Require(close_nid_postfix(copy) == 0); + const int kq = kqueue_nid_postfix(); + int marker = 0; + KernelEvent events[4]{}; + Require(Apply(kq, Change(0, EvfiltRead, EvAdd, 0, 0, &marker)) == 0); + Require(Poll(kq, events, 4) == 0); + const KernelTimespec brief{0, 20000000}; + Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, &brief) == 0); + Require(Apply(kq, Change(0, EvfiltRead, EvAdd, 0, 0, &marker)) == 0); + Require(Apply(kq, Change(0, EvfiltRead, EvDelete)) == 0); + RequireFailure(Apply(kq, Change(0, EvfiltRead, EvDelete)), GuestEnoent); + Require(ThrownMessage([kq] { Apply(kq, Change(0, EvfiltWrite, EvAdd)); }).find("EVFILT_WRITE on a non-socket descriptor") != std::string::npos); + Require(close_nid_postfix(kq) == 0); +} + int main() { + StandardInput(); CreateAndClose(); Timers(); PipeRead(); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 29893f2b803f..ac68175d01d4 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -114,7 +114,7 @@ - [sceKernelMprotect, sceKernelMtypeprotect](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) over a range with a gap that is not registered in the allocation tracker protect the registered parts and skip the gap, and return 0, as FreeBSD 11.4's [`vm_map_protect`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/vm/vm_map.c) does (it has no check for holes); a range whose start is unmapped (also when later pages are mapped) returns `SCE_KERNEL_ERROR_EINVAL` (`mprotect`: `EINVAL`) because an existing test pins that for an unmapped start; FreeBSD answers 0 there too (`vm_map_protect` takes the next entry), so this errno is not taken from the source and was not changed. `sceKernelMprotect` threw for a gap before, which ended the title. Every part is checked for a GPU or image pin before the first one is protected; the parts are then protected one after the other, so a host protection failure in a later part leaves the earlier parts changed (FreeBSD's `vm_map_protect` cannot fail in its second pass). The behaviour on the console is not known. None of this was measured on a console. - [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, as FreeBSD 11's [`sys_mprotect`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/vm/vm_mmap.c#L606) does with `VM_PROT_ALL`. Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`. Not measured on a console; the map calls and `sceKernelMtypeprotect` still refuse undefined bits - [JIT shared memory](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - `sceKernelJitCreateSharedMemory`, `sceKernelJitCreateAliasOfSharedMemory` and `sceKernelJitMapSharedMemory` take the signatures of [Orbital](https://github.com/AlexAltea/orbital)'s `tools/ida/db_types.json` (name, length, maximum protection, descriptor out; descriptor, maximum protection, descriptor out; descriptor, protection, address in and out), which ps4sdk, ps5-jar-loader and the call sites in RetroArch PPSA99169, XPSemu PPSA97358 and VLC PPSA85300 match; SharpProspero declares the alias as returning the descriptor. The object is a host anonymous file shared by its descriptors (a `memfd` on Linux, a delete-on-close temporary file mapped as an executable section on Windows), the alias is a duplicate descriptor, and every map maps the whole object, so writes through one view show through the others; a non-null address is a hint, as in `mmap` without `MAP_FIXED`. Not measured on the console: a length that is zero or not a multiple of 16 KiB and protection bits other than CPU read, write and execute are `EINVAL`; an alias or a map asking for more than the descriptor's maximum protection (write and execute counting as read) is `EACCES`, as FreeBSD 11's `vm_map_protect` treats a protection past `max_protection`; a descriptor that is not a JIT object is `EBADF`. The name is ignored, the objects do not count against the flexible memory budget, `mprotect` of a view is not limited to the maximum protection, and `mmap` of the descriptors is unsupported, as for `shm_open`. On Linux the `memfd` is created without `MFD_EXEC`, which Linux 6.3 added and older kernels reject with `EINVAL`, and relies on the protection of the mapping: the flag only sets the file's execute permission bits, and the direct memory `memfd` is mapped executable the same way; a host with `vm.memfd_noexec` set to 2 refuses the creation and the call throws -- [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters; other filters, `EV_CLEAR` on a socket or a pipe, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event takes a mark of 1 only, where the host's writability stands in for one free byte, since the host does not report the send buffer's free space; other write marks and marks below 1, which make FreeBSD's read filter always fire, throw +- [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters and a read event on the host stream on descriptor 0, which never triggers, as `select` never reports descriptor 0 readable; PPSA34015 registers descriptor 0 for reading in a new kqueue at start-up and does not check the result; other filters, `EV_CLEAR` on a socket or a pipe, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event takes a mark of 1 only, where the host's writability stands in for one free byte, since the host does not report the send buffer's free space; other write marks and marks below 1, which make FreeBSD's read filter always fire, throw - [EVFILT_VNODE, sceKernelAddFileEvent and sceKernelDeleteFileEvent](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) watch the host file or directory behind the descriptor and turn host changes into FreeBSD 11 vnode hints, which [`filt_vfsvnode`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L5032) adds whole when they share a bit with the requested `fflags`: a write that grows a file is `NOTE_WRITE | NOTE_EXTEND`, another write `NOTE_WRITE`, a shrinking truncate or an attribute change `NOTE_ATTRIB`, a higher link count `NOTE_LINK`, a lower one or a pending delete `NOTE_DELETE`, a new path `NOTE_RENAME`; in a directory, an entry created, removed or renamed is `NOTE_WRITE`, with `NOTE_LINK` for a subdirectory, and on Linux an entry moved from or to another directory adds `NOTE_EXTEND`; inotify queues the two halves of a rename one after the other, so a watcher woken by the first can read it alone, and a half read without the other is classified only after a `getdents64` on the watched directory, which takes the directory lock that `rename` holds until both halves are queued ([`do_renameat2`](https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L4975) around `vfs_rename` and its `fsnotify_move`, [`iterate_dir`](https://github.com/torvalds/linux/blob/v6.8/fs/readdir.c#L103)), an ordering taken from the kernel source, not from inotify(7), which promises none; a directory registered with `NOTE_EXTEND` on Windows is accepted, as `vfs_kqfilter` does not check it, but never receives it, because the directory handle cannot tell a move between directories from a removal plus an addition. FreeBSD's hints depend on the filesystem and the console's filesystems are not known: a growing `ftruncate` reports `NOTE_WRITE | NOTE_EXTEND` where `vop_setattr_post` reports `NOTE_ATTRIB`, and on Windows `utimes` on a file reports `NOTE_WRITE`. The three Windows notifications are told apart in the error, which names the `parent`, `entries` or `subdirectories` watch, the path it was given and `GetLastError`, but a `FindFirstChangeNotification` that fails still throws rather than returning an error to the guest: the EVFILT_VNODE attach path is [`vfs_kqfilter`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L4936), which returns `EINVAL`, `ENOMEM` or 0 after `fget` has turned a descriptor that is not open into `EBADF`, and consults no directory permission, so no FreeBSD errno stands for a host that refuses to notify, and what the console does on such a host is not measured. Only the `parent` watch can fail this way in practice: `entries` and `subdirectories` need the `FILE_LIST_DIRECTORY` that opening the watched directory already took. The parent handle produces no hint of its own, as `RunHostWatch` only calls `FindNextChangeNotification` on it, so a later change may leave the watch on its 50 ms poll instead of throwing; that is not done here. Windows uses `FindFirstChangeNotification` on the parent directory (and on a watched directory itself) and compares `GetFileInformationByHandleEx` snapshots at least every 50 ms, because a write through an open handle is reported only when the handle is flushed or closed, so file hints can arrive 50 ms late, and a file deleted while watched, which the POSIX delete of Windows 10 1903 moves into `$Extend\$Deleted` until its last handle closes, reports `NOTE_DELETE` without `NOTE_RENAME` and keeps its handle without a parent directory watch, as that directory cannot be watched; Linux uses inotify on the file and is not run. `NOTE_REVOKE`, `NOTE_OPEN`, `NOTE_CLOSE`, `NOTE_CLOSE_WRITE`, `NOTE_READ`, unknown bits and descriptors other than files, directories, pipes and sockets throw. `sceKernelAddFileEvent(eq, fd, watch, udata)` and `sceKernelDeleteFileEvent(eq, fd)` take the [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9d/include/orbis/libkernel.h#L35) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0/src/SharpProspero/Interop/Kernel/KernelEqueue.cs#L164) signatures; `watch` is taken as the `fflags` and the event is added with `EV_CLEAR`, neither confirmed on a title (feat(libkernel): implement kevent EV_DISABLE, EV_DISPATCH and EV_FORCEONESHOT) (fix(libkernel): follow kqueue_register for EV_FORCEONESHOT and modify flags) - [sem_timedwait](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) consumes an available count before checking the timeout and waits indefinitely for a null timeout, following FreeBSD 11's `sem_timedwait`. Console behavior for null and invalid timeouts when a count is available is unverified. - [tcsendbreak, tcdrain, tcflush, tcflow, tcgetpgrp, tcsetpgrp](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) follow FreeBSD 11.4's [`termios.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/termios.c): `tcflush` answers `EINVAL` for a queue other than `TCIFLUSH`, `TCOFLUSH` or `TCIOFLUSH` and `tcflow` for an action outside `TCOOFF`..`TCION` before the descriptor is looked at, and every other call fails as its terminal ioctl does on a descriptor that is not a terminal, with the errors `tcgetattr` gives, so `tcsendbreak` never reaches its 0.4 s pause. FreeBSD's `pipe_ioctl` answers `TIOCGPGRP` and `TIOCSPGRP` on a pipe or FIFO with the descriptor's SIGIO owner, which is not modelled, so `tcgetpgrp` and `tcsetpgrp` throw on a host pipe or FIFO above descriptor 2; descriptors 0 to 2 are taken as non-terminals that are not pipes, whatever the host attaches to them, so a pipe the guest creates on a freed descriptor 0 to 2 gets `ENOTTY` there. The PS5 `libkernel` is assumed to use this FreeBSD code, and `tcdrain` is not a cancellation point here From 68eb5da72e00b6025c7bc9fbb85ded8cae7ec512 Mon Sep 17 00:00:00 2001 From: Nithishvelan M Date: Fri, 16 Oct 2026 19:10:54 +0530 Subject: [PATCH 082/176] fix(libSceSaveData.native): return error for uninitialized sceSaveDataSyncSaveDataMemory (cherry picked from commit 738eff083c00994b16f2aa63264ea45d82004dd9) --- core/libs/tests/SaveDataMemoryMetadata.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/libs/tests/SaveDataMemoryMetadata.cpp b/core/libs/tests/SaveDataMemoryMetadata.cpp index 3a6011501280..f54e96b0c35f 100644 --- a/core/libs/tests/SaveDataMemoryMetadata.cpp +++ b/core/libs/tests/SaveDataMemoryMetadata.cpp @@ -52,6 +52,9 @@ int main() { Require(sceSaveDataCreateTransactionResource(1024) == kSaveDataNotInitialized); Require(sceSaveDataDeleteTransactionResource(1) == kSaveDataNotInitialized); + const std::uint32_t syncParamUninit[2] = {7531u, 0u}; + Require(sceSaveDataSyncSaveDataMemory(nullptr) == static_cast(0x809F0000u)); + Require(sceSaveDataSyncSaveDataMemory(syncParamUninit) == static_cast(0x809F0001u)); const auto previous = std::filesystem::current_path(); const auto root = std::filesystem::temp_directory_path() / ("anyps5-metadata-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); From 514284b72feb27037c3945bb4068efb4ec86fc74 Mon Sep 17 00:00:00 2001 From: Romans Pokrovskis Date: Sun, 18 Oct 2026 09:49:06 +0300 Subject: [PATCH 083/176] fix(audio): route legacy vibration PCM with per-port timing (cherry picked from commit a2d6c16bcde01b749993cc04fb50919307cad5a7) --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 40 +++ core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 104 +++++++- .../tests/AudioOutVibration.cpp | 99 ++++++++ .../tests/AudioOutVibrationDevice.cpp | 235 ++++++++++++++++++ .../tests/AudioOutVibrationTimeline.cpp | 70 ++++++ .../libScePad/include/PadAudioHapticQueue.hpp | 48 ++++ core/libs/prx/libScePad/include/PadState.hpp | 2 + core/libs/prx/libScePad/src/PadState.cpp | 38 ++- core/libs/tests/GuestPad.cpp | 7 + docs/dev/debt/libSceAudioOut.md | 2 + 10 files changed, 630 insertions(+), 15 deletions(-) create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp create mode 100644 core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 38bdb8cf0ede..577fa1347929 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -19,6 +19,46 @@ target_link_libraries(libSceAudioOut PRIVATE libc libkernel libScePad SDL2-stati configure_windows_unwind(libSceAudioOut) if(BUILD_TESTING) + add_test_executable(audio_out_vibration_timeline_tests tests/AudioOutVibrationTimeline.cpp) + target_include_directories(audio_out_vibration_timeline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_vibration_timeline_tests PRIVATE libc) + configure_windows_unwind(audio_out_vibration_timeline_tests) + add_test(NAME audio_out_vibration_timeline COMMAND audio_out_vibration_timeline_tests) + set_tests_properties(audio_out_vibration_timeline PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(audio_out_vibration_timeline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(audio_out_vibration_device_tests tests/AudioOutVibrationDevice.cpp src/AudioOut.cpp src/AudioOut2PadMix.cpp src/AudioOutShutdown.cpp) + target_compile_definitions(audio_out_vibration_device_tests PRIVATE SDL_MAIN_HANDLED) + target_include_directories(audio_out_vibration_device_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_link_libraries(audio_out_vibration_device_tests PRIVATE libc libkernel libScePad) + configure_windows_unwind(audio_out_vibration_device_tests) + foreach(vibrationDeviceMode IN ITEMS quad pulse open_failure timing discovery_failure) + set(vibrationDeviceTest "audio_out_vibration_device_${vibrationDeviceMode}") + add_test(NAME ${vibrationDeviceTest} COMMAND audio_out_vibration_device_tests ${vibrationDeviceMode}) + set_tests_properties(${vibrationDeviceTest} PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(${vibrationDeviceTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + + add_test_executable(audio_out_vibration_tests tests/AudioOutVibration.cpp) + target_include_directories(audio_out_vibration_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_vibration_tests PRIVATE libSceAudioOut libScePad libc) + configure_windows_unwind(audio_out_vibration_tests) + foreach(vibrationMode IN ITEMS s16 f32 batch_s16 batch_f32 mono_s16 mono_f32 gain_s16 gain_f32 muted silence drain ex pad_control) + set(vibrationTest "audio_out_vibration_${vibrationMode}") + add_test(NAME ${vibrationTest} COMMAND audio_out_vibration_tests --${vibrationMode}) + set_tests_properties(${vibrationTest} PROPERTIES TIMEOUT 5 ENVIRONMENT "SDL_AUDIODRIVER=dummy") + if(WIN32) + set_tests_properties(${vibrationTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_test_executable(audio_out_port_locks_tests tests/AudioOutPortLocks.cpp) target_include_directories(audio_out_port_locks_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 16d6f40196c6..034d07bdf939 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -23,6 +23,8 @@ #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libSceAudioOut/include/AudioOutSdl.hpp" #include "AudioOutShutdown.hpp" +#include "AudioOut2PadMix.hpp" +#include "prx/libScePad/include/PadState.hpp" static constexpr int PORT_TYPE_MAIN = 0; static constexpr int PORT_TYPE_BGM = 1; @@ -98,6 +100,14 @@ static std::uint32_t bytesPerSample(Format f) { return formatIsFloat(f) ? sizeof(float) : sizeof(std::int16_t); } +static std::uint32_t checkedBufferSize(std::uint32_t frames, std::uint32_t channels, std::uint32_t bps) { + const std::uint64_t size = static_cast(frames) * channels * bps; + if (size > static_cast(std::numeric_limits::max())) { + throw std::runtime_error("checkedBufferSize: audio buffer size overflows"); + } + return static_cast(size); +} + struct Port { bool used = false; int type = 0; @@ -109,6 +119,7 @@ struct Port { int mixLevel = DEFAULT_VOLUME; std::uint64_t lastDataOutputTime = 0; std::uint64_t virtualQueueEnd = 0; + AudioOut2PadLayout padLayout; SDL_AudioDeviceID device = 0; SDL_AudioSpec spec = {}; }; @@ -160,6 +171,30 @@ static bool openDevice(Port& port) { return true; } +static bool openVibrationDevice(Port& port) { + if (!ensureSdlAudio()) throw std::runtime_error(std::string("AudioOut: could not initialize controller audio: ") + SDL_GetError()); + const int count = SDL_GetNumAudioDevices(0); + if (count < 0) throw std::runtime_error(std::string("AudioOut: could not enumerate controller sound cards: ") + SDL_GetError()); + for (int index = 0; index < count; ++index) { + const char* name = SDL_GetAudioDeviceName(index, 0); + if (!AudioOut2IsPadAudioDevice(name)) continue; + const std::string controllerName(name); + SDL_AudioSpec native{}; + if (SDL_GetAudioDeviceSpec(index, 0, &native) == 0 && native.channels != 0 && native.channels != AUDIO_OUT2_PAD_CHANNELS) continue; + port.padLayout = AudioOut2PadLayoutForDriver(SDL_GetCurrentAudioDriver()); + SDL_AudioSpec desired{}; + desired.freq = static_cast(port.freq); + desired.format = AUDIO_F32SYS; + desired.channels = static_cast(port.padLayout.channels); + desired.samples = static_cast(port.samplesNum); + port.device = SDL_OpenAudioDevice(controllerName.c_str(), 0, &desired, &port.spec, 0); + if (port.device == 0) throw std::runtime_error(std::string("AudioOut: could not open the controller sound card: ") + SDL_GetError()); + SDL_PauseAudioDevice(port.device, 0); + return true; + } + return false; +} + static void closeDevice(Port& port) { if (port.device != 0 && SDL_WasInit(SDL_INIT_AUDIO) != 0) { SDL_ClearQueuedAudio(port.device); @@ -221,6 +256,23 @@ static const void* prepareBuffer(const Port& port, const void* data, std::vector return buf.data(); } +static std::vector prepareVibration(const Port& port, const void* data) { + std::vector prepared; + const auto* samples = prepareBuffer(port, data, prepared); + std::vector pad(static_cast(port.samplesNum) * AUDIO_OUT2_PAD_CHANNELS, 0.0f); + constexpr float unity[2] = {1.0f, 1.0f}; + for (std::uint32_t frame = 0; frame < port.samplesNum; ++frame) { + float input[2]{}; + for (int channel = 0; channel < port.channels; ++channel) { + const auto index = static_cast(frame) * port.channels + channel; + input[channel] = formatIsFloat(port.format) ? static_cast(samples)[index] : static_cast(samples)[index] / 32768.0f; + } + AudioOut2AccumulatePadFrame(AudioOut2Route::PadVibration, input, port.channels, unity, pad.data() + static_cast(frame) * AUDIO_OUT2_PAD_CHANNELS); + } + AudioOut2FinishPadMix(pad.data(), port.samplesNum); + return pad; +} + static void sleepUs(std::unique_lock& lock, std::uint64_t us) { lock.unlock(); PreciseSleepUs(us); @@ -241,12 +293,19 @@ static void queueAudio(Port& port, const void* data, std::unique_lock prepareBuf; - const void* prepared = prepareBuffer(port, data, prepareBuf); - const std::uint32_t preparedSize = port.samplesNum * - static_cast(port.channels) * bytesPerSample(port.format); - - const void* queueData = prepared; - const std::uint32_t queueSize = preparedSize; + std::vector padFrames; + const void* queueData = nullptr; + std::uint32_t queueSize = 0; + if (port.type == PORT_TYPE_VIBRATION) { + queueSize = checkedBufferSize(port.samplesNum, port.padLayout.channels, sizeof(float)); + const auto pad = prepareVibration(port, data); + padFrames.resize(static_cast(port.samplesNum) * port.padLayout.channels); + AudioOut2WritePadFrames(pad.data(), port.padLayout, padFrames.data(), port.samplesNum); + queueData = padFrames.data(); + } else { + queueData = prepareBuffer(port, data, prepareBuf); + queueSize = checkedBufferSize(port.samplesNum, static_cast(port.channels), bytesPerSample(port.format)); + } const std::uint64_t bufferUs = port.freq != 0 ? (1000000ULL * port.samplesNum) / port.freq @@ -283,6 +342,25 @@ static void paceVirtualPort(Port& port, bool hasData, std::unique_lock& lock) { + const auto pad = port.type == PORT_TYPE_VIBRATION && data != nullptr ? prepareVibration(port, data) : std::vector{}; + paceVirtualPort(port, data != nullptr, lock); + const auto source = static_cast(&port - g_ports) + 1; + if (port.type == PORT_TYPE_VIBRATION && data == nullptr) PadClearLegacyAudioHaptics_nid_postfix(source); + if (!pad.empty()) { + constexpr std::uint32_t grainFrames = 256; + const auto begin = port.virtualQueueEnd - (1000000ULL * port.samplesNum) / port.freq; + for (std::uint32_t frame = 0; frame < port.samplesNum;) { + const auto count = std::min(grainFrames, port.samplesNum - frame); + const auto start = begin + (1000000ULL * frame) / port.freq; + const auto end = begin + (1000000ULL * (frame + count)) / port.freq; + const auto rumble = AudioOut2RumbleFromPadFrames(pad.data() + static_cast(frame) * AUDIO_OUT2_PAD_CHANNELS, count); + PadQueueLegacyAudioHaptics_nid_postfix(source, start, end, rumble[0], rumble[1]); + frame += count; + } + } +} + static bool portTypeValid(int type) { return (type >= PORT_TYPE_MAIN && type <= PORT_TYPE_PADSPK) || type == PORT_TYPE_VIBRATION || @@ -312,6 +390,14 @@ static int allocatePort(int type, std::uint32_t len, std::uint32_t freq, Format port.lastDataOutputTime = 0; port.virtualQueueEnd = sceKernelGetProcessTime(); port.mixLevel = type == PORT_TYPE_PADSPK ? DEFAULT_PADSPK_MIX_LEVEL : DEFAULT_VOLUME; + if (type == PORT_TYPE_VIBRATION) { + try { + openVibrationDevice(port); + } catch (...) { + port = Port{}; + throw; + } + } for (int c = 0; c < port.channels; c++) { port.volume[c] = DEFAULT_VOLUME; } @@ -362,6 +448,7 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len throw std::runtime_error("sceAudioOutOpen: unknown format param"); } + if (type == PORT_TYPE_VIBRATION && channelsForFormat(format) > 2) throw std::runtime_error("sceAudioOutOpen: vibration layouts above stereo are not implemented"); return allocatePort(type, len, freq, format); } @@ -374,6 +461,7 @@ int APS5_VABI sceAudioOutClose(int handle) { return -2144993277; } closeDevice(*port); + if (port->type == PORT_TYPE_VIBRATION) PadClearLegacyAudioHaptics_nid_postfix(handle); *port = Port{}; return 0; } @@ -390,7 +478,7 @@ int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) try { } if (port->device == 0) { - paceVirtualPort(*port, ptr != nullptr, lock); + outputVirtualPort(*port, ptr, lock); } else { queueAudio(*port, ptr, lock); } @@ -428,7 +516,7 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) for (std::uint32_t i = 0; i < num; i++) { Port& port = *getPort(param[i].handle); if (port.device == 0) { - paceVirtualPort(port, param[i].ptr != nullptr, lock); + outputVirtualPort(port, param[i].ptr, lock); } else { queueAudio(port, param[i].ptr, lock); } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp new file mode 100644 index 000000000000..4f27e0bf7db7 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp @@ -0,0 +1,99 @@ +#include "prx/libScePad/include/PadState.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); +int APS5_VABI sceAudioOutExOpen(int, std::uint32_t); +int APS5_VABI sceAudioOutClose(int); +int APS5_VABI sceAudioOutOutput(int, const void*); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); +} + +static void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +struct Case { + std::string_view name; + std::uint32_t format = 1; + bool batch = false; + std::array volume{32768, 32768}; + std::array expected{128, 255}; + bool silence = false; + bool drain = false; + bool ex = false; +}; + +int main(int argc, char** argv) { + try { + const std::string_view mode = argc == 2 ? argv[1] : "--s16"; + const Case cases[] = { + {"--s16"}, {"--f32", 4}, {"--batch_s16", 1, true}, {"--batch_f32", 4, true}, + {"--mono_s16", 0, false, {32768, 32768}, {128, 128}}, + {"--mono_f32", 3, false, {32768, 32768}, {128, 128}}, + {"--gain_s16", 1, false, {16384, 8192}, {64, 64}}, + {"--gain_f32", 4, true, {16384, 8192}, {64, 64}}, + {"--muted", 4, false, {0, 0}, {0, 0}}, + {"--silence", 1, true, {32768, 32768}, {0, 0}, true}, + {"--drain", 4, false, {32768, 32768}, {0, 0}, false, true}, + {"--ex", 1, true, {32768, 32768}, {0, 0}, false, false, true}, + {"--pad_control"} + }; + const Case* selected = nullptr; + for (const auto& entry : cases) if (entry.name == mode) selected = &entry; + Require(argc <= 2 && selected != nullptr, "invalid test arguments"); + const auto& test = *selected; + Pad::Initialize(); + Pad::SetVibrationMode(1); + std::uint32_t seen = 0; + PadOutputState output{}; + static_cast(PadFetchOutput_nid_postfix(&seen, &output)); + struct Port { + int handle = -1; + ~Port() { if (handle > 0) sceAudioOutClose(handle); } + } port; + if (mode == "--pad_control") { + PadSubmitAudioHaptics_nid_postfix(128, 255); + } else { + const std::uint32_t frames = test.ex ? 256u : 48000u; + const bool floating = test.format == 3 || test.format == 4; + const std::uint32_t channels = test.format == 0 || test.format == 3 ? 1u : 2u; + std::vector integers(frames * 2); + std::vector floats(frames * 2); + for (std::uint32_t frame = 0; frame < frames && !test.silence; ++frame) { + integers[frame * channels] = 8192; + floats[frame * channels] = 0.25f; + if (channels == 2) { + integers[frame * channels + 1] = -16384; + floats[frame * channels + 1] = -0.5f; + } + } + port.handle = test.ex ? sceAudioOutExOpen(0x10000000, 0) : sceAudioOutOpen(0x10000000, 10, 0, frames, 48000, test.format); + Require(port.handle > 0, "legacy vibration port did not open"); + auto volume = test.volume; + Require(sceAudioOutSetVolume(port.handle, 3, volume.data()) == 0, "legacy vibration volume failed"); + const void* samples = test.drain ? nullptr : floating ? static_cast(floats.data()) : static_cast(integers.data()); + AudioOutOutputParam param{}; + param.handle = port.handle; + param.ptr = samples; + const int result = test.batch ? sceAudioOutOutputs(¶m, 1) : sceAudioOutOutput(port.handle, samples); + Require(result == static_cast(frames), "legacy vibration output returned the wrong frame count"); + } + const bool changed = PadFetchOutput_nid_postfix(&seen, &output); + Require(changed == (test.expected[0] != 0 || test.expected[1] != 0), "legacy vibration output published the wrong controller state"); + Require(output.audioVibrationLarge == test.expected[0] && output.audioVibrationSmall == test.expected[1], "legacy vibration PCM did not reach the existing motor bridge"); + Require(output.vibrationLarge == 0 && output.vibrationSmall == 0, "audio vibration replaced regular motor state"); + std::puts("legacy vibration bridge test passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp new file mode 100644 index 000000000000..c6d2803c6541 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp @@ -0,0 +1,235 @@ +#include "SDL.h" +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" +#include "prx/libScePad/include/PadState.hpp" +#include "prx/libScePad/include/PadAudioHapticQueue.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); +int APS5_VABI sceAudioOutClose(int); +int APS5_VABI sceAudioOutOutput(int, const void*); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); +int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); +} + +namespace { +constexpr std::uint32_t frames = 256; +bool available = true; +bool failOpen = false; +bool failInitialize = false; +bool failEnumeration = false; +bool pulse = false; +std::uint32_t frequency = 48000; +int opened = 0; +int closed = 0; +int queued = 0; +std::vector played; +struct Block { + int source; + std::uint64_t begin; + std::uint64_t end; + std::array rumble; +}; +std::vector blocks; +std::vector clears; +PadAudioHapticQueue timeline; + +void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +float Expected(std::int16_t sample, int volume, bool floating) { + const float value = sample / 32768.0f; + if (floating) return std::clamp(value * (volume / 32768.0f), -1.0f, 1.0f); + return static_cast(std::clamp(static_cast(sample) * volume / 32768, -32768, 32767)) / 32768.0f; +} + +void Run(std::uint32_t format, bool batch, int volume) { + const bool floating = format >= 3; + const std::uint32_t channels = format == 0 || format == 3 ? 1u : 2u; + const int handle = sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, format); + Require(handle == 1, "a closed or failed vibration port remained allocated"); + std::array levels{volume, volume / 2}; + Require(sceAudioOutSetVolume(handle, 3, levels.data()) == 0, "vibration gains failed"); + std::array integers{}; + std::array floats{}; + for (std::uint32_t frame = 0; frame < frames; ++frame) { + integers[frame * channels] = 8192; + floats[frame * channels] = 0.25f; + if (channels == 2) { + integers[frame * channels + 1] = -16384; + floats[frame * channels + 1] = -0.5f; + } + } + const void* data = floating ? static_cast(floats.data()) : static_cast(integers.data()); + AudioOutOutputParam outputs[2]{}; + outputs[0].handle = handle; + outputs[0].ptr = data; + outputs[1].handle = 9999; + outputs[1].ptr = data; + const int before = queued; + Require(sceAudioOutOutputs(outputs, 2) < 0 && queued == before, "an invalid batch queued a valid prefix"); + Require((batch ? sceAudioOutOutputs(outputs, 1) : sceAudioOutOutput(handle, data)) == static_cast(frames), "vibration frame count changed"); + Require(queued == before + 1, "native vibration did not reach the named controller device"); + const std::uint32_t deviceChannels = pulse ? 6u : 4u; + Require(played.size() == frames * deviceChannels, "native vibration frame layout changed"); + const auto left = Expected(8192, levels[0], floating); + const auto right = channels == 1 ? left : Expected(-16384, levels[1], floating); + for (std::uint32_t frame = 0; frame < frames; ++frame) { + for (std::uint32_t channel = 0; channel < deviceChannels; ++channel) { + const auto expected = channel == deviceChannels - 2 ? left : channel == deviceChannels - 1 ? right : 0.0f; + Require(played[frame * deviceChannels + channel] == expected, "vibration PCM or gain leaked into a speaker channel"); + } + } + std::uint64_t time = 0; + Require(sceAudioOutGetLastOutputTime(handle, &time) == 0 && time != 0, "vibration acceptance time was not recorded"); + Require(sceAudioOutOutput(handle, nullptr) == static_cast(frames) && queued == before + 1, "draining vibration queued samples"); + std::uint64_t after = 0; + Require(sceAudioOutGetLastOutputTime(handle, &after) == 0 && after == time, "a drain changed the last data time"); + Require(sceAudioOutClose(handle) == 0, "native vibration close failed"); +} + +int Submit(std::uint32_t count, std::uint32_t rate, bool split = false) { + const int handle = sceAudioOutOpen(0x10000000, 10, 0, count, rate, 1); + Require(handle > 0, "timed vibration port did not open"); + std::vector samples(static_cast(count) * 2); + for (std::uint32_t frame = 0; frame < count; ++frame) { + samples[frame * 2] = split && frame >= 256 ? 16384 : 8192; + samples[frame * 2 + 1] = split && frame >= 256 ? -8192 : -16384; + } + Require(sceAudioOutOutput(handle, samples.data()) == static_cast(count), "timed vibration output failed"); + return handle; +} + +void RunTiming() { + available = false; + const int shortPort = Submit(256, 48000); + Require(blocks.size() == 1 && blocks[0].end - blocks[0].begin == 5333 && blocks[0].rumble == std::array{128, 255}, "short block timing changed"); + const int longPort = Submit(4800, 24000); + Require(blocks.size() == 20 && blocks.back().end - blocks[1].begin == 200000, "long block duration did not follow frame count/rate"); + for (std::size_t index = 2; index < blocks.size(); ++index) Require(blocks[index].begin == blocks[index - 1].end, "successive grains overlap or leave a gap"); + auto model = timeline; + Require(model.Read(blocks[1].begin + 81000) == std::array{128, 255}, "a long block inherited the AudioOut2 timeout"); + Require(sceAudioOutOutput(shortPort, nullptr) == 256 && clears == std::vector{shortPort}, "a drain cleared another port"); + Require(sceKernelGetProcessTime() >= blocks[0].end, "a drain returned before the submitted block ended"); + Require(sceAudioOutClose(shortPort) == 0 && sceAudioOutClose(longPort) == 0, "timed port close failed"); + Require(clears == std::vector{shortPort, shortPort, longPort}, "close did not clear its own source"); + Require(timeline.Read(sceKernelGetProcessTime()) == std::array{}, "close retained a queued source"); + + const auto firstLong = blocks.size(); + const int drained = Submit(4800, 24000); + const auto end = blocks.back().end; + Require(blocks.size() == firstLong + 19, "long PCM was not divided into grains"); + Require(sceAudioOutOutput(drained, nullptr) == 4800 && sceKernelGetProcessTime() >= end, "long drain did not wait for its frame duration"); + Require(sceAudioOutClose(drained) == 0, "drained port close failed"); + + const auto firstSplit = blocks.size(); + const int split = Submit(512, 48000, true); + Require(blocks.size() == firstSplit + 2, "two-grain PCM was reduced to one value"); + Require(blocks[firstSplit].rumble == std::array{128, 255} && blocks.back().rumble == std::array{255, 128}, "grain-local energy was replaced by whole-block RMS"); + Require(sceAudioOutClose(split) == 0, "split port close failed"); + Require(opened == 0 && queued == 0, "virtual vibration opened a speaker device"); +} +} + +int InitializeSdlAudio_nid_no_patch() { return failInitialize ? -1 : 0; } +extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { + blocks.push_back({source, begin, end, {large, small}}); + timeline.Queue(source, begin, end, large, small); +} +extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source) { + clears.push_back(source); + timeline.Clear(source); +} +extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; } +extern "C" int SDLCALL SDL_GetNumAudioDevices(int) { return failEnumeration ? -1 : available ? 2 : 1; } +extern "C" const char* SDLCALL SDL_GetAudioDeviceName(int index, int) { return index == 0 ? "Main speakers" : "DualSense Wireless Controller"; } +extern "C" int SDLCALL SDL_GetAudioDeviceSpec(int index, int, SDL_AudioSpec* spec) { *spec = {}; spec->channels = index == 0 ? 2 : 4; return 0; } +extern "C" const char* SDLCALL SDL_GetCurrentAudioDriver() { return pulse ? "pulseaudio" : "dummy"; } +extern "C" const char* SDLCALL SDL_GetError() { return "test controller open failure"; } +extern "C" SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char* name, int capture, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int flags) { + Require(name != nullptr && std::string_view(name) == "DualSense Wireless Controller", "vibration opened the default speakers"); + Require(capture == 0 && flags == 0 && desired->callback == nullptr && desired->freq == static_cast(frequency) && desired->format == AUDIO_F32SYS && desired->channels == (pulse ? 6 : 4) && desired->samples == frames, "native controller request changed"); + if (failOpen) return 0; + ++opened; + *obtained = *desired; + return 42; +} +extern "C" void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID device, int) { Require(device == 42, "wrong native device paused"); } +extern "C" Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID device) { Require(device == 42, "wrong native queue queried"); return 0; } +extern "C" int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID device, const void* data, Uint32 bytes) { + Require(device == 42 && bytes % sizeof(float) == 0, "wrong vibration queue"); + const auto* samples = static_cast(data); + const std::size_t sampleCount = bytes / sizeof(float); + played.assign(samples, samples + sampleCount); + ++queued; + return 0; +} +extern "C" void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID device) { Require(device == 42, "wrong native queue cleared"); } +extern "C" void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID device) { Require(device == 42, "wrong native device closed"); ++closed; } + +int main(int argc, char** argv) { + try { + Require(argc == 2, "invalid test arguments"); + const std::string_view mode = argv[1]; + Require(mode == "quad" || mode == "pulse" || mode == "open_failure" || mode == "timing" || mode == "discovery_failure", "invalid device mode"); + pulse = mode == "pulse"; + Pad::Initialize(); + Pad::SetVibrationMode(1); + std::uint32_t seen = 0; + PadOutputState state{}; + static_cast(PadFetchOutput_nid_postfix(&seen, &state)); + if (mode == "timing") { + RunTiming(); + std::puts("legacy vibration device tests passed"); + return 0; + } + if (mode == "open_failure") { + failOpen = true; + bool threw = false; + try { static_cast(sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1)); } + catch (const std::runtime_error&) { threw = true; } + Require(threw && opened == 0 && queued == 0, "a failed native open silently changed routes"); + failOpen = false; + } + if (mode == "discovery_failure") { + for (bool* failure : {&failInitialize, &failEnumeration}) { + *failure = true; + bool threw = false; + try { static_cast(sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1)); } + catch (const std::runtime_error&) { threw = true; } + Require(threw && opened == 0 && queued == 0, "a discovery failure silently changed routes"); + *failure = false; + } + } + for (auto rate : {24000u, 48000u}) { + frequency = rate; + for (auto format : {0u, 1u, 3u, 4u}) + for (bool batch : {false, true}) + for (int volume : {0, 16384, 32768, 131072}) Run(format, batch, volume); + } + for (auto format : {2u, 5u, 6u, 7u}) { + bool threw = false; + try { static_cast(sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, format)); } + catch (const std::runtime_error&) { threw = true; } + Require(threw && opened == 64, "an unknown multichannel vibration layout was accepted"); + } + Require(opened == closed && opened == 64, "native vibration device ownership changed"); + Require(!PadFetchOutput_nid_postfix(&seen, &state), "native waveform output also sent motor approximation"); + std::puts("legacy vibration device tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp new file mode 100644 index 000000000000..75f961ebfcda --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp @@ -0,0 +1,70 @@ +#include "prx/libScePad/include/PadAudioHapticQueue.hpp" +#include + +static void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +template +static bool Throws(TAction action) { + try { action(); } + catch (const std::invalid_argument&) { return true; } + return false; +} + +int main() { + try { + { + PadAudioHapticQueue queue; + queue.Queue(1, 1000, 6333, 128, 255); + Require(queue.Read(999) == std::array{}, "a queued block began early"); + Require(queue.Read(1000) == std::array{128, 255}, "a block did not begin at its queued start"); + Require(queue.Read(6332) == std::array{128, 255}, "a block ended early"); + Require(queue.Read(6333) == std::array{}, "a short block inherited an 80 ms hold"); + } + { + PadAudioHapticQueue queue; + queue.Queue(1, 1000, 201000, 200, 100); + Require(queue.Read(81000) == std::array{200, 100}, "a long block expired after 80 ms"); + Require(queue.Read(200999) == std::array{200, 100}, "a long block ended before its frame duration"); + Require(queue.Read(201000) == std::array{}, "a long block outlived its frame duration"); + } + { + PadAudioHapticQueue queue; + queue.Queue(1, 100, 200, 200, 10); + queue.Queue(1, 200, 300, 0, 0); + queue.Queue(1, 400, 500, 20, 30); + Require(queue.Read(199) == std::array{200, 10}, "the first grain changed"); + Require(queue.Read(200) == std::array{}, "silent PCM retained an earlier peak"); + Require(queue.Read(350) == std::array{}, "a gap between blocks vibrated"); + Require(queue.Read(400) == std::array{20, 30}, "a later grain did not start"); + } + { + PadAudioHapticQueue queue; + queue.Queue(1, 0, 1000, 200, 10); + queue.Queue(2, 0, 1000, 20, 220); + Require(queue.Read(10) == std::array{200, 220}, "independent sources did not combine"); + queue.Clear(1); + Require(queue.Read(10) == std::array{20, 220}, "draining one source cleared another"); + queue.Clear(1); + queue.Clear(99); + Require(queue.Read(10) == std::array{20, 220}, "clearing an absent source changed output"); + queue.Clear(2); + Require(queue.Read(10) == std::array{}, "closing the final source retained output"); + } + { + PadAudioHapticQueue queue; + Require(Throws([&] { queue.Queue(0, 0, 1, 1, 1); }), "an invalid source was accepted"); + Require(Throws([&] { queue.Queue(1, 1, 1, 1, 1); }), "a zero-length interval was accepted"); + Require(Throws([&] { queue.Queue(1, 2, 1, 1, 1); }), "a reversed interval was accepted"); + queue.Queue(1, 0, 100, 10, 20); + Require(Throws([&] { queue.Queue(1, 99, 200, 1, 1); }), "overlapping source blocks were accepted"); + Require(queue.Read(50) == std::array{10, 20}, "a refused interval changed the active block"); + } + std::puts("legacy vibration timeline tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp b/core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp new file mode 100644 index 000000000000..2ad526fa00f1 --- /dev/null +++ b/core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp @@ -0,0 +1,48 @@ +#ifndef CORE_LIBS_PRX_LIBSCEPAD_PADAUDIOHAPTICQUEUE_HPP +#define CORE_LIBS_PRX_LIBSCEPAD_PADAUDIOHAPTICQUEUE_HPP + +#include +#include +#include +#include +#include +#include +#include + +class PadAudioHapticQueue { + struct Block { + std::uint64_t begin; + std::uint64_t end; + std::array rumble; + }; + std::map> sources; + +public: + void Queue(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { + if (source <= 0 || begin >= end) throw std::invalid_argument("Pad: invalid legacy haptic interval"); + auto& blocks = sources[source]; + if (!blocks.empty() && begin < blocks.back().end) throw std::invalid_argument("Pad: overlapping legacy haptic intervals"); + blocks.push_back({begin, end, {large, small}}); + } + + void Clear(int source) { sources.erase(source); } + + std::array Read(std::uint64_t now) { + std::array result{}; + for (auto source = sources.begin(); source != sources.end();) { + auto& blocks = source->second; + while (!blocks.empty() && blocks.front().end <= now) blocks.pop_front(); + if (blocks.empty()) { + source = sources.erase(source); + continue; + } + if (blocks.front().begin <= now) { + for (std::size_t channel = 0; channel < result.size(); ++channel) result[channel] = std::max(result[channel], blocks.front().rumble[channel]); + } + ++source; + } + return result; + } +}; + +#endif diff --git a/core/libs/prx/libScePad/include/PadState.hpp b/core/libs/prx/libScePad/include/PadState.hpp index c8c7d44e46aa..8186ee492a3a 100644 --- a/core/libs/prx/libScePad/include/PadState.hpp +++ b/core/libs/prx/libScePad/include/PadState.hpp @@ -69,5 +69,7 @@ extern "C" void PadPublishInput_nid_postfix(const PadInputState& input); extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error); extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out); extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small); +extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small); +extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source); #endif diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index f615a0ff5fc9..d95766a59f03 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -1,4 +1,5 @@ #include "prx/libScePad/include/PadState.hpp" +#include "prx/libScePad/include/PadAudioHapticQueue.hpp" #include "prx/libScePad/include/Pad.hpp" #include "prx/libScePad/include/InputCommandFile.hpp" #include "prx/libScePad/include/InputScript.hpp" @@ -23,6 +24,8 @@ namespace { constexpr std::uint64_t kLatchExpiry = 100000; PadOutputState output; std::chrono::steady_clock::time_point audioHapticEnd; + std::array audioHapticRumble{}; + PadAudioHapticQueue legacyHaptics; std::uint64_t timestamp = 0; struct QueuedInput { @@ -45,6 +48,17 @@ namespace { bool prevTouchActive[2] = {false, false}; std::uint8_t touchIds[2] = {0, 0}; + bool RefreshAudioHaptics() { + if (std::chrono::steady_clock::now() >= audioHapticEnd) audioHapticRumble = {}; + const auto legacy = legacyHaptics.Read(sceKernelGetProcessTime()); + const auto large = std::max(audioHapticRumble[0], legacy[0]); + const auto small = std::max(audioHapticRumble[1], legacy[1]); + if (output.audioVibrationLarge == large && output.audioVibrationSmall == small) return false; + output.audioVibrationLarge = large; + output.audioVibrationSmall = small; + return true; + } + void normalize(Quat& q) { const float n = std::sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z); if (n < 1e-6f) { q = Quat{}; return; } @@ -417,10 +431,7 @@ extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) { extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out) { std::lock_guard lock(stateMutex); - if ((output.audioVibrationLarge || output.audioVibrationSmall) && std::chrono::steady_clock::now() >= audioHapticEnd) { - output.audioVibrationLarge = output.audioVibrationSmall = 0; - ++output.sequence; - } + if (RefreshAudioHaptics()) ++output.sequence; if (seenSequence == nullptr || out == nullptr || *seenSequence == output.sequence) return false; *seenSequence = output.sequence; *out = output; @@ -431,9 +442,22 @@ extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8 std::lock_guard lock(stateMutex); if (large == 0 && small == 0) return; const auto now = std::chrono::steady_clock::now(); - if (now >= audioHapticEnd) output.audioVibrationLarge = output.audioVibrationSmall = 0; - output.audioVibrationLarge = std::max(output.audioVibrationLarge, large); - output.audioVibrationSmall = std::max(output.audioVibrationSmall, small); + if (now >= audioHapticEnd) audioHapticRumble = {}; + audioHapticRumble[0] = std::max(audioHapticRumble[0], large); + audioHapticRumble[1] = std::max(audioHapticRumble[1], small); audioHapticEnd = now + std::chrono::milliseconds(80); + static_cast(RefreshAudioHaptics()); ++output.sequence; } + +extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { + std::lock_guard lock(stateMutex); + legacyHaptics.Queue(source, begin, end, large, small); + if (RefreshAudioHaptics()) ++output.sequence; +} + +extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source) { + std::lock_guard lock(stateMutex); + legacyHaptics.Clear(source); + if (RefreshAudioHaptics()) ++output.sequence; +} diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index 61e824535699..ac71cc935371 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -323,6 +323,13 @@ int main() { Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); Require(snapshot.audioVibrationLarge == 100 && snapshot.audioVibrationSmall == 150); Require(snapshot.vibrationLarge == 12 && snapshot.vibrationSmall == 34); + PadQueueLegacyAudioHaptics_nid_postfix(1, 0, ~std::uint64_t{0}, 200, 20); + Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); + Require(snapshot.audioVibrationLarge == 200 && snapshot.audioVibrationSmall == 150); + PadClearLegacyAudioHaptics_nid_postfix(1); + Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); + Require(snapshot.audioVibrationLarge == 100 && snapshot.audioVibrationSmall == 150); + Require(snapshot.vibrationLarge == 12 && snapshot.vibrationSmall == 34); PadSubmitAudioHaptics_nid_postfix(0, 0); std::this_thread::sleep_for(std::chrono::milliseconds(100)); Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 01bb47513905..a480c3077bf7 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -18,6 +18,8 @@ - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the stereo fold is assumed, not the console's downmix: C at -3 dB into both sides, Ls/Rs, Lb/Rb and Ltf/Rtf at -3 dB and Ltb/Rtb at -6 dB into their own side, LFE dropped - [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented +- [Legacy vibration ports](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp): PPSA99902's [public input wrapper](https://github.com/Nazky/UnleashedRecomp-Prospero/blob/2ce067a1d51bb58670c7ee4a3aed893c504da805/UnleashedRecomp/hid/driver/sdl_hid.cpp#L316) opens type 10 with 256 stereo S16 or F32 frames at 48000 Hz. Mono duplicates its channel as AudioOut2 does; wider vibration layouts throw. Native controller output uses AudioOut2's four-channel layout, or its six-channel PulseAudio mapping. Without a controller sound card the existing RMS motor transform is applied to segments of at most 256 frames, scheduled by the legacy queue's frame count and rate; drain and close remove only that port's segments. Concurrent sources combine by the maximum per motor, and the pad consumer uses this approximation only for controllers other than DualSense. This host timing and motor model is not a console measurement or waveform reproduction; legacy native output is unmeasured on hardware, and user routing retains the single-controller limitation. AudioOut2 retains its separate 80 ms peak hold. + - [sceAudioOutDeviceIdOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA01317, which fails to load without it; declared without parameters, signature unknown. It opens a port on a given audio device; which device ids exist and how they map to host outputs is unknown, so it throws - [sceAudioOut2PortGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) and [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - the port state reports the channel count of the port's data format, output main, volume 127 and a zero reroute counter and flags. Taking the channel count from the data format follows [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs), another emulator. A payload process on a PS5 (firmware 13.60), which is not the foreground application, read output 0, volume -1 and reroute counter 5 for its main ports, and 6 channels for an 8-channel port on a 5.1 system, so a title's values are unverified, including the output of a pad port, the volume after `sceAudioOut2PortSetAttributes` and the reroute counter on a device change. The speaker info reports type 0 with available bits `0x3`, the front left speaker at +30 degrees and the front right one at -30, and -360 for the azimuth and elevation of every other slot. On that console the type followed the HDMI device type and the available bits its channels: type 0 with the device set to TV, 1 to AV amplifier and 2 to sound bar, which [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs) names television, AV receiver and sound bar (3 is headphones there), and available bits `0x3` for 2 channels and `0x3F` for 5.1 (centre at 0, surround pair at +110 and -110, -360 for the LFE). The front pair and the empty slots read the same in every setting; 7.1 on the AV amplifier reported 2 channels on that console's HDMI chain. The host output is always reported as a stereo television here. The console also returns `0x80268006` before `sceAudioOut2Initialize`, `0x80268001` for flags above 1 and `0x8026800C` for a null pointer, which are not modelled. ASTRO BOT and ASTRO's PLAYROOM reportedly stop on an assertion that depends on these values (#4317), but which field matters has not been isolated From e3c37ac0d5c6fa3d8ec8ea587ce43cc2b26a84ea Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:17:54 +0200 Subject: [PATCH 084/176] test(libkernel): cover direct replacement flexible budget (cherry picked from commit 8627ac796583c9db7694c8c324546d39b7bd7016) --- core/libs/tests/GuestFlexibleMemoryBudget.cpp | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/core/libs/tests/GuestFlexibleMemoryBudget.cpp b/core/libs/tests/GuestFlexibleMemoryBudget.cpp index ced1a258d5dc..6bb0a34e2a64 100644 --- a/core/libs/tests/GuestFlexibleMemoryBudget.cpp +++ b/core/libs/tests/GuestFlexibleMemoryBudget.cpp @@ -22,6 +22,14 @@ std::uint64_t GuestImageSegmentBytes_nid_no_patch(); void* APS5_VABI dlopen_nid_postfix(const char*, int); char* APS5_VABI dlerror_nid_postfix(); int APS5_VABI dlclose_nid_postfix(void*); +std::size_t APS5_VABI sceKernelGetDirectMemorySize(); +int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); +int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMapDirectMemory2(void**, std::size_t, int, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMapNamedDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t, const char*); +int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); +int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); +int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -122,6 +130,57 @@ int main(int argc, char** argv) { Require(sceKernelMapFlexibleMemory(&extra, PS5_PAGE_SIZE, 3, 0) == SCE_KERNEL_ERROR_ENOMEM && extra == nullptr); Require(sceKernelReleaseFlexibleMemory(full, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); + for (int api = 0; api < 4; ++api) { + constexpr auto directPage = PS5_PAGE_SIZE; + const auto checkDirectBudget = [&](std::size_t expected) { + std::size_t current = 0; + Require(sceKernelAvailableFlexibleMemorySize(¤t) == 0); + std::printf("Direct replacement API=%d available=%zu expected=%zu\n", api, current, expected); + std::fflush(stdout); + Require(current == expected); + }; + void* original = nullptr; + Require(sceKernelMapFlexibleMemory(&original, directPage * 4, 3, 0) == 0); + auto* bytes = static_cast(original); + bytes[0] = 17; + bytes[directPage * 4 - 1] = 29; + bytes[directPage] = 37; + bytes[directPage * 3 - 1] = 41; + std::int64_t physical = -1; + Require(sceKernelAllocateDirectMemory(0, sceKernelGetDirectMemorySize(), directPage * 2, directPage, 0, &physical) == 0); + const auto mapDirect = [&](void*& address, int flags) { + if (api == 0) return sceKernelMapDirectMemory(&address, directPage * 2, 3, flags, physical, directPage); + if (api == 1) return sceKernelMapDirectMemory2(&address, directPage * 2, 0, 3, flags, physical, directPage); + if (api == 2) return sceKernelMapNamedDirectMemory(&address, directPage * 2, 3, flags, physical, directPage, "budget direct"); + KernelBatchMapEntry entry{address, static_cast(physical), directPage * 2, 3, 0, 0, 0}; + int processed = -1; + const int result = sceKernelBatchMap2(&entry, 1, &processed, flags); + Require(processed == (result == 0 ? 1 : 0)); + address = entry.start; + return result; + }; + void* middle = static_cast(original) + directPage; + Require(mapDirect(middle, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(middle == static_cast(original) + directPage); + checkDirectBudget(before - directPage * 4); + Require(bytes[0] == 17 && bytes[directPage * 4 - 1] == 29); + Require(bytes[directPage] == 37 && bytes[directPage * 3 - 1] == 41); + Require(mapDirect(middle, 0x10) == 0 && middle == static_cast(original) + directPage); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(middle, 0, &info, sizeof(info)) == 0 && info.is_direct && !info.is_flexible); + Require(bytes[0] == 17 && bytes[directPage * 4 - 1] == 29); + checkDirectBudget(before - directPage * 2); + const auto recovered = before - directPage * 2; + void* reused = nullptr; + Require(sceKernelMapFlexibleMemory(&reused, recovered, 3, 0) == 0); + static_cast(reused)[recovered - 1] = 31; + Require(sceKernelReleaseFlexibleMemory(reused, recovered) == 0); + checkDirectBudget(before - directPage * 2); + Require(sceKernelReleaseDirectMemory(physical, directPage * 2) == 0); + checkDirectBudget(before - directPage * 2); + Require(sceKernelReleaseFlexibleMemory(original, directPage * 4) == 0); + checkDirectBudget(before); + } } if (module) { Require(dlclose_nid_postfix(module) == 0); From 84b9d954c95ec9c8fe85bdb18ed4bb558c2bad66 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:23:30 +0200 Subject: [PATCH 085/176] fix(libkernel): account for fixed flexible memory replacements (cherry picked from commit 393506c161a5eb8b1855e6fa77d73c3b56c8a395) --- .../prx/libkernel/DirectMemory/Export.cpp | 15 ++++- core/libs/tests/GuestFlexibleMemoryBudget.cpp | 60 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 8f1306a48744..8991e4dd516c 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -127,7 +127,20 @@ int _mapFlexible(void** addr, size_t len, int prot, int flags) { const size_t configured = _configuredFlexibleSize(); const size_t images = GuestImageSegmentBytes_nid_no_patch(); std::lock_guard lock(g_flexibleLock); - const size_t used = images + _flexibleUsedLocked(); + size_t replaced = 0; + constexpr int MapFixed = 0x10; + constexpr int MapNoOverwrite = 0x80; + if (addr != nullptr && *addr != nullptr && (flags & (MapFixed | MapNoOverwrite)) == MapFixed) { + const auto start = reinterpret_cast(*addr); + if (len <= std::numeric_limits::max() - start) { + const auto end = start + len; + for (const auto& [rangeStart, rangeLength] : g_flexibleRanges) { + const auto rangeEnd = rangeStart + rangeLength; + if (rangeStart < end && rangeEnd > start) replaced += std::min(end, rangeEnd) - std::max(start, rangeStart); + } + } + } + const size_t used = images + _flexibleUsedLocked() - replaced; if (len > configured - std::min(configured, used)) { std::fprintf(stderr, "[memory] flexible memory exhausted: request 0x%zx with 0x%zx of 0x%zx in use\n", len, used, configured); return SCE_KERNEL_ERROR_ENOMEM; diff --git a/core/libs/tests/GuestFlexibleMemoryBudget.cpp b/core/libs/tests/GuestFlexibleMemoryBudget.cpp index 6bb0a34e2a64..cba381e73422 100644 --- a/core/libs/tests/GuestFlexibleMemoryBudget.cpp +++ b/core/libs/tests/GuestFlexibleMemoryBudget.cpp @@ -123,11 +123,71 @@ int main(int argc, char** argv) { Require(sceKernelMapFlexibleMemory(&oversized, before + PS5_PAGE_SIZE, 3, 0) == SCE_KERNEL_ERROR_ENOMEM && oversized == nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); if (mode == "limited") { + const auto checkBudget = [&](std::size_t expected) { + std::size_t current = 0; + Require(sceKernelAvailableFlexibleMemorySize(¤t) == 0); + std::printf("Fixed flexible mapping: available=%zu expected=%zu\n", current, expected); + std::fflush(stdout); + Require(current == expected); + }; + constexpr auto page = PS5_PAGE_SIZE; + void* original = nullptr; + Require(sceKernelMapFlexibleMemory(&original, page * 4, 3, 0) == 0); + auto* bytes = static_cast(original); + for (std::size_t i = 0; i < 4; ++i) bytes[i * page] = static_cast(17 + i); + auto* middle = static_cast(original) + page; + void* replacement = middle; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == middle); + Require(bytes[0] == 17 && bytes[page] == 0 && bytes[page * 2] == 19 && bytes[page * 3] == 20); + checkBudget(before - page * 4); + bytes[page] = 23; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(bytes[page] == 23); + checkBudget(before - page * 4); + replacement = original; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == original); + Require(bytes[0] == 0 && bytes[page] == 23 && bytes[page * 3] == 20); + checkBudget(before - page * 4); + Require(sceKernelMapFlexibleMemory(&replacement, page * 3, 3, 0x10) == 0 && replacement == original); + Require(bytes[0] == 0 && bytes[page] == 0 && bytes[page * 2] == 0 && bytes[page * 3] == 20); + checkBudget(before - page * 4); + Require(sceKernelReleaseFlexibleMemory(middle, page * 2) == 0); + checkBudget(before - page * 2); + replacement = middle; + Require(sceKernelMapFlexibleMemory(&replacement, page * 3, 3, 0x10) == 0 && replacement == middle); + Require(bytes[page] == 0 && bytes[page * 3] == 0); + checkBudget(before - page * 4); + Require(sceKernelReleaseFlexibleMemory(original, page * 4) == 0); + checkBudget(before); const auto length = before & ~(PS5_PAGE_SIZE - 1); void* full = nullptr; Require(sceKernelMapFlexibleMemory(&full, length, 3, 0) == 0); void* extra = nullptr; Require(sceKernelMapFlexibleMemory(&extra, PS5_PAGE_SIZE, 3, 0) == SCE_KERNEL_ERROR_ENOMEM && extra == nullptr); + static_cast(full)[0] = 31; + replacement = full; + Require(sceKernelMapFlexibleMemory(&replacement, length, 3, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(static_cast(full)[0] == 31); + checkBudget(before - length); + auto* fullBytes = static_cast(full); + fullBytes[page] = 37; + fullBytes[length - page] = 41; + auto* fullMiddle = static_cast(full) + page; + replacement = fullMiddle; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == fullMiddle); + Require(fullBytes[0] == 31 && fullBytes[page] == 0 && fullBytes[length - page] == 41); + checkBudget(before - length); + replacement = static_cast(full) + length - page; + Require(sceKernelMapFlexibleMemory(&replacement, page * 2, 3, 0x10) == SCE_KERNEL_ERROR_ENOMEM); + Require(fullBytes[0] == 31 && fullBytes[length - page] == 41); + checkBudget(before - length); + replacement = full; + Require(sceKernelMapFlexibleMemory(&replacement, length + page, 3, 0x10) == SCE_KERNEL_ERROR_ENOMEM); + Require(static_cast(full)[0] == 31); + checkBudget(before - length); + Require(sceKernelMapFlexibleMemory(&replacement, length, 3, 0x10) == 0 && replacement == full); + Require(static_cast(full)[0] == 0); + checkBudget(before - length); Require(sceKernelReleaseFlexibleMemory(full, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); for (int api = 0; api < 4; ++api) { From d4043de8409aad71ea354b1c7c4bb4ef97b51226 Mon Sep 17 00:00:00 2001 From: cadenasynyster <8373765+cadenasynyster@users.noreply.github.com> Date: Sun, 18 Oct 2026 11:41:56 +0200 Subject: [PATCH 086/176] feat(libc): implement std::__throw_bad_variant_access Onimusha: Way of the Sword (PPSA27836 01.000.001) imports _ZSt26__throw_bad_variant_accessv (9G6z-nF6njg) from libc, and its bundled libc.prx does not export it, so the title fails to load with an unresolved import. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GtXavMkgCTCdmRpweviHcQ (cherry picked from commit 9f3e867eec3e4c1195c8c4a581fa841371816a50) --- core/libs/prx/libc/src/exception/StandardExceptions.cpp | 4 ++++ core/libs/tests/ExceptionRuntime.cpp | 4 ++++ docs/dev/debt/libc.md | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index e6fd4e1f2368..2a4ad4aff429 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -93,6 +93,7 @@ const char* PlainWhat(const ExceptionObject* object) { if (std::strcmp(name, "St17bad_function_call") == 0) return "std::bad_function_call"; if (std::strcmp(name, "St12bad_weak_ptr") == 0) return "bad_weak_ptr"; if (std::strcmp(name, "St19bad_optional_access") == 0) return "bad optional access"; + if (std::strcmp(name, "St18bad_variant_access") == 0) return "bad_variant_access"; return name; } const char* MessageWhat(const ExceptionObject* object) { return object->message ? object->message : ""; } @@ -342,6 +343,8 @@ void APS5_VABI _ZNSt19bad_optional_accessD0Ev_nid_postfix(LibcException::Excepti const char* APS5_VABI _ZNKSt19bad_optional_access4whatEv_nid_postfix(const LibcException::ExceptionObject* self) { return LibcException::PlainWhat(self); } void APS5_VABI _ZNSt19bad_optional_accessC1Ev_nid_postfix(LibcException::ExceptionObject* self) { self->vtable = &_ZTVSt19bad_optional_access_nid_postfix.destroy; } void APS5_VABI _ZNSt19bad_optional_accessC2Ev_nid_postfix(LibcException::ExceptionObject* self) { self->vtable = &_ZTVSt19bad_optional_access_nid_postfix.destroy; } +LibcException::TypeRecord _ZTISt18bad_variant_access_nid_postfix {LibcException::SingleTypeVtable + 2, "St18bad_variant_access", &_ZTISt9exception_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt18bad_variant_access_nid_postfix {0, &_ZTISt18bad_variant_access_nid_postfix, LibcException::GuestDestroyPlain_nid_no_patch, LibcException::GuestDeletePlain_nid_no_patch, LibcException::GuestPlainWhat_nid_no_patch}; LibcException::TypeRecord _ZTISt11logic_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11logic_error", &_ZTISt9exception_nid_postfix}; LibcException::ExceptionVtable _ZTVSt11logic_error_nid_postfix {0, &_ZTISt11logic_error_nid_postfix, LibcException::GuestDestroyMessage_nid_no_patch, LibcException::GuestDeleteMessage_nid_no_patch, LibcException::GuestMessageWhat_nid_no_patch}; @@ -479,6 +482,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt19_Throw_bad_weak_ptrv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt12bad_weak_ptr_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt26_Throw_bad_optional_accessv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt19bad_optional_access_nid_postfix); } +[[noreturn]] void APS5_VABI _ZSt26__throw_bad_variant_accessv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt18bad_variant_access_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code) { struct RegexObject { LibcException::ExceptionObject base; std::regex_constants::error_type code; }; auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(RegexObject))); diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 42079527dade..b3d327330876 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); extern "C" [[noreturn]] void _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix(); extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); extern "C" [[noreturn]] void _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); +extern "C" [[noreturn]] void _ZSt26__throw_bad_variant_accessv_nid_postfix(); extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); extern "C" void __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, void(*)(void*), void(*)(void*, std::size_t)); @@ -250,6 +252,8 @@ int main() { catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); std::abort(); } catch (const std::bad_weak_ptr& value) { if (std::strcmp(value.what(), "bad_weak_ptr") != 0) std::abort(); } + try { _ZSt26__throw_bad_variant_accessv_nid_postfix(); std::abort(); } + catch (const std::bad_variant_access& value) { if (std::strcmp(value.what(), "bad_variant_access") != 0) std::abort(); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } catch (const std::invalid_argument&) {} try { _ZNKSt9exception6_RaiseEv_nid_postfix(&object); assert(false); } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 6ff6985aa83b..70fff12d58a1 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -89,6 +89,10 @@ - [%p and %lp](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) follow the console's `libSceLibcInternal.sprx` measured from a homebrew payload on firmware 12.70 ([#7549](https://github.com/boykopovar/AnyPS5/issues/7549)), not FreeBSD: bare lowercase hex with no `0x` prefix even under `#`, `0` for a null pointer, `+` and the space flag ignored, the width counting only the digits, `-` padding on the right, `0` padding with zeros when there is no precision, the precision as the minimum number of digits, nothing for a null pointer under `%.0p`, and `l` accepted. Only `snprintf` and `swprintf` of that module were measured, from a payload process rather than a title process, so a width beside `%.0p`, the `0` flag beside a precision (which C17 ignores) and `%.0p` of a non-null pointer follow this formatter's own rules and are not measured; `swprintf` was not run with `%lp`, so the wide family still refuses that conversion and returns -1. Linux routes `%p` and `%lp` to this formatter because glibc prints the FreeBSD form and `(nil)` for a null pointer, which means a format that combines `%p` with a conversion this formatter refuses, such as an argument position above 99, throws there instead of reaching glibc - [collate and collate _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose `compare`, `transform` and `hash` take the guest's 16-bit characters, so the length of a range is a count of characters: `compare` orders by 16-bit value (the C locale, as Dinkumware's `char_traits::compare` does), `transform` copies the characters into the result as the guest's bytes and sizes the result in bytes (two per character, a 16-bit terminator, the 7 characters that still fit next to it and `length * 2 | 15` plus two bytes of storage beyond that), and `hash` mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's `libc.prx` exports both `_Getcat` (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked `libc.prx` is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either `_Getcat`, so neither has been exercised by a title. - [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, on Linux reads the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`) in the guest scanner instead of glibc, whose `wchar_t` is 32 bits: each input byte is stored as one 16-bit unit of the same value, as the guest's C-locale `mbrtowc` converts it, and a format without them still goes to the host. `sscanf_s` and `vsscanf_s` take this path on both platforms; Windows hands `%ls`, `%lc` and `%l[` of `sscanf`, `vsscanf` and `fscanf` to the host, whose `wchar_t` is 16 bits. A format that reaches the guest scanner throws for a conversion outside C17's (`%2$C`, `%'ls`). Bytes above 0x7F and locales other than C are not measured on the console +- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte `wcrtomb`; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments or `%m`, so a narrow `%p` and a NaN there still follow the host (glibc prints a null pointer as `(nil)`, where FreeBSD prints `0x0`, and a NaN with its sign, `-nan`, and with the `+` and space flags, where FreeBSD prints `nan`); argument positions above 99 (FreeBSD's `NL_ARGMAX`) throw when a format requires the guest formatter +- [collate and collate _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose `compare`, `transform` and `hash` take the guest's 16-bit characters, so the length of a range is a count of characters: `compare` orders by 16-bit value (the C locale, as Dinkumware's `char_traits::compare` does), `transform` copies the characters into the result and records size and capacity in 16-bit elements (inline capacity 7, heap capacity `length | 7`, `(capacity + 1) * 2` bytes including the terminator), following Dinkumware's [VC12 `basic_string`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xstring#L500-L531), and `hash` mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's `libc.prx` exports both `_Getcat` (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked `libc.prx` is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either `_Getcat`, so neither has been exercised by a title. +- [std::__throw_bad_variant_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) throws `std::bad_variant_access` with the `what()` string `bad_variant_access`, the one [LLVM libc++](https://github.com/llvm/llvm-project/blob/main/libcxx/src/variant.cpp) returns, since the export carries libc++'s `__throw_bad_variant_access` name; the console's string is unverified. Onimusha: Way of the Sword (PPSA27836 01.000.001) imports it from `libc`, and its bundled `libc.prx` does not export it. +- [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, on Linux throws for the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`): glibc would store a 32-bit `wchar_t` per character into the guest's 16-bit buffer. Windows hands `%ls`, `%lc` and `%l[` to the host, whose `wchar_t` is 16 bits - [The scanf family](../../../core/libs/prx/libc/include/WindowsScanning.hpp) (libc) on Windows returns 0, as C17 and FreeBSD do, when input fails after only suppressed conversions have completed (`sscanf("12", "%*d%d", &value)`). Linux hands the format to glibc, which returns `EOF` there. On Windows the host stops a `%x`, `%X`, `%i` or `%p` after the `x` of a `0x` that no hex digit follows (or that the width cuts off), and a float conversion (`%e`, `%f`, `%g`, `%a`) after the `e` of an exponent with no digits (`1e`), where C17 and FreeBSD stop before the `x` or the `e` - [Guest path resolution](../../../core/libs/prx/libc/src/General.cpp) falls back to ASCII case-insensitive component lookup, following [shadPS4's host filesystem](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/file_sys/backends/host_fs.cpp). PPSA03747 requests `Skyrim.INI`, `Data/Video` and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied. Explicit native Windows drive and UNC paths bypass guest parent-component validation.. A relinked module's `.guest.prx` is matched the same way when the original module is not beside it: Borderlands 4 (PPSA01494) loads `/app0/prx/libRenoirCore.PS5.prx`, which it ships as `librenoircore.ps5.prx` - [tzname](../../../core/libs/prx/libc/src/Time.cpp) (libc) points at the host's zone names, read at load and again by `tzset`: the abbreviations a `TZ` string gives (`EST5EDT` is `EST` and `EDT` on both hosts), but on Windows without `TZ` the CRT's full names such as `GMT Standard Time`, which ICU's [uprv_tzname](https://github.com/unicode-org/icu/blob/457157a92aa053e632cc7fcfd0e12f8a943b2d11/icu4c/source/common/putil.cpp#L1244-L1264) (ES-DE in PPSA99169) cannot map to a zone ID. A zone with no daylight-saving time has `tzname[1]` empty on Windows and equal to `tzname[0]` on Linux, where FreeBSD leaves three spaces. The console's names are not known From 7c21928275694d9125912cbeeb9943ee6a4a7cb3 Mon Sep 17 00:00:00 2001 From: Hussein Kadhim Date: Fri, 16 Oct 2026 23:04:55 +0300 Subject: [PATCH 087/176] fix(libkernel): reject alarm seconds beyond the itimer range FreeBSD alarm() is setitimer(ITIMER_REAL), and setitimer fails with EINVAL for it_value.tv_sec above INT32_MAX / 2, so alarm() returns -1 and leaves the pending alarm alone. alarm(UINT_MAX) armed an alarm decades away and returned the old remaining time instead. (cherry picked from commit 2dea16d07d4e0b36e4a3d1eebe3b5c3ffb739807) (cherry picked from commit b3522206cb8d61f6ee7adb26df6d39e95b8a57ae) --- core/libs/prx/libkernel/System/src/Signals.cpp | 6 +++++- core/libs/tests/GuestGetitimer.cpp | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 36678ba19659..def7bb030fad 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -271,6 +271,7 @@ extern "C" int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* } namespace { +constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2; struct AlarmState { std::mutex lock; std::condition_variable changed; @@ -347,6 +348,10 @@ void NotifyAlarms(AlarmState& alarms) { } extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) { + if (seconds > MaxItimerSeconds) { + *__error_nid_postfix() = 22; + return static_cast(-1); + } auto& alarms = Alarms(); std::lock_guard lock(alarms.lock); const auto now = std::chrono::steady_clock::now(); @@ -399,7 +404,6 @@ extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output namespace { constexpr int ItimerReal = 0; constexpr int ItimerProf = 2; -constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2; constexpr std::int64_t MinExactIntervalMicros = 1000; bool ValidItimer(const KernelTimeval& time) { diff --git a/core/libs/tests/GuestGetitimer.cpp b/core/libs/tests/GuestGetitimer.cpp index 04f037f4997a..1fa5f825167e 100644 --- a/core/libs/tests/GuestGetitimer.cpp +++ b/core/libs/tests/GuestGetitimer.cpp @@ -86,9 +86,18 @@ int main() { Require(GuestAlarm_nid_no_patch(0) > 0, "getitimer cancelled the alarm"); Require(ReadRemaining() == 0, "cancelled alarm is not zero"); - GuestAlarm_nid_no_patch(std::numeric_limits::max()); - Require(ReadRemaining() > static_cast(std::numeric_limits::max()) * 1000000, - "long alarm was truncated to signed 32 bits"); + constexpr unsigned int longestAlarm = std::numeric_limits::max() / 2; + Require(GuestAlarm_nid_no_patch(longestAlarm) == 0, "alarm was already active"); + Require(ReadRemaining() > static_cast(longestAlarm - 1) * 1000000, + "long alarm was truncated"); + for (unsigned int invalid : {longestAlarm + 1, static_cast(std::numeric_limits::max()), std::numeric_limits::max()}) { + *__error_nid_postfix() = 0; + Require(GuestAlarm_nid_no_patch(invalid) == std::numeric_limits::max() && *__error_nid_postfix() == 22, + "an alarm beyond the itimer range did not fail with EINVAL"); + Require(ReadRemaining() > static_cast(longestAlarm - 1) * 1000000, + "a rejected alarm replaced the pending one"); + } + Require(GuestAlarm_nid_no_patch(0) == longestAlarm, "a rejected alarm changed the remaining time"); GuestAlarm_nid_no_patch(1); std::this_thread::sleep_for(std::chrono::milliseconds(1100)); Require(ReadRemaining() == 0, "expired alarm is not zero"); From d53070ad79b63a7816c8adfe014dcc773fb0e11a Mon Sep 17 00:00:00 2001 From: ionut27001 <194206737+ionut27001@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:54:37 +0300 Subject: [PATCH 088/176] fix(libkernel): answer sysarch I386_GET_FSBASE, I386_GET_GSBASE and undefined operations as measured on a PS5 In a payload process on a PS5 (firmware 12.70), I386_GET_FSBASE (7) and I386_GET_GSBASE (9) wrote 4 bytes, the low 32 bits of the FS and GS bases, and failed with EFAULT for a null argument; both threw here. On Linux x86-64 hosts they now do the same. The nine operation numbers tried that FreeBSD 11's amd64 sysarch has no case for (2, 5, 6, 12, 127, 133, 134, 1000 and -1), each with a null argument, failed with EINVAL there, as its default case does, and threw here. Every such number now returns EINVAL on every host. Threads made with pthread_create and scePthreadCreate started with their creator's GS base there; the test now checks that a guest thread does so on Linux. The sysarch debt entry records these measurements and what AMD64_GET_XFPUSTATE and I386_GET_XFPUSTATE returned there; both still throw here. (cherry picked from commit bd22a701f5a63f7e1bfcbdd042beaa508e3026d2) --- .../libs/prx/libkernel/System/src/Process.cpp | 18 ++++++++- core/libs/tests/GuestSysarch.cpp | 40 ++++++++++++++++++- docs/dev/debt/libkernel.md | 2 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index eb917d95f44c..2a1981abbf7d 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -463,11 +463,19 @@ int APS5_VABI sched_get_priority_min_nid_postfix(int policy) { } int APS5_VABI sysarch(int operation, void* parameters) { + constexpr int i386GetFsBase = 7; + constexpr int i386GetGsBase = 9; constexpr int getFsBase = 128; constexpr int getGsBase = 130; constexpr int setGsBase = 131; constexpr std::uint64_t userAddressEnd = 0x0000800000000000ull; - if (operation != getFsBase && operation != getGsBase && operation != setGsBase) + constexpr std::array definedOperations{0, 1, 3, 4, 7, 8, 9, 10, 11, 128, 129, 130, 131, 132}; + if (std::find(definedOperations.begin(), definedOperations.end(), operation) == definedOperations.end()) { + *__error_nid_postfix() = guestInvalid; + return -1; + } + const bool i386 = operation == i386GetFsBase || operation == i386GetGsBase; + if (!i386 && operation != getFsBase && operation != getGsBase && operation != setGsBase) NotImplemented_nid_no_patch(("sysarch: operation " + std::to_string(operation)).c_str()); #if defined(__linux__) && defined(__x86_64__) if (parameters == nullptr) { @@ -484,8 +492,14 @@ int APS5_VABI sysarch(int operation, void* parameters) { if (::syscall(SYS_arch_prctl, ARCH_SET_GS, base) != 0) throw std::system_error(errno, std::generic_category(), "sysarch: setting the GS base"); return 0; } - if (::syscall(SYS_arch_prctl, operation == getFsBase ? ARCH_GET_FS : ARCH_GET_GS, &base) != 0) + const bool fs = operation == getFsBase || operation == i386GetFsBase; + if (::syscall(SYS_arch_prctl, fs ? ARCH_GET_FS : ARCH_GET_GS, &base) != 0) throw std::system_error(errno, std::generic_category(), "sysarch: reading a segment base"); + if (i386) { + const auto low = static_cast(base); + std::memcpy(parameters, &low, sizeof(low)); + return 0; + } std::memcpy(parameters, &base, sizeof(base)); return 0; #else diff --git a/core/libs/tests/GuestSysarch.cpp b/core/libs/tests/GuestSysarch.cpp index 7167b5003fe5..f2c4551138a7 100644 --- a/core/libs/tests/GuestSysarch.cpp +++ b/core/libs/tests/GuestSysarch.cpp @@ -1,13 +1,16 @@ -#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" #include #include +#include #include #include extern "C" { int APS5_VABI sysarch(int number, void* args); int* APS5_VABI __error_nid_postfix(); +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); } static void Require(bool value) { if (!value) std::abort(); } @@ -21,9 +24,31 @@ template static std::string Thrown(TCall call) { return {}; } +#if defined(__linux__) && defined(__x86_64__) +static void* APS5_VABI ReadGsBase(void* arg) { + auto* read = static_cast(arg); + if (sysarch(130, read) != 0) *read = 0; + return nullptr; +} + +static void RequireLowHalf(int operation, std::uint64_t base) { + unsigned char bytes[8]; + std::memset(bytes, 0xaa, sizeof(bytes)); + Require(sysarch(operation, bytes) == 0); + std::uint32_t low = 0; + std::memcpy(&low, bytes, sizeof(low)); + Require(low == static_cast(base)); + for (int i = 4; i < 8; ++i) Require(bytes[i] == 0xaa); +} +#endif + int main() { Require(Thrown([] { sysarch(129, nullptr); }) == "sysarch: operation 129 not implemented"); - Require(Thrown([] { sysarch(7, nullptr); }) == "sysarch: operation 7 not implemented"); + Require(Thrown([] { sysarch(132, nullptr); }) == "sysarch: operation 132 not implemented"); + for (const int operation : {2, 5, 6, 12, 127, 133, 134, 1000, -1}) { + *__error_nid_postfix() = 0; + Require(sysarch(operation, nullptr) == -1 && *__error_nid_postfix() == 22); + } #if defined(__linux__) && defined(__x86_64__) static std::uint64_t slots[2] = {0x1234567890abcdefull, 0}; std::uint64_t base = reinterpret_cast(slots); @@ -38,6 +63,16 @@ int main() { std::uint64_t self = 0; asm volatile("movq %%fs:0, %0" : "=r"(self)); Require(self == fs); + RequireLowHalf(7, fs); + RequireLowHalf(9, base); + *__error_nid_postfix() = 0; + Require(sysarch(7, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(sysarch(9, nullptr) == -1 && *__error_nid_postfix() == 14); + std::uint64_t inherited = 0; + Pthread child = nullptr; + Require(scePthreadCreate(&child, nullptr, ReadGsBase, &inherited, "sysarch_gs") == 0); + Require(scePthreadJoin(child, nullptr) == 0 && inherited == base); std::uint64_t outside = 0x0000800000000000ull; *__error_nid_postfix() = 0; Require(sysarch(131, &outside) == -1 && *__error_nid_postfix() == 22); @@ -50,6 +85,7 @@ int main() { #else std::uint64_t base = 0; Require(Thrown([&base] { sysarch(130, &base); }) == "sysarch: segment bases on this host not implemented"); + Require(Thrown([&base] { sysarch(7, &base); }) == "sysarch: segment bases on this host not implemented"); #endif return 0; } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index ac68175d01d4..356aae3a8a65 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -161,7 +161,7 @@ - [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`. - [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it. - [execve](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws like `execvp`: replacing the process image is not supported. The console behavior for invalid arguments and executable path lookups is not modeled. -- [sysarch](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `AMD64_GET_FSBASE`, `AMD64_GET_GSBASE` and `AMD64_SET_GSBASE` follow FreeBSD 11's amd64 [`sysarch`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c) on Linux x86-64 hosts, through the host's `arch_prctl`. The GS base belongs to the calling thread, a base at or above `0x800000000000` (`VM_MAXUSER_ADDRESS`) returns `EINVAL`, and a null argument returns `EFAULT`. `AMD64_SET_FSBASE` throws, since guest and host code share the thread's FS base for their TLS. The other operations, and every operation on other hosts (Windows keeps its TEB in GS), throw. What a new thread's GS base starts as is not modelled: Linux threads inherit their creator's +- [sysarch](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `AMD64_GET_FSBASE`, `AMD64_GET_GSBASE` and `AMD64_SET_GSBASE` follow FreeBSD 11's amd64 [`sysarch`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c) on Linux x86-64 hosts, through the host's `arch_prctl`. The GS base belongs to the calling thread, a base at or above `0x800000000000` (`VM_MAXUSER_ADDRESS`) returns `EINVAL`, and a null argument returns `EFAULT`. `AMD64_SET_FSBASE` throws, since guest and host code share the thread's FS base for their TLS. In a payload process on a PS5 (firmware 12.70), `I386_GET_FSBASE` (7) and `I386_GET_GSBASE` (9) wrote 4 bytes, the low 32 bits of the FS and GS bases, and failed with `EFAULT` for a null argument; here they do the same on Linux x86-64 hosts. There a GS base set by one thread left the main thread's unchanged, and threads made with `pthread_create` and `scePthreadCreate` started with their creator's GS base; here a guest thread's host thread is created by the creating thread, and Linux's [`copy_thread`](https://github.com/torvalds/linux/blob/v6.6/arch/x86/kernel/process.c#L182-L186) gives a new thread its creator's GS base. The operation numbers FreeBSD 11's amd64 `sysarch` has no case for returned `EINVAL` there for the nine tried, each with a null argument (2, 5, 6, 12, 127, 133, 134, 1000 and -1; 6 is `I386_VM86`, which its header names), as its [default case](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c#L324-L325) does; here every such number returns `EINVAL` on every host; the numbers other than those nine follow that default case and were not measured. `AMD64_GET_XFPUSTATE` (132) returned 0 there for lengths 0, 64, 128, 256 and 512 (512 was also the largest length a search accepted) and failed with `EINVAL` for 1024, 2048, 4096 and 8192 and with `EFAULT` for a null argument or address, and `I386_GET_XFPUSTATE` (11) failed with `EFAULT` for a null argument; both throw here. The other operations FreeBSD 11's amd64 `sysarch` handles, and every operation it handles on other hosts (Windows keeps its TEB in GS), throw. - [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified. - [kern.proc.vmmap](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_VMMAP`, pid} returns packed FreeBSD 11 `kinfo_vmentry` records (144 bytes each, an empty path) for the used ranges of the guest address-space arena, in address order, as [`kinfo_getvmmap`](https://github.com/freebsd/freebsd-src/blob/releng/11.0/lib/libutil/kinfo_getvmmap.c) reads them. A range is one record: the type is always `KVME_TYPE_DEFAULT`, the protection is `sceKernelVirtualQuery`'s for the range's first page (0 where it reports none), and offset, object, resident page counts, flags and path are 0. Only the calling process's pid is accepted (`ESRCH` otherwise, -1 included, as FreeBSD's `pget` gives); a write returns `EPERM` and a short buffer `ENOMEM`. Ranges outside the arena, which guest code cannot map, are not reported. Whether a PS5 application may read its own map, and what types and paths the console reports, is unverified - [machdep.cacheline_size](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns -1 with `ENOENT`, matching FreeBSD amd64 where this sysctl OID is not defined. From 6e089fe113d54be4e85af40fc8b47df54f82b988 Mon Sep 17 00:00:00 2001 From: thuanquang Date: Mon, 19 Oct 2026 03:42:22 +0700 Subject: [PATCH 089/176] test(libkernel): time blocking waits from before the writer thread starts PipeRead (guest_kqueue) and the blocking read in guest_socket_events took their start time after creating the thread that writes after 50 ms, so a main thread preempted between the two measured less than the writer's sleep and failed the 40 ms check although the wait did block until the write. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit e0da1fb1c91b640a1ef4b4fc5bfe5d04e18ee358) --- core/libs/tests/GuestKqueue.cpp | 4 ++-- core/libs/tests/GuestSocketEvents.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index 06b594a01da0..ca3404840e50 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -230,12 +230,12 @@ static void PipeRead() { char buffer[4]{}; Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 3); Require(Poll(kq, events, 4) == 0); + const KernelTimespec limit{5, 0}; + const auto start = std::chrono::steady_clock::now(); std::thread writer([&pipe] { std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(write_nid_postfix(pipe[1], "d", 1) == 1); }); - const KernelTimespec limit{5, 0}; - const auto start = std::chrono::steady_clock::now(); Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, &limit) == 1); const auto waited = std::chrono::steady_clock::now() - start; writer.join(); diff --git a/core/libs/tests/GuestSocketEvents.cpp b/core/libs/tests/GuestSocketEvents.cpp index 94bf0656db6b..64184d40807c 100644 --- a/core/libs/tests/GuestSocketEvents.cpp +++ b/core/libs/tests/GuestSocketEvents.cpp @@ -104,11 +104,11 @@ int main() { const int reader = accept_nid_postfix(listener, nullptr, nullptr); Require(reader >= 0); Require(sceKernelAddReadEvent(blocking, reader, 1, nullptr) == SCE_OK); + const auto start = std::chrono::steady_clock::now(); std::thread sender([&] { std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(send_nid_postfix(writer, message, sizeof(message), 0) == sizeof(message)); }); - const auto start = std::chrono::steady_clock::now(); Require(sceKernelWaitEqueue(blocking, events.data(), events.size(), &count, nullptr) == SCE_OK); sender.join(); Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40)); From b2d0a6ce59ce4836dce10ed7defa5bfda2822bfa Mon Sep 17 00:00:00 2001 From: valenciana-nc Date: Sun, 18 Oct 2026 21:21:40 +0000 Subject: [PATCH 090/176] fix(libkernel_sys): reject root names in symlink targets and test target restrictions Replace the leading-slash absolute-target check with a root-name/root-directory check so Windows targets like C:/ or \\server\share are rejected too, and add guest tests covering rejected (.. , ../x, a/../b, a/../../x, /abs, plus C:/ and UNC paths on Windows) and accepted (sub/dir, a..b, ...) targets. (cherry picked from commit 84d77798c28a7e225b0b9edbaab6d43ce72672e0) --- core/libs/prx/libkernel_sys/Export.cpp | 6 ++--- core/libs/tests/GuestKernelSys.cpp | 34 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 1bb5f28b6ef9..9719ebff0e73 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -442,11 +442,12 @@ int APS5_VABI chroot_nid_postfix(const char* path) { int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { if (target == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); if (*target == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); - if (*target == '/') { + const std::filesystem::path contents(target); + if (contents.has_root_name() || contents.has_root_directory()) { NotImplemented_nid_no_patch("symlink: absolute targets"); return -1; } - for (const auto& part : std::filesystem::path(target)) { + for (const auto& part : contents) { if (part == "..") { NotImplemented_nid_no_patch("symlink: parent-directory targets"); return -1; @@ -461,7 +462,6 @@ int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { if (std::filesystem::exists(std::filesystem::symlink_status(native, error))) return static_cast(Fail(GuestEexist)); #endif if (!std::filesystem::is_directory(native.parent_path(), error)) return static_cast(Fail(GuestEnoent)); - const std::filesystem::path contents(target); const bool directory = std::filesystem::is_directory(native.parent_path() / contents, error); #ifdef _WIN32 DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index e59d59d32a72..8291d5a7b35e 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -80,6 +80,39 @@ bool ChrootThrows(const std::string& path) { return false; } +bool SymlinkTargetThrows(const char* target, const std::string& created) { + try { + symlink_nid_postfix(target, created.c_str()); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { + const auto created = (root / "rejected").string(); + for (const auto& target : {std::string(".."), std::string("../x"), std::string("a/../b"), std::string("a/../../x"), std::string("/abs")}) + Require(SymlinkTargetThrows(target.c_str(), created)); +#ifdef _WIN32 + for (const auto& target : {std::string("C:/"), std::string("\\\\server\\share")}) + Require(SymlinkTargetThrows(target.c_str(), created)); +#endif + const auto nested = (root / "accepted-nested").string(); + const int link = symlink_nid_postfix("sub/dir", nested.c_str()); + if (link == 0) { + Require(std::filesystem::is_symlink(std::filesystem::symlink_status(nested))); + const auto dots = (root / "accepted-dots").string(); + Require(symlink_nid_postfix("a..b", dots.c_str()) == 0); + Require(std::filesystem::is_symlink(std::filesystem::symlink_status(dots))); + const auto ellipsis = (root / "accepted-ellipsis").string(); + Require(symlink_nid_postfix("...", ellipsis.c_str()) == 0); + Require(std::filesystem::is_symlink(std::filesystem::symlink_status(ellipsis))); + } else { + Require(link == -1 && Error() == 1); + std::fprintf(stderr, "symbolic links unavailable: the symlink target success cases not tested\n"); + } +} + void CheckPathconf(const std::filesystem::path& root, const std::string& file) { const auto directory = root.generic_string(); for (const auto& path : {file, directory}) { @@ -382,6 +415,7 @@ int main() { Require(symlink_nid_postfix("original.bin", (root / "missing" / "symbolic").string().c_str()) == -1 && Error() == 2); Require(symlink_nid_postfix(nullptr, symbolic.c_str()) == -1 && Error() == 14); Require(symlink_nid_postfix("original.bin", "") == -1 && Error() == 2); + CheckSymlinkTargetRestrictions(root); const int base = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(base >= 0); const int relative = openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_RDONLY); From 413fe9148f8e1dc38a5be9a7c1893972703a217f Mon Sep 17 00:00:00 2001 From: LeandroLP23 Date: Fri, 16 Oct 2026 11:47:18 +0200 Subject: [PATCH 091/176] feat(tools): warn when a pull request adds a technical debt entry over 1000 characters Co-Authored-By: Claude Opus 5.5 (cherry picked from commit fb1ad2fcf4b5ffd5d7930a408ff947c1eed09c4c) --- CMakeLists.txt | 2 + core/libs/tests/GuestPositionedClose.cpp | 73 +++++++++++++++++++ tools/check_conventions.py | 13 ++++ .../test_check_conventions_debt_length.py | 64 ++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 core/libs/tests/GuestPositionedClose.cpp create mode 100644 tools/tests/test_check_conventions_debt_length.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1da80fadbe..b07cfe7c953d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_unwrap_self.py) add_test(NAME conventions_function_bodies COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_comments.py) + add_test(NAME conventions_debt_length COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_debt_length.py) add_test(NAME test_registration COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_test_registration.py) add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE} diff --git a/core/libs/tests/GuestPositionedClose.cpp b/core/libs/tests/GuestPositionedClose.cpp new file mode 100644 index 000000000000..3d44da1f082e --- /dev/null +++ b/core/libs/tests/GuestPositionedClose.cpp @@ -0,0 +1,73 @@ +#include "prx/libc/include/PreciseWait.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); +int APS5_VABI sceKernelClose(int); +std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Positioned close check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr std::int64_t ErrorEbadf = static_cast(0x80020009u); + +int main() { + const auto root = std::filesystem::path("anyps5-positioned-close-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + const auto path = (root / "data.bin").string(); + constexpr std::size_t fileSize = 48u << 20; + { + std::ofstream stream(path, std::ios::binary); + const std::vector block(1u << 20, 'x'); + for (std::size_t written = 0; written < fileSize; written += block.size()) stream.write(block.data(), static_cast(block.size())); + Require(stream.good()); + } + std::vector buffer(32u << 20); + int overlapped = 0; + for (int round = 0; round < 60; ++round) { + const int descriptor = sceKernelOpen(path.c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(descriptor >= 0); + std::atomic state{0}; + std::atomic threw{false}; + std::int64_t result = 0; + std::thread reader([&] { + state = 1; + try { + result = sceKernelPread(descriptor, buffer.data(), buffer.size(), 0); + } catch (const std::exception&) { + threw = true; + } + state = 2; + }); + while (state.load() == 0) std::this_thread::yield(); + PreciseSleepUs(300); + const int closed = sceKernelClose(descriptor); + reader.join(); + Require(!threw.load()); + Require(closed == 0); + Require(result == static_cast(buffer.size()) || result == ErrorEbadf); + if (result == static_cast(buffer.size())) ++overlapped; + } + Require(overlapped > 0); + std::filesystem::remove_all(root); + return 0; +} diff --git a/tools/check_conventions.py b/tools/check_conventions.py index c3c186cc7426..e36545afcdeb 100644 --- a/tools/check_conventions.py +++ b/tools/check_conventions.py @@ -27,6 +27,7 @@ "commit-subject": ("error", "docs/dev/CONVENTIONS.md", "Commit subject is not Conventional Commits"), "pr-title": ("error", "docs/dev/CONVENTIONS.md", "Pull request title is not Conventional Commits"), "pr-template": ("error", ".github/pull_request_template.md", "Fill in the pull request template"), + "debt-length": ("warning", "CONTRIBUTING.md#documentation", "Technical debt entry over 1000 characters: state the gap first (what differs from the console, what is unverified, what throws) and keep only the behaviour it needs"), } CPP = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx", ".inl", ".ipp"} @@ -39,6 +40,7 @@ DEBT = "docs/dev/TechnicalDebt.md" DEBT_DIR = "docs/dev/debt/" DEBT_TARGET = re.compile(r"\]\((?:\.\./)+(core/[^)#\s]+)") +DEBT_ENTRY_LIMIT = 1000 CONVENTIONAL = re.compile(r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?: \S") STRINGS = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'') @@ -244,6 +246,12 @@ def docs(self): continue self.report("doc-link", path, number, target) + def debt_entries(self): + for path, lines in self.files(lambda p: is_debt(p.as_posix())): + for number, text in lines: + if long_debt_entry(text): + self.report("debt-length", path, number, f"{len(text)} characters") + def workflows(self): for path in sorted(self.status): if self.status[path] == "D" or not re.fullmatch(r"\.github/workflows/[^/]+\.ya?ml", path): @@ -313,6 +321,10 @@ def is_debt(path): return path == DEBT or (path.startswith(DEBT_DIR) and path.endswith(".md") and "/" not in path[len(DEBT_DIR):]) +def long_debt_entry(text): + return text.startswith("- ") and len(text) > DEBT_ENTRY_LIMIT + + def debt_name(text): match = DEBT_TARGET.search(text) if not match: @@ -473,6 +485,7 @@ def output(check, repo): check.shaders() check.repository() check.docs() + check.debt_entries() check.workflows() check.commits() pr = json.load(open(args.event, encoding="utf-8")).get("pull_request") if args.event else None diff --git a/tools/tests/test_check_conventions_debt_length.py b/tools/tests/test_check_conventions_debt_length.py new file mode 100644 index 000000000000..50dad5328935 --- /dev/null +++ b/tools/tests/test_check_conventions_debt_length.py @@ -0,0 +1,64 @@ +import io +import os +import subprocess +import sys +import tempfile +import unittest +from contextlib import redirect_stdout +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +import check_conventions +from check_conventions import Check + +DEBT = "docs/dev/debt/libSceExample.md" +SHORT = "- [sceShort](../../../core/libs/prx/libSceExample/Export.cpp) (libSceExample) - layout unknown" +LONG = "- [sceLong](../../../core/libs/prx/libSceExample/Export.cpp) (libSceExample) - " + "x" * 1000 +PROSE = "Not an entry. " + "y" * 1000 + + +class DebtLengthTests(unittest.TestCase): + def test_long_debt_entry(self): + self.assertFalse(check_conventions.long_debt_entry(SHORT)) + self.assertFalse(check_conventions.long_debt_entry("- " + "z" * (check_conventions.DEBT_ENTRY_LIMIT - 2))) + self.assertTrue(check_conventions.long_debt_entry("- " + "z" * (check_conventions.DEBT_ENTRY_LIMIT - 1))) + self.assertFalse(check_conventions.long_debt_entry(PROSE)) + + def test_added_long_entry_warns_without_failing(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + + def git(*args): + return subprocess.run(["git", "-c", f"safe.directory={root}", "-c", "user.name=Conventions Test", + "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args], + cwd=root, capture_output=True, check=True, encoding="utf-8").stdout + + def write(text): + (root / DEBT).parent.mkdir(parents=True, exist_ok=True) + (root / DEBT).write_text(text, encoding="utf-8") + + git("init", "-q") + write(f"# libSceExample technical debt\n\n### Functional\n\n{SHORT}\n") + git("add", ".") + git("commit", "-qm", "docs: add base") + write(f"# libSceExample technical debt\n\n{PROSE}\n\n### Functional\n\n{SHORT}\n{LONG}\n") + git("commit", "-qam", "docs(debt): add a long entry") + head = git("rev-parse", "HEAD").strip() + base = git("rev-parse", "HEAD~1").strip() + + previous = Path.cwd() + try: + os.chdir(root) + check = Check(base, head) + check.debt_entries() + with redirect_stdout(io.StringIO()): + status = check_conventions.output(check, "example/repo") + finally: + os.chdir(previous) + + self.assertEqual(check.findings, [("debt-length", DEBT, 8, f"{len(LONG)} characters")]) + self.assertEqual(status, 0) + + +if __name__ == "__main__": + unittest.main() From 36f03f76259ee12c109f41b5e174a83c2807584c Mon Sep 17 00:00:00 2001 From: Muhammad Wasif Ali Wasif <76695420+wachi35676@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:55:37 +0500 Subject: [PATCH 092/176] fix(libc): read /dev/random and /dev/urandom through fopen fopen resolved the random device paths to a host file that does not exist and failed with ENOENT, while open and sceKernelOpen already serve them from host entropy. A read-only fopen of either path now returns a stream whose fread fills the buffer with host entropy. setvbuf, feof, ferror, clearerr, the stream locks and fclose work on it, fflush returns 0 as on a FreeBSD stream that is not writing, other stream functions throw, and opening the paths for writing throws as in libkernel. ProsperoTV (PPSA99003) reads its phone pairing code this way and showed "Code expired or unavailable". Co-Authored-By: Claude Opus 5.5 (cherry picked from commit b4e80e8fc72c9d6397415e6fe08bd15c85a63003) --- core/libs/prx/libc/include/FileStream.hpp | 19 +++++++++- core/libs/prx/libc/src/FileAndHeap.cpp | 27 ++++++++++++-- core/libs/prx/libc/src/RuntimeSupport.cpp | 12 +++---- core/libs/prx/libc/src/StandardStreams.cpp | 2 +- core/libs/tests/GuestFilesystem.cpp | 42 ++++++++++++++++++++++ docs/dev/debt/libc.md | 2 ++ 6 files changed, 93 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 6e203a265d63..b2eb126d9f1d 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -54,6 +54,7 @@ class FileStream { std::FILE* _handle; bool _dynamic; bool guestError = false; + bool randomDevice = false; public: explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) { @@ -109,10 +110,20 @@ class FileStream { errno = error; } + void MarkRandomDevice() { + randomDevice = true; + _guest.descriptor = -1; + } + + bool IsRandomDevice() const { + return randomDevice; + } + bool Reopen(const std::filesystem::path& filename, const char* mode) { auto* previous = GetHandle(); _guest = {}; guestError = false; + randomDevice = false; #ifdef _WIN32 std::wstring wideMode; for (const char character : std::string_view(mode)) wideMode.push_back(static_cast(character)); @@ -189,11 +200,17 @@ class FileStream { }; static_assert(std::is_standard_layout_v); -inline std::FILE* GetNativeStream(FileStream* stream) { +inline std::FILE* GetStreamHandle(FileStream* stream) { if (!stream) throw std::runtime_error("FileStream: null stream"); return stream->GetHandle(); } +inline std::FILE* GetNativeStream(FileStream* stream) { + auto* handle = GetStreamHandle(stream); + if (stream->IsRandomDevice()) throw std::runtime_error("FileStream: this function is not implemented on a /dev/random or /dev/urandom stream"); + return handle; +} + extern "C" { extern FileStream _Stdout_nid_postfix; extern FileStream _Stderr_nid_postfix; diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 4c00567886bc..8f61659574c2 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/HostErrno.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/HostEntropy.hpp" #ifdef __linux__ #include @@ -307,6 +309,19 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) errno = EINVAL; return nullptr; } + if (const std::string_view path(filename); path == "/dev/random" || path == "/dev/urandom") { + if (parsed->writes) throw std::runtime_error(std::string(__func__) + ": writing to " + filename + " is not implemented"); +#ifdef _WIN32 + std::unique_ptr device(std::fopen("NUL", "rb"), std::fclose); +#else + std::unique_ptr device(std::fopen("/dev/null", "rb"), std::fclose); +#endif + if (!device) return nullptr; + auto stream = std::make_unique(device.get(), true); + device.release(); + stream->MarkRandomDevice(); + return stream.release(); + } std::filesystem::path fpath; if (const int error = ResolvePathError_nid_no_patch(filename, fpath)) { errno = error; return nullptr; } std::unique_ptr handle(OpenNative(fpath, parsed->native), std::fclose); @@ -337,7 +352,7 @@ int APS5_VABI fclose_nid_postfix(FileStream* stream) { errno = 9; return -1; } - GetNativeStream(stream); + GetStreamHandle(stream); std::unique_ptr owner(stream->IsDynamic() ? stream : nullptr); return stream->Close(); } @@ -369,7 +384,7 @@ FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return 0; } - auto* handle = GetNativeStream(stream); + auto* handle = GetStreamHandle(stream); if (size == 0 || count == 0) return 0; if (!buffer) throw std::runtime_error("fread: null buffer"); if (count > std::numeric_limits::max() / size) throw std::overflow_error("fread: buffer size overflow"); @@ -377,6 +392,11 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File if (bytes > std::numeric_limits::max() - reinterpret_cast(buffer)) throw std::overflow_error("fread: buffer address overflow"); const GuestArena::HostWrite destination(buffer, bytes); if (!destination.Open()) throw std::runtime_error("fread: buffer is not writable"); + if (stream->IsRandomDevice()) { + HostEntropy_nid_no_patch(buffer, bytes); + stream->SyncStatus(); + return count; + } const auto result = std::fread(buffer, size, count, handle); stream->SyncStatus(); return result; @@ -458,7 +478,8 @@ int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) { int APS5_VABI fflush_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } - std::FILE* native = stream ? GetNativeStream(stream) : nullptr; + std::FILE* native = stream ? GetStreamHandle(stream) : nullptr; + if (stream && stream->IsRandomDevice()) return 0; #ifdef __linux__ if (native && !__fwriting(native)) return 0; #endif diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index f3509f8ff4cd..b59e9deae7ea 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -135,18 +135,18 @@ unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() { void APS5_VABI _Lockfilelock_nid_postfix(FileStream* stream) { if (stream == nullptr) throw std::invalid_argument("_Lockfilelock: null stream"); #ifdef _WIN32 - _lock_file(GetNativeStream(stream)); + _lock_file(GetStreamHandle(stream)); #else - flockfile(GetNativeStream(stream)); + flockfile(GetStreamHandle(stream)); #endif } void APS5_VABI _Unlockfilelock_nid_postfix(FileStream* stream) { if (stream == nullptr) throw std::invalid_argument("_Unlockfilelock: null stream"); #ifdef _WIN32 - _unlock_file(GetNativeStream(stream)); + _unlock_file(GetStreamHandle(stream)); #else - funlockfile(GetNativeStream(stream)); + funlockfile(GetStreamHandle(stream)); #endif } @@ -161,9 +161,9 @@ void APS5_VABI funlockfile_nid_postfix(FileStream* stream) { int APS5_VABI ftrylockfile_nid_postfix(FileStream* stream) { if (stream == nullptr) throw std::invalid_argument("ftrylockfile: null stream"); #ifdef _WIN32 - return TryEnterCriticalSection(&reinterpret_cast(GetNativeStream(stream))->lock) ? 0 : -1; + return TryEnterCriticalSection(&reinterpret_cast(GetStreamHandle(stream))->lock) ? 0 : -1; #else - return ftrylockfile(GetNativeStream(stream)) == 0 ? 0 : -1; + return ftrylockfile(GetStreamHandle(stream)) == 0 ? 0 : -1; #endif } diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp index ec6c26c02c42..1f48e1c791df 100644 --- a/core/libs/prx/libc/src/StandardStreams.cpp +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -171,7 +171,7 @@ int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, st buffer = nullptr; size = BUFSIZ; } - return std::setvbuf(GetNativeStream(stream), buffer, native, size); + return std::setvbuf(GetStreamHandle(stream), buffer, native, size); } void APS5_VABI setbuf_nid_postfix(FileStream* stream, char* buffer) { setvbuf_nid_postfix(stream, buffer, buffer ? 0 : 2, buffer ? 1024 : 0); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 49e491835bb4..b569e04a93f9 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -71,6 +71,16 @@ int APS5_VABI chdir_nid_postfix(const char*); void* APS5_VABI opendir_nid_postfix(const char*); FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); +std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); +int APS5_VABI fclose_nid_postfix(FileStream*); +int APS5_VABI fgetc_nid_postfix(FileStream*); +int APS5_VABI feof_nid_postfix(FileStream*); +int APS5_VABI ferror_nid_postfix(FileStream*); +int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); +int APS5_VABI fflush_nid_postfix(FileStream*); +void APS5_VABI flockfile_nid_postfix(FileStream*); +void APS5_VABI funlockfile_nid_postfix(FileStream*); +int APS5_VABI ftrylockfile_nid_postfix(FileStream*); int* APS5_VABI __error_nid_postfix(); int APS5_VABI pipe_nid_postfix(int*); int APS5_VABI dup2_nid_postfix(int, int); @@ -1088,6 +1098,38 @@ int main() { Require(read_nid_postfix(random, second, 3) == 3); Require(std::memcmp(first, second, sizeof(first)) != 0 && second[3] == 0); Require(close_nid_postfix(random) == 0); + auto* stream = fopen_nid_postfix(device, "rb"); + Require(stream != nullptr); + Require(setvbuf_nid_postfix(stream, nullptr, 2, 0) == 0); + std::memset(first, 0, sizeof(first)); + std::memset(second, 0, sizeof(second)); + Require(fread_nid_postfix(first, 1, sizeof(first), stream) == sizeof(first)); + Require(fread_nid_postfix(second, 8, 8, stream) == 8); + Require(std::memcmp(first, second, sizeof(first)) != 0); + Require(feof_nid_postfix(stream) == 0 && ferror_nid_postfix(stream) == 0); + flockfile_nid_postfix(stream); + Require(fread_nid_postfix(first, 1, 4, stream) == 4); + funlockfile_nid_postfix(stream); + Require(ftrylockfile_nid_postfix(stream) == 0); + funlockfile_nid_postfix(stream); + Require(fflush_nid_postfix(stream) == 0); + bool unsupported = false; + try { + fgetc_nid_postfix(stream); + } catch (const std::runtime_error&) { + unsupported = true; + } + Require(unsupported); + Require(fclose_nid_postfix(stream) == 0); + for (const char* mode : {"wb", "ab", "r+b"}) { + bool rejected = false; + try { + fopen_nid_postfix(device, mode); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected); + } } Require(unlink_nid_postfix(presentName.c_str()) == 0 && !std::filesystem::exists(present)); const auto empty = root / "empty"; diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 70fff12d58a1..1638138a0e2d 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -79,6 +79,8 @@ - [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way - [`fopen` streams](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows share delete access, like the descriptors [`sceKernelOpen`](../../../core/libs/prx/libkernel/File/src/Open.cpp) opens, so a file the guest holds open can be removed or renamed. A stream reopened with `freopen` still goes through `_wfreopen`, which does not share delete access. On Windows, opening a directory in a read mode fails with `EACCES`, where FreeBSD opens it; directory streams are not emulated - The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Its [slots](../../../core/libs/prx/libc/src/exception/Unwind.cpp) answer as `UnwindCursor` of [LLVM 18.1.8](https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/libunwind/src/UnwindCursor.hpp) does with DWARF unwind information: `getReg` and `setReg` of a register number outside -2 (`UNW_REG_SP`) to 16 (`rip`), `getFloatReg` and `setFloatReg` print libunwind's message and abort, as `_LIBUNWIND_ABORT` does; the destructors do nothing; `getInfo` gives the function's range, LSDA and personality from its FDE, `DW_CFA_GNU_args_size` as `gp`, `UNWIND_X86_64_MODE_DWARF` as `format`, the FDE as `unwind_info` and the start of the ELF segment or PE section holding the IP as `extra`, and all zero when no FDE covers the IP. Differences: `step` walks through Windows host frames that have no FDE with `RtlVirtualUnwind`, as `_Unwind_Backtrace` does, and `getInfo` reports them with no range, as `_Unwind_GetRegionStart` does; `step` looks a signal frame up at its IP instead of the IP minus one and ends at a frame whose rules leave `rsp` and `rip` unchanged, where libunwind would return that frame again; `getFunctionName` asks `dladdr` on Linux and macOS but takes the nearest export at or below the IP in the image holding it on Windows, since PE exports carry no size; `jumpto` restores the registers as `_Unwind_RaiseException` installs a landing pad, without `r10` and `r11`. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked +- [`fopen` of /dev/random and /dev/urandom](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) in a read mode returns a stream over the host null device whose `fread` fills the buffer with host OS entropy, as the libkernel [random device](../../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) descriptors do. Only the exact paths are recognised, the stream has no descriptor, `setvbuf`, `feof`, `ferror`, `clearerr`, `flockfile`, `ftrylockfile`, `funlockfile` and `fclose` work on it, `fflush` returns 0 as on a FreeBSD read-only stream, and every other stream function throws, as does opening the paths for writing. `freopen` onto these paths still fails with `ENOENT`. ProsperoTV (PPSA99003) reads its phone pairing code this way +- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked - The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled - The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. - [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask. From fa1517c87c18076c8f204903739aa0d1b5f9a210 Mon Sep 17 00:00:00 2001 From: Romans Pokrovskis Date: Mon, 19 Oct 2026 01:44:21 +0300 Subject: [PATCH 093/176] test(audio): cover repeated legacy vibration grains (cherry picked from commit 166e84bed1e9e674a264bf20a626aaca84316e5b) --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 2 +- .../tests/AudioOutVibrationDevice.cpp | 57 ++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 577fa1347929..c8198659b417 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -35,7 +35,7 @@ if(BUILD_TESTING) target_include_directories(audio_out_vibration_device_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) target_link_libraries(audio_out_vibration_device_tests PRIVATE libc libkernel libScePad) configure_windows_unwind(audio_out_vibration_device_tests) - foreach(vibrationDeviceMode IN ITEMS quad pulse open_failure timing discovery_failure) + foreach(vibrationDeviceMode IN ITEMS quad pulse open_failure timing cadence discovery_failure) set(vibrationDeviceTest "audio_out_vibration_device_${vibrationDeviceMode}") add_test(NAME ${vibrationDeviceTest} COMMAND audio_out_vibration_device_tests ${vibrationDeviceMode}) set_tests_properties(${vibrationDeviceTest} PROPERTIES TIMEOUT 5) diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp index c6d2803c6541..6d0a7ca4f174 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp @@ -140,6 +140,57 @@ void RunTiming() { Require(sceAudioOutClose(split) == 0, "split port close failed"); Require(opened == 0 && queued == 0, "virtual vibration opened a speaker device"); } + +void RunCadence() { + available = false; + const std::array ports{ + sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1), + sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1) + }; + Require(ports[0] > 0 && ports[1] > 0 && ports[0] != ports[1], "repeated vibration ports did not open independently"); + constexpr std::array, 4> patterns{{{8192, -16384}, {0, 0}, {-16384, 8192}, {16384, -8192}}}; + constexpr std::array, 4> expected{{{128, 255}, {0, 0}, {255, 128}, {255, 128}}}; + std::array ends{}; + std::array samples{}; + std::vector boundaries; + for (std::size_t call = 0; call < 32; ++call) { + for (std::size_t port = 0; port < ports.size(); ++port) { + const auto pattern = (call + port) % patterns.size(); + for (std::size_t frame = 0; frame < frames; ++frame) { + samples[frame * 2] = patterns[pattern][0]; + samples[frame * 2 + 1] = patterns[pattern][1]; + } + const auto count = blocks.size(); + Require(sceAudioOutOutput(ports[port], samples.data()) == static_cast(frames), "repeated vibration output rejected 256 frames"); + Require(blocks.size() == count + 1, "repeated vibration output did not queue one grain per call"); + const auto& block = blocks.back(); + Require(block.source == ports[port] && block.rumble == expected[pattern], "repeated vibration output used the wrong source or PCM energy"); + Require(block.end - block.begin == 5333 && block.begin >= ends[port], "repeated vibration interval has the wrong duration or overlaps its preceding call"); + ends[port] = block.end; + boundaries.insert(boundaries.end(), {block.begin, block.end - 1, block.end}); + } + } + Require(blocks.size() == 64 && clears.empty() && opened == 0 && queued == 0, "repeated virtual output changed routes or cleared a live port"); + std::sort(boundaries.begin(), boundaries.end()); + boundaries.erase(std::unique(boundaries.begin(), boundaries.end()), boundaries.end()); + const auto check = [&](PadAudioHapticQueue model, int removed) { + for (const auto time : boundaries) { + std::array value{}; + for (const auto& block : blocks) { + if (block.source == removed || time < block.begin || time >= block.end) continue; + for (std::size_t channel = 0; channel < value.size(); ++channel) value[channel] = std::max(value[channel], block.rumble[channel]); + } + Require(model.Read(time) == value, "repeated vibration timeline lost a grain, silence, boundary or independent source"); + } + }; + check(timeline, 0); + Require(sceAudioOutOutput(ports[0], nullptr) == static_cast(frames) && clears == std::vector{ports[0]}, "repeated vibration drain cleared the wrong source"); + check(timeline, ports[0]); + Require(sceAudioOutClose(ports[0]) == 0 && sceAudioOutClose(ports[1]) == 0, "repeated vibration port close failed"); + Require(clears == std::vector{ports[0], ports[0], ports[1]}, "repeated vibration close did not clear only its source"); + Require(timeline.Read(ends[0]) == std::array{}, "repeated vibration close retained queued output"); + std::puts("64 repeated legacy stereo S16/48 kHz outputs: 256 frames per call, 32 calls per port; PCM, interval boundaries, source mixing and drain isolation passed"); +} } int InitializeSdlAudio_nid_no_patch() { return failInitialize ? -1 : 0; } @@ -182,13 +233,17 @@ int main(int argc, char** argv) { try { Require(argc == 2, "invalid test arguments"); const std::string_view mode = argv[1]; - Require(mode == "quad" || mode == "pulse" || mode == "open_failure" || mode == "timing" || mode == "discovery_failure", "invalid device mode"); + Require(mode == "quad" || mode == "pulse" || mode == "open_failure" || mode == "timing" || mode == "cadence" || mode == "discovery_failure", "invalid device mode"); pulse = mode == "pulse"; Pad::Initialize(); Pad::SetVibrationMode(1); std::uint32_t seen = 0; PadOutputState state{}; static_cast(PadFetchOutput_nid_postfix(&seen, &state)); + if (mode == "cadence") { + RunCadence(); + return 0; + } if (mode == "timing") { RunTiming(); std::puts("legacy vibration device tests passed"); From 5e45c8dc6d50d5eb7aa0cdd290e50ca193723254 Mon Sep 17 00:00:00 2001 From: hypermist Date: Fri, 16 Oct 2026 01:06:15 +1300 Subject: [PATCH 094/176] feat(libc): implement the remaining Dinkumware _Atomic helpers The 1-, 2- and 8-byte loads, stores, exchanges, compare-exchanges and fetch operations, _Atomic_is_lock_free_N, the flag and fence functions and the lock-based generic _Atomic_copy, _Atomic_exchange and _Atomic_compare_exchange_*, matching the libc.prx bundled with PPSA08427 including its handling of invalid memory orders. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8tinbP1MUpUaDsbNWnGnu (cherry picked from commit ec3fe412db38dd0e521834a679fd786ebfa212ec) --- core/libs/prx/libc/CMakeLists.txt | 11 +- core/libs/prx/libc/src/DinkumwareAtomics.cpp | 175 +++++++++++++++++++ core/libs/tests/GuestDinkumwareAtomics.cpp | 120 +++++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libc/src/DinkumwareAtomics.cpp create mode 100644 core/libs/tests/GuestDinkumwareAtomics.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 5bb224d9f62d..925c5de2fcd7 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -44,6 +44,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AtomicBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp @@ -81,7 +82,6 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RealPath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Threads.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/BinaryWordIo.cpp ) @@ -417,6 +417,15 @@ if(BUILD_TESTING) set_tests_properties(guest_environment PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_dinkumware_atomics_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareAtomics.cpp) + target_include_directories(guest_dinkumware_atomics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_dinkumware_atomics_tests PRIVATE libc) + configure_windows_unwind(guest_dinkumware_atomics_tests) + add_test(NAME guest_dinkumware_atomics COMMAND guest_dinkumware_atomics_tests) + if(WIN32) + set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_hostname_tests ${LIBS_INCLUDE_DIR}/tests/GuestHostname.cpp) target_include_directories(guest_hostname_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_hostname_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/DinkumwareAtomics.cpp b/core/libs/prx/libc/src/DinkumwareAtomics.cpp new file mode 100644 index 000000000000..f8aecb774ec5 --- /dev/null +++ b/core/libs/prx/libc/src/DinkumwareAtomics.cpp @@ -0,0 +1,175 @@ +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr int MemoryOrderSeqCst = 5; + +template +void Store(volatile TValue* target, TValue value) { + __atomic_store_n(target, value, __ATOMIC_SEQ_CST); +} + +template +TValue Load(volatile TValue* target) { + return __atomic_load_n(target, __ATOMIC_SEQ_CST); +} + +bool IsValidOrder(int order) { + return order >= 0 && order <= MemoryOrderSeqCst; +} + +template +TValue Exchange(volatile TValue* target, TValue value) { + return __atomic_exchange_n(target, value, __ATOMIC_SEQ_CST); +} + +template +int CompareExchange(volatile TValue* target, TValue* expected, TValue desired) { + return __atomic_compare_exchange_n(target, expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? 1 : 0; +} + +void LockFlag(volatile unsigned char* flag) { + while (__atomic_exchange_n(flag, static_cast(1), __ATOMIC_ACQUIRE) != 0) { + } +} + +void UnlockFlag(volatile unsigned char* flag) { + __atomic_store_n(flag, static_cast(0), __ATOMIC_SEQ_CST); +} + +void CopyBytes(volatile void* destination, const volatile void* source, std::size_t size) { + std::memcpy(const_cast(destination), const_cast(source), size); +} + +int LockedCompareExchange(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired) { + LockFlag(flag); + void* object = const_cast(target); + const bool equal = std::memcmp(object, expected, size) == 0; + if (equal) { + std::memcpy(object, desired, size); + } else { + std::memcpy(expected, object, size); + } + UnlockFlag(flag); + return equal ? 1 : 0; +} + +} + +extern "C" { + +void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { Store(target, value); } +void APS5_VABI _Atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { Store(target, value); } +void APS5_VABI _Atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { Store(target, value); } +void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { Store(target, value); } + +std::uint8_t APS5_VABI _Atomic_load_1_nid_postfix(volatile std::uint8_t* target, int) { return Load(target); } +std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t* target, int) { return Load(target); } +std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t* target, int order) { + if (order == 3 || order == 4 || !IsValidOrder(order)) return 0; + return Load(target); +} + +std::uint8_t APS5_VABI _Atomic_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return Exchange(target, value); } +std::uint16_t APS5_VABI _Atomic_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return Exchange(target, value); } +std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return Exchange(target, value); } +std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? Exchange(target, value) : 0; } + +int APS5_VABI _Atomic_compare_exchange_strong_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_strong_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t* expected, std::uint32_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_strong_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_sub_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_sub_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_and_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_and_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_and_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_or_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Atomic_fetch_or_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_or_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_xor_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_xor_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Atomic_fetch_xor_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST) : 0; } + +int APS5_VABI _Atomic_is_lock_free_1_nid_postfix() { return 1; } +int APS5_VABI _Atomic_is_lock_free_2_nid_postfix() { return 1; } +int APS5_VABI _Atomic_is_lock_free_4_nid_postfix() { return 1; } +int APS5_VABI _Atomic_is_lock_free_8_nid_postfix() { return 1; } + +int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char* flag, int) { + return __atomic_exchange_n(flag, static_cast(1), __ATOMIC_SEQ_CST); +} + +void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char* flag, int) { + __atomic_store_n(flag, static_cast(0), __ATOMIC_SEQ_CST); +} + +void APS5_VABI _Atomic_thread_fence_nid_postfix(int order) { + if (order == MemoryOrderSeqCst) __atomic_thread_fence(__ATOMIC_SEQ_CST); +} + +void APS5_VABI _Atomic_signal_fence_nid_postfix(int) { +} + +void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, const volatile void* source, int) { + LockFlag(flag); + CopyBytes(target, source, size); + UnlockFlag(flag); +} + +void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, volatile void* value, int) { + LockFlag(flag); + auto* object = static_cast(const_cast(target)); + auto* other = static_cast(const_cast(value)); + for (std::size_t i = 0; i < size; ++i) { + const unsigned char byte = object[i]; + object[i] = other[i]; + other[i] = byte; + } + UnlockFlag(flag); +} + +int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int) { + return LockedCompareExchange(flag, size, target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int) { + return LockedCompareExchange(flag, size, target, expected, desired); +} + +} diff --git a/core/libs/tests/GuestDinkumwareAtomics.cpp b/core/libs/tests/GuestDinkumwareAtomics.cpp new file mode 100644 index 000000000000..8e45d769024c --- /dev/null +++ b/core/libs/tests/GuestDinkumwareAtomics.cpp @@ -0,0 +1,120 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); +void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t*, int); +std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t*, int); +std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int); +std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t*, std::uint16_t*, std::uint16_t, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t*, std::uint64_t*, std::uint64_t, int, int); +std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); +std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t*, std::uint16_t, int); +std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int); +std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); +std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +int APS5_VABI _Atomic_is_lock_free_4_nid_postfix(); +int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char*, int); +void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char*, int); +void APS5_VABI _Atomic_thread_fence_nid_postfix(int); +void APS5_VABI _Atomic_signal_fence_nid_postfix(int); +void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, const volatile void*, int); +void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, volatile void*, int); +int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int); +} + +static void Require(bool value) { if (!value) std::abort(); } + +constexpr int Relaxed = 0, Acquire = 2, Release = 3, AcqRel = 4, SeqCst = 5; + +static void CheckTyped() { + std::uint8_t byte = 0; + _Atomic_store_1_nid_postfix(&byte, 7, SeqCst); + Require(byte == 7 && _Atomic_fetch_add_1_nid_postfix(&byte, 250, Relaxed) == 7 && byte == 1); + Require(_Atomic_fetch_or_1_nid_postfix(&byte, 0x80, AcqRel) == 1 && byte == 0x81); + std::uint16_t half = 5; + Require(_Atomic_load_2_nid_postfix(&half, Release) == 5 && _Atomic_fetch_sub_2_nid_postfix(&half, 6, SeqCst) == 5 && half == 0xffff); + std::uint16_t expectedHalf = 1; + Require(_Atomic_compare_exchange_strong_2_nid_postfix(&half, &expectedHalf, 2, SeqCst, Relaxed) == 0 && expectedHalf == 0xffff); + Require(_Atomic_compare_exchange_strong_2_nid_postfix(&half, &expectedHalf, 2, SeqCst, Relaxed) == 1 && half == 2); + std::uint32_t word = 0xff0f; + Require(_Atomic_fetch_and_4_nid_postfix(&word, 0xf0ff, Acquire) == 0xff0f && word == 0xf00f); + Require(_Atomic_exchange_4_nid_postfix(&word, 9, 6) == 0xf00f && word == 9); + std::uint64_t wide = 0; + _Atomic_store_8_nid_postfix(&wide, 0x123456789ull, Release); + Require(_Atomic_load_8_nid_postfix(&wide, Acquire) == 0x123456789ull); + Require(_Atomic_load_8_nid_postfix(&wide, Release) == 0 && _Atomic_load_8_nid_postfix(&wide, AcqRel) == 0); + Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, SeqCst) == 0x123456789ull && wide == 0x12345678aull); + Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, 6) == 0 && wide == 0x12345678aull); + Require(_Atomic_exchange_8_nid_postfix(&wide, 1, 7) == 0 && wide == 0x12345678aull); + Require(_Atomic_fetch_xor_8_nid_postfix(&wide, 0xf, Relaxed) == 0x12345678aull && wide == 0x123456785ull); + std::uint64_t expectedWide = 0x123456785ull; + Require(_Atomic_compare_exchange_weak_8_nid_postfix(&wide, &expectedWide, 3, SeqCst, SeqCst) == 1 && wide == 3); + Require(_Atomic_is_lock_free_4_nid_postfix() == 1); +} + +static void CheckFlagsAndFences() { + unsigned char flag = 0; + Require(_Atomic_flag_test_and_set_nid_postfix(&flag, SeqCst) == 0 && flag == 1); + Require(_Atomic_flag_test_and_set_nid_postfix(&flag, Relaxed) == 1); + _Atomic_flag_clear_nid_postfix(&flag, Release); + Require(flag == 0); + _Atomic_thread_fence_nid_postfix(SeqCst); + _Atomic_signal_fence_nid_postfix(SeqCst); +} + +struct Wide { + std::uint64_t parts[3]; +}; + +static void CheckGeneric() { + unsigned char lock = 0; + Wide target{{1, 2, 3}}, source{{4, 5, 6}}, expected{{1, 2, 3}}; + _Atomic_copy_nid_postfix(&lock, sizeof(Wide), &target, &source, SeqCst); + Require(std::memcmp(&target, &source, sizeof(Wide)) == 0 && lock == 0); + Wide other{{7, 8, 9}}; + _Atomic_exchange_nid_postfix(&lock, sizeof(Wide), &target, &other, SeqCst); + Require(target.parts[0] == 7 && other.parts[0] == 4 && lock == 0); + const Wide desired{{10, 11, 12}}; + Require(_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 0); + Require(std::memcmp(&expected, &target, sizeof(Wide)) == 0 && lock == 0); + Require(_Atomic_compare_exchange_weak_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 1); + Require(std::memcmp(&target, &desired, sizeof(Wide)) == 0 && lock == 0); +} + +static void CheckConcurrentGeneric() { + unsigned char lock = 0; + Wide counter{{0, 0, 0}}; + constexpr int Iterations = 20000; + auto worker = [&] { + for (int i = 0; i < Iterations; ++i) { + Wide expected; + _Atomic_copy_nid_postfix(&lock, sizeof(Wide), &expected, &counter, SeqCst); + Wide next; + do { + next = Wide{{expected.parts[0] + 1, expected.parts[1] + 2, expected.parts[2] + 3}}; + } while (!_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &counter, &expected, &next, SeqCst, SeqCst)); + } + }; + std::thread first(worker); + std::thread second(worker); + first.join(); + second.join(); + Require(counter.parts[0] == 2 * Iterations && counter.parts[1] == 4 * Iterations && counter.parts[2] == 6 * Iterations); +} + +int main() { + CheckTyped(); + CheckFlagsAndFences(); + CheckGeneric(); + CheckConcurrentGeneric(); + return 0; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 1638138a0e2d..31f3b6eac175 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -23,6 +23,7 @@ - [sceLibcMspaceReallocalign](../../../core/libs/prx/libc/src/Mspace.cpp) (libSceLibcInternal, implemented in libc) takes `(void* heap, void* block, size_t alignment, size_t size)`, the order of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x13880, whose internal routine at 0x12390 treats the third argument as the alignment and, for a null block, passes it to the routine `sceLibcMspaceMemalign` uses). Measured on a PS5 (firmware 13.60) from a homebrew payload: a null block with alignment 4096 and size 100 returns a 4096-aligned block; an alignment of 0 or 3 succeeds and 100 gives a 128-aligned block, as the routine raises it to at least 32 and to the next power of two; an alignment of 2^63 or more returns null, keeps the block and leaves `errno` alone; and a size of 0 returns a block (the same one when it is already suitable) instead of freeing it. A block that is aligned and large enough is kept where it is, without giving back the bytes past the new size as the console's in-place resize does - [acosl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, like the other libc math functions here, not by the console's (FreeBSD msun ld80 code in Sony's `libc.prx`): results can differ from the console's in the last bit, and on Linux glibc sets `errno` to `EDOM` for `acosl` outside [-1, 1] where msun only raises the floating-point exception. `long double` is the x87 80-bit format, as Sony's `frexpl` uses it (`fldt 0x8(%rsp)`, result in `st(0)`) - [atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, as `acosl` is, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's `log10l(1000)` is not exactly 3), and on Linux glibc sets `errno` to `EDOM` or `ERANGE` where msun only raises the floating-point exception. `ldexpl` and `truncl` are exact operations and give the console's result; `logb` and `remainder` give the console's value too, but on Linux glibc sets `errno` (`ERANGE` for `logb(0)`, `EDOM` for `remainder` by zero or of an infinity) where msun only raises the floating-point exception +- [Dinkumware atomics](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - every operation is sequentially consistent whatever memory order it is given, which is never weaker than the console. Matching the `libc.prx` bundled with PPSA08427 (that binary run side by side on every order from 0 to 7), `_Atomic_load_8` returns 0 for a release, acq_rel or invalid order and the 8-byte exchange and fetch operations return 0 without touching memory for an order above `memory_order_seq_cst`; the generic `_Atomic_copy`, `_Atomic_exchange` and `_Atomic_compare_exchange_*` lock the byte flag passed first, as the console does - [std_execute_once](../../../core/libs/prx/libc/Export.cpp) (libc) - assumed to be the plain-named twin of `std::_Execute_once` (`_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_`, the callee of Dinkumware `call_once`): it delegates there and gets the [thread.once.callonce] semantics already verified for that symbol (one invocation per flag, concurrent callers block until it completes, and a failing or throwing invocation leaves the flag unready so the next caller runs it). The name equivalence is not confirmed on a title, and the callback convention (called with (null, arg, null), non-zero return meaning success) is the twin's, unverified for this symbol - [__atomic_load, __atomic_store, __atomic_exchange and __atomic_compare_exchange](../../../core/libs/prx/libc/src/AtomicBuiltins.cpp) (libc) - the generic, size-parameterised libatomic entry points are a single `ud2` in the `libc.prx` bundled with PPSA08427 (disassembly), so the console traps on any call; these throw instead - [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console. From 7157c989716a626f97299181102c7db8f7e64d0e Mon Sep 17 00:00:00 2001 From: BIGFERRY999 Date: Thu, 15 Oct 2026 01:04:34 +0100 Subject: [PATCH 095/176] feat(libc): implement ceill and floorl (cherry picked from commit b28a84d4215dce46bc825da40fee02767dac3594) --- core/libs/tests/GuestMath.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 18032dad386b..7bebc8d6407e 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -83,6 +83,8 @@ float APS5_VABI copysignf_nid_postfix(float, float); double APS5_VABI remquo_nid_postfix(double, double, int*); float APS5_VABI remquof_nid_postfix(float, float, int*); float APS5_VABI fmaf_nid_postfix(float, float, float); +long double APS5_VABI ceill_nid_postfix(long double); +long double APS5_VABI floorl_nid_postfix(long double); float APS5_VABI tanf_nid_postfix(float); int APS5_VABI rand_nid_postfix(); void APS5_VABI srand_nid_postfix(unsigned int); From f0a05a62ea4af856bd7d3209bae06a0808759215 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:54:17 +0530 Subject: [PATCH 096/176] feat(libSceSysmodule): register libSceProprietaryVoiceChatHelperPrx module id 0x132 (cherry picked from commit 16df3d1536f302f9a1ccd6ae64a5db57ee39ca97) --- docs/dev/debt/libSceSysmodule.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/dev/debt/libSceSysmodule.md b/docs/dev/debt/libSceSysmodule.md index 80f91983aade..67b979439238 100644 --- a/docs/dev/debt/libSceSysmodule.md +++ b/docs/dev/debt/libSceSysmodule.md @@ -4,6 +4,10 @@ - [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module ID 0x00000132 maps to `libSceProprietaryVoiceChatHelperPrx` as verified in [#5017](https://github.com/boykopovar/AnyPS5/pull/5017) and reported in Discussion [#6344](https://github.com/boykopovar/AnyPS5/discussions/6344) for EA SPORTS FC 27 (PPSA34015). +### Known modules + +- [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module ID 0x00000132 maps to `libSceProprietaryVoiceChatHelperPrx` as verified in [#5017](https://github.com/boykopovar/AnyPS5/pull/5017) and reported in Discussion [#6344](https://github.com/boykopovar/AnyPS5/discussions/6344) for EA SPORTS FC 27 (PPSA34015). + ### Functional - [sceSysmoduleGetModuleInfoForUnwind](../../../core/libs/prx/libSceSysmodule/Export.cpp) (libSceSysmodule) - on Windows the region and file name of an address inside a loaded image are cached after the first lookup, because `VirtualQuery` on a large image section is slow. An entry is used only while the loader still reports the same image for the address (same base, time stamp and size). A page protection change inside the image after the first lookup, which would split or merge the region `VirtualQuery` reports, is not noticed. Addresses outside loaded images are queried every time From 34e8e935fec5dc123936c4da7608d4e3f635c257 Mon Sep 17 00:00:00 2001 From: valenciana-nc Date: Sun, 18 Oct 2026 21:28:26 +0000 Subject: [PATCH 097/176] fix(libkernel): validate getdirentries output buffers in the guest arena sceKernelGetdirentries/sceKernelGetdents wrote up to nbytes into buf and 8 bytes into *basep with only null checks, unlike sceKernelRead, pread and readv which all gate on GuestArena::HostWrite. Validate both buffers the same way so a forged pointer fails with EFAULT instead of writing to an arbitrary host address. (cherry picked from commit 5d9a6483b5a4c2f686e341f896a25f3b6450dfa6) --- core/libs/prx/libkernel/File/src/Stdio.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index f147221fd04c..9a9f00936da7 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -975,6 +975,12 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); + if (basep != nullptr) { + const GuestArena::HostWrite baseDestination(basep, sizeof(*basep)); + if (!baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); + } if (!File::DirectoryDescriptorPath(fd)) { if (WithoutParameterHandler([fd] { return ::_get_osfhandle(fd); }) == -1) return SceErrorFromErrno(GUEST_EBADF); return SceErrorFromErrno(GUEST_EINVAL); @@ -1017,6 +1023,15 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas constexpr std::size_t GuestMaxName = 255; if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); + if (basep != nullptr) { + const GuestArena::HostWrite baseDestination(basep, sizeof(*basep)); + if (!baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); + } + struct stat status{}; + if (::fstat(fd, &status) != 0) return SceErrorFromErrno(errno); + if (!S_ISDIR(status.st_mode)) return SceErrorFromErrno(GUEST_EINVAL); const off_t base = ::lseek(fd, 0, SEEK_CUR); if (base < 0) return SceErrorFromErrno(errno); if (basep != nullptr) *basep = static_cast(base); From 54b4a5b64d14fcccb2e25a250aae07761c8be536 Mon Sep 17 00:00:00 2001 From: valenciana-nc Date: Sun, 18 Oct 2026 19:00:40 -0600 Subject: [PATCH 098/176] fix(libkernel): keep the getdirentries base guard open through its write and check the descriptor first (cherry picked from commit 950eeb847d28c08bc70f7cefaf339844d4973d70) --- core/libs/prx/libkernel/File/src/Stdio.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 9a9f00936da7..683046a9953e 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -975,16 +975,13 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); - const GuestArena::HostWrite destination(buf, static_cast(nbytes)); - if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); - if (basep != nullptr) { - const GuestArena::HostWrite baseDestination(basep, sizeof(*basep)); - if (!baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); - } if (!File::DirectoryDescriptorPath(fd)) { if (WithoutParameterHandler([fd] { return ::_get_osfhandle(fd); }) == -1) return SceErrorFromErrno(GUEST_EBADF); return SceErrorFromErrno(GUEST_EINVAL); } + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + const GuestArena::HostWrite baseDestination(basep, basep == nullptr ? 0 : sizeof(*basep)); + if (!destination.Open() || !baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); if (basep != nullptr) *basep = 0; return File::ReadDirectoryDescriptor(fd, buf, nbytes); } @@ -1023,15 +1020,12 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas constexpr std::size_t GuestMaxName = 255; if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); - const GuestArena::HostWrite destination(buf, static_cast(nbytes)); - if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT); - if (basep != nullptr) { - const GuestArena::HostWrite baseDestination(basep, sizeof(*basep)); - if (!baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); - } struct stat status{}; if (::fstat(fd, &status) != 0) return SceErrorFromErrno(errno); if (!S_ISDIR(status.st_mode)) return SceErrorFromErrno(GUEST_EINVAL); + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + const GuestArena::HostWrite baseDestination(basep, basep == nullptr ? 0 : sizeof(*basep)); + if (!destination.Open() || !baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); const off_t base = ::lseek(fd, 0, SEEK_CUR); if (base < 0) return SceErrorFromErrno(errno); if (basep != nullptr) *basep = static_cast(base); From 02213b9e5c4f58b042c3490ebd37df9391f609fa Mon Sep 17 00:00:00 2001 From: Nixitov Date: Fri, 16 Oct 2026 19:59:48 +0200 Subject: [PATCH 099/176] feat(libkernel): declare physhm_open and physhm_unlink (cherry picked from commit ce8f32854a4e499d5df3b5d71caa0f366906619c) --- docs/dev/debt/libkernel.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 356aae3a8a65..9bf84a83af0f 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -15,6 +15,7 @@ - [physhm_open, physhm_unlink](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel, NIDs `n371J5cP+uo` and `AUqJNkobQ1c`) - declared as throwing stubs: the PS5 physical shared memory API is not documented, and [shadPS4's module generator](https://github.com/shadps4-emu/moduleGenerator/blob/main/libraries/kernel.cpp) also declares both with no arguments and returns `ORBIS_OK`, so the signature, the handle a success would return and the console's behaviour are unmeasured. The no-argument `int` signature follows shadPS4. Forever Launcher (PPSA99030) imports both and stops at load on `physhm_open`, and the relinker resolves no call site for either in that title, so no handle is fabricated and both throw - [__aps5_get_pending_module_args_nid_no_patch](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - returns a pointer to a default 16-byte argument block `{size: 0x10, version: 0x200, context: nullptr}` when no arguments are pending (`pendingModuleArgs.argp` is null). Unity's `UnifyPS5Plugin.prx` (Cult of the Lamb PPSA06464, #5621) is loaded as a needed library at process start, where the dynamic linker executes its `DT_INIT` before `sceKernelLoadStartModule` runs; its entry function reads `argp[0]` before checking `args` and expects a block of `{0x10, 0x200, ptr}` or `{0x58, 0x103}`, so a null pointer caused a segmentation fault. The console's default module argument block and its contents for imported modules are unverified. - [sceKernelError](../../../core/libs/prx/libkernel/System/src/Error.cpp) follows [shadPS4's kernel implementation](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/kernel/kernel.cpp): zero returns zero; other guest POSIX error values receive the `0x80020000` bias without modifying errno. Arithmetic is defined modulo 32 bits to avoid signed overflow for inputs outside the errno domain. Console behavior for those inputs and PS5 equivalence have not been measured. +- [physhm_open, physhm_unlink](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel, NIDs `n371J5cP+uo` and `AUqJNkobQ1c`) - declared as throwing stubs: the PS5 physical shared memory API is not documented, and [shadPS4's module generator](https://github.com/shadps4-emu/moduleGenerator/blob/main/libraries/kernel.cpp) also declares both with no arguments and returns `ORBIS_OK`, so the signature, the handle a success would return and the console's behaviour are unmeasured. The no-argument `int` signature follows shadPS4. Forever Launcher (PPSA99030) imports both and stops at load on `physhm_open`, and the relinker resolves no call site for either in that title, so no handle is fabricated and both throw - [getlogin_r](../../../core/libs/prx/libkernel/System/src/Login.cpp) (libkernel) returns the host effective account name: `GetUserNameW` as UTF-8 on Windows and `getpwuid_r(geteuid())` on POSIX. A valid account returns a name even without a login session; host `getlogin_r`, `USER` and `LOGNAME` are not consulted. The console's login name and behaviour without a session are unknown. Account lookup or encoding failures, a missing or empty account name, and POSIX account records exceeding the 16 KiB lookup buffer throw; there is no replacement name or retry. A short guest buffer returns `ERANGE` without modifying it, and a null output returns `EFAULT`. - [sceKernelGetSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports baseline system software version `0x01000000` ("01.000.000") for ProsperoStore (PPSA99000); the console's firmware version is not modeled. Measured on PS5 firmware 13.42: record values 0, 24, 39, 40, 41, 48 and 64 in the leading 64-bit field are accepted and preserved, the 28-byte string at offset 8 and packed version at 0x24 are filled, and bytes from 0x28 onward remain untouched. This API reports "13.590.001" / `0x13590001` in that environment, while the Prospero variant reports "13.420.000" / `0x13420000`; their values must not be assumed identical. A null pointer still returns `SCE_KERNEL_ERROR_EFAULT`, which was not measured; other firmware versions and record values remain unverified. - [kern.rng_pseudo](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` fills the whole output buffer with bytes from the host's `std::random_device` and leaves the length unchanged; a write returns -1 with `EPERM` and a call without an output buffer throws. The only caller seen is mbedTLS's `mbedtls_hardware_poll` in the catalog's ProsperoLight boilerplate, which asks for 64-byte blocks and fails if fewer come back. The console's generator, its per-call maximum and its answer to a size query are unknown From 7661c21823a6975b6fdcade6c5b6c816cb4feb86 Mon Sep 17 00:00:00 2001 From: valenciana-nc Date: Sun, 18 Oct 2026 19:02:58 -0600 Subject: [PATCH 100/176] test(libkernel_sys): read accepted symlink targets back through readlink and remove them (cherry picked from commit 1a200a1e149d428dd6b307de3b30ef68bd5c2285) --- core/libs/tests/GuestKernelSys.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 8291d5a7b35e..32cedd479f18 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -97,16 +97,21 @@ void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { for (const auto& target : {std::string("C:/"), std::string("\\\\server\\share")}) Require(SymlinkTargetThrows(target.c_str(), created)); #endif + const auto links = [](const std::string& created, const char* target) { + char buffer[16]; + const auto length = std::strlen(target); + return readlink_nid_postfix(created.c_str(), buffer, sizeof(buffer)) == static_cast(length) && + std::memcmp(buffer, target, length) == 0; + }; const auto nested = (root / "accepted-nested").string(); const int link = symlink_nid_postfix("sub/dir", nested.c_str()); if (link == 0) { - Require(std::filesystem::is_symlink(std::filesystem::symlink_status(nested))); + Require(links(nested, "sub/dir")); const auto dots = (root / "accepted-dots").string(); - Require(symlink_nid_postfix("a..b", dots.c_str()) == 0); - Require(std::filesystem::is_symlink(std::filesystem::symlink_status(dots))); + Require(symlink_nid_postfix("a..b", dots.c_str()) == 0 && links(dots, "a..b")); const auto ellipsis = (root / "accepted-ellipsis").string(); - Require(symlink_nid_postfix("...", ellipsis.c_str()) == 0); - Require(std::filesystem::is_symlink(std::filesystem::symlink_status(ellipsis))); + Require(symlink_nid_postfix("...", ellipsis.c_str()) == 0 && links(ellipsis, "...")); + Require(std::filesystem::remove(nested) && std::filesystem::remove(dots) && std::filesystem::remove(ellipsis)); } else { Require(link == -1 && Error() == 1); std::fprintf(stderr, "symbolic links unavailable: the symlink target success cases not tested\n"); From 43036abb410cc1faa91b5a510b35427a791a4b84 Mon Sep 17 00:00:00 2001 From: valenciana-nc Date: Sun, 18 Oct 2026 19:04:54 -0600 Subject: [PATCH 101/176] fix(libkernel): check the AIO Multiple batch limit before reading the requests (cherry picked from commit 767ea1ea0f7e6154acd13d0e7b71a4f0706dda4e) --- core/libs/prx/libkernel/Aio/src/Aio.cpp | 2 +- core/libs/tests/GuestAio.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index b30777014d4a..d09937b56a58 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -128,9 +128,9 @@ int SubmitCommands(KernelAioRwRequest* req, std::int32_t size, bool write, std:: } int SubmitCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, bool write, std::int32_t* id, const char* name) { + if (req != nullptr && id != nullptr && size > AioRequestNumMax) throw std::runtime_error(std::string(name) + ": more than 128 requests per batch is not modelled"); const int error = ValidateRequests(req, size, id); if (error != 0) return error; - if (size > AioRequestNumMax) throw std::runtime_error(std::string(name) + ": more than 128 requests per batch is not modelled"); for (std::int32_t i = 0; i < size; ++i) { const std::int32_t queue = AllocateId(); SetState(queue, RunRequest(req[i], write) ? AioCompleted : AioAborted); diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index a0bc2b611fd8..a401b9032155 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -341,6 +341,16 @@ int main(int argc, char** argv) { Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 0, 0, writeIds) == SCE_KERNEL_ERROR_EINVAL); KernelAioRwRequest noResult{0, 1, readBack.data(), nullptr, batchFd}; Check(sceKernelAioSubmitWriteCommandsMultiple(&noResult, 1, 0, writeIds) == SCE_KERNEL_ERROR_EFAULT); + std::array unread{}; + std::array unreadIds{}; + for (const auto submit : {sceKernelAioSubmitReadCommandsMultiple, sceKernelAioSubmitWriteCommandsMultiple}) { + bool limited = false; + try { submit(unread.data(), 129, 0, unreadIds.data()); } catch (const std::runtime_error&) { limited = true; } + Check(limited); + } + std::int32_t unreadId = 0; + Check(sceKernelAioSubmitReadCommands(unread.data(), 129, 0, &unreadId) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioSubmitWriteCommands(unread.data(), 129, 0, &unreadId) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, 2, nullptr, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(nullptr, 2, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, -1, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); From cb0952a8c7844bc53191448c9cfb08e3fe0ae689 Mon Sep 17 00:00:00 2001 From: Jota <139649225+jotitadev@users.noreply.github.com> Date: Sun, 18 Oct 2026 23:01:26 -0300 Subject: [PATCH 102/176] test(audioin): skip the silent blocks a late capture device leaves sceAudioInInput waits four block lengths (21 ms at 256 samples and 48 kHz) for the host device and returns silence when nothing arrived. guest_audio_in_capture required the recording in the first four blocks after sceAudioInOpen and in the first block after sceAudioInAsyncOpen. On a loaded host the SDL disk driver's thread starts or runs late, a block comes back silent, and the test aborted at captured == recorded. Read until a block carries sound, at most 200 blocks, and check the recording as before. (cherry picked from commit b079a96e3eee7fc2a3b12c3d0a6fdfe7c0325107) --- core/libs/tests/GuestAudioIn.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/libs/tests/GuestAudioIn.cpp b/core/libs/tests/GuestAudioIn.cpp index d9e957911493..0071148f888d 100644 --- a/core/libs/tests/GuestAudioIn.cpp +++ b/core/libs/tests/GuestAudioIn.cpp @@ -74,6 +74,14 @@ void TestValidation() { Require(ReportsNotImplemented("sceAudioInAsyncOpen", [] { sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 0x10); })); } +void InputSound(int handle, std::vector& block) { + for (int attempt = 0; attempt < 200; ++attempt) { + Require(sceAudioInInput(handle, block.data()) == 256); + if (std::any_of(block.begin(), block.end() - 1, [](std::int16_t sample) { return sample != 0; })) return; + } + Require(false); +} + void TestCapture() { const auto path = std::filesystem::temp_directory_path() / ("anyps5_audio_in_capture-" + std::to_string(std::random_device{}()) + ".raw"); std::vector recorded(256 * 2 * 4); @@ -88,7 +96,7 @@ void TestCapture() { std::vector block(256 * 2 + 1, 0x5555); std::vector captured; for (int i = 0; i < 4; ++i) { - Require(sceAudioInInput(handle, block.data()) == 256); + InputSound(handle, block); captured.insert(captured.end(), block.begin(), block.end() - 1); Require(block.back() == 0x5555); } @@ -98,7 +106,7 @@ void TestCapture() { Require(sceAudioInClose(handle) == 0); const int async = sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 2); Require(async == handle && sceAudioInGetSilentState(async) == 0); - Require(sceAudioInInput(async, block.data()) == 256); + InputSound(async, block); Require(std::equal(block.begin(), block.end() - 1, recorded.begin()) && block.back() == 0x5555); Require(sceAudioInClose(async) == 0); std::filesystem::remove(path); From 08d5aa06d0e42037c14cfac157cda2f5e2eb6963 Mon Sep 17 00:00:00 2001 From: fdfawef324 <287205748+fdfawef324@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:57:16 -0400 Subject: [PATCH 103/176] fix(recompiler): key shader caches on compiled sources Co-Authored-By: Codex (cherry picked from commit f3f453ec9d97234ed124424bdca41a292fb21433) --- CMakeLists.txt | 4 + core/shader/recompiler/CMakeLists.txt | 9 +- .../recompiler/ShaderCacheVersion.cmake | 4 +- .../test_shader_cache_runtime_sources.py | 182 ++++++++++++++++++ 4 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 tools/tests/test_shader_cache_runtime_sources.py diff --git a/CMakeLists.txt b/CMakeLists.txt index b07cfe7c953d..3fac0caf5901 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,10 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/test_shader_cache_native_paths.py ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ShaderCacheVersion.cmake ${CMAKE_COMMAND}) set_tests_properties(shader_cache_native_paths PROPERTIES TIMEOUT 20) + add_test(NAME shader_cache_runtime_sources COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_shader_cache_runtime_sources.py + ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler ${CMAKE_COMMAND}) + set_tests_properties(shader_cache_runtime_sources PROPERTIES TIMEOUT 180) add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_encoding.py) add_test(NAME pr_overlap_dependencies COMMAND ${Python3_EXECUTABLE} diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 6f2f8a6380b9..36fa3f3ab003 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -168,16 +168,22 @@ function(add_shader_recompiler target runtimeTarget) target_compile_features(${target} PUBLIC cxx_std_20) + if(ANYPS5_ENABLE_SPIRV_TOOLS) + target_sources(${target} PRIVATE ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvOptimizer.cpp) + endif() + set(shaderCacheGeneratedDir "${CMAKE_CURRENT_BINARY_DIR}/${target}_generated") set(shaderCacheVersionSource "${shaderCacheGeneratedDir}/ShaderCacheVersion.cpp") file(GLOB_RECURSE shaderCacheRecompilerSources CONFIGURE_DEPENDS - "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.cpp" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.hpp" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.h" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.inc" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.cmake" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeLists.txt" ) + get_target_property(shaderCacheCompiledSources ${target} SOURCES) + list(APPEND shaderCacheRecompilerSources ${shaderCacheCompiledSources}) + list(REMOVE_DUPLICATES shaderCacheRecompilerSources) set(shaderCacheKeySources "${PROJECT_SOURCE_DIR}/core/libs/prx/libc/include/HostThreadLocal.hpp") foreach(source IN LISTS shaderCacheKeySources) if(NOT EXISTS "${source}") @@ -313,7 +319,6 @@ function(add_shader_recompiler target runtimeTarget) ) if(ANYPS5_ENABLE_SPIRV_TOOLS) - target_sources(${target} PRIVATE ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvOptimizer.cpp) target_link_libraries(${target} PRIVATE SPIRV-Tools-opt SPIRV-Tools-static) endif() target_compile_definitions(${target} PUBLIC ANYPS5_ENABLE_SPIRV_TOOLS=$) diff --git a/core/shader/recompiler/ShaderCacheVersion.cmake b/core/shader/recompiler/ShaderCacheVersion.cmake index 81be546106a8..847a51349674 100644 --- a/core/shader/recompiler/ShaderCacheVersion.cmake +++ b/core/shader/recompiler/ShaderCacheVersion.cmake @@ -26,7 +26,6 @@ endforeach() list(SORT sources) list(SORT guard) get_filename_component(rootReal "${ROOT}" REALPATH) -get_filename_component(recompilerReal "${RECOMPILER_DIR}" REALPATH) set(sourcesReal) foreach(source IN LISTS sources) get_filename_component(real "${source}" REALPATH) @@ -70,9 +69,8 @@ foreach(source IN LISTS sources) if(NOT resolved) continue() endif() - string(FIND "${resolved}" "${recompilerReal}/" resolvedInRecompiler) string(FIND "${resolved}" "${rootReal}/3rdparty/" resolvedInThirdParty) - if(resolvedInRecompiler EQUAL 0 OR resolvedInThirdParty EQUAL 0 OR resolved IN_LIST sourcesReal) + if(resolvedInThirdParty EQUAL 0 OR resolved IN_LIST sourcesReal) continue() endif() file(RELATIVE_PATH relativeResolved "${rootReal}" "${resolved}") diff --git a/tools/tests/test_shader_cache_runtime_sources.py b/tools/tests/test_shader_cache_runtime_sources.py new file mode 100644 index 000000000000..64221cceb252 --- /dev/null +++ b/tools/tests/test_shader_cache_runtime_sources.py @@ -0,0 +1,182 @@ +from pathlib import Path +import os +import re +import shutil +import subprocess +import sys +import tempfile +import unittest + + +SOURCE = Path(sys.argv.pop(1)) +CMAKE = sys.argv.pop(1) + + +class Fixture: + def __init__(self, root, spirv_tools=False): + self.root = root + self.recompiler = root / 'core/shader/recompiler' + self.build = root / 'build' + self.calls = 0 + for directory, directories, files in os.walk(SOURCE, followlinks=False): + for path in (Path(directory), *(Path(directory) / name for name in directories + files)): + if path.is_symlink() or getattr(path.lstat(), 'st_file_attributes', 0) & 0x400: + raise AssertionError('The source fixture contains a reparse point') + shutil.copytree(SOURCE, self.recompiler) + header = root / 'core/libs/prx/libc/include/HostThreadLocal.hpp' + header.parent.mkdir(parents=True) + header.write_text('struct FixtureThreadLocal {};\n', encoding='utf-8') + self.guard = root / 'driver.cpp' + self.guard.write_text('int fixture_driver;\n', encoding='utf-8') + compiler = root / ('compiler.cmd' if os.name == 'nt' else 'compiler') + if os.name == 'nt': + compiler.write_text('@echo off\nif "%~1"=="-print-sysroot" if "%~2"=="" exit /b 0\nif "%~1"=="--version" if "%~2"=="" exit /b 0\n>>"%~dp0compiler-invoked" echo %*\nexit /b 99\n', encoding='utf-8') + else: + compiler.write_text('#!/bin/sh\nif [ "$#" -eq 1 ]; then case "$1" in -print-sysroot|--version) exit 0;; esac; fi\nprintf "%s\\n" "$*" >> "$(dirname "$0")/compiler-invoked"\nexit 99\n', encoding='utf-8') + compiler.chmod(0o755) + mode = 'ON' if spirv_tools else 'OFF' + (root / 'CMakeLists.txt').write_text(f'''cmake_minimum_required(VERSION 3.20) +set(CMAKE_CXX_COMPILER "{compiler.as_posix()}") +set(CMAKE_CXX_COMPILER_ID GNU) +set(CMAKE_CXX_COMPILER_VERSION 15.2.0) +set(CMAKE_CXX_COMPILER_ID_RUN TRUE) +set(CMAKE_CXX_COMPILER_FORCED TRUE) +set(CMAKE_CXX_COMPILER_WORKS TRUE) +set(CMAKE_CXX_ABI_COMPILED TRUE) +set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT 17) +set(CMAKE_CXX_EXTENSIONS_COMPUTED_DEFAULT ON) +set(CMAKE_CXX_COMPILE_FEATURES cxx_std_20) +project(ShaderCacheSourceFixture LANGUAGES CXX) +set(CMAKE_BUILD_TYPE Release) +set(ANYPS5_ENABLE_SPIRV_TOOLS {mode}) +set(ANYPS5_USE_SYSTEM_DEPS ON) +set(ANYPS5_SYSTEM_DEPS_STAMP fixture) +add_library(fixture_runtime INTERFACE) +add_library(glslang INTERFACE) +add_library(SPIRV INTERFACE) +add_library(SPIRV-Tools-opt INTERFACE) +add_library(SPIRV-Tools-static INTERFACE) +include("${{CMAKE_CURRENT_SOURCE_DIR}}/core/shader/recompiler/CMakeLists.txt") +add_shader_recompiler(shader_recompiler_probe fixture_runtime GUARD_SOURCES "${{CMAKE_CURRENT_SOURCE_DIR}}/driver.cpp") +get_target_property(runtimeSources shader_recompiler_probe SOURCES) +list(JOIN runtimeSources "\\n" runtimeSources) +file(WRITE "${{CMAKE_CURRENT_BINARY_DIR}}/runtime-sources.txt" "${{runtimeSources}}\\n") +''', encoding='utf-8') + self.configure() + + def configure(self, flags=''): + result = subprocess.run([CMAKE, '-S', str(self.root), '-B', str(self.build), '-G', 'Ninja', f'-DCMAKE_CXX_FLAGS={flags}'], + capture_output=True, encoding='utf-8', errors='replace', timeout=15) + if result.returncode: + raise AssertionError(result.stdout + result.stderr) + if (self.root / 'compiler-invoked').exists(): + raise AssertionError('The generator fixture invoked a compiler: ' + (self.root / 'compiler-invoked').read_text(encoding='utf-8')) + self.generated = self.build / 'shader_recompiler_probe_generated' + self.listing = self.generated / 'sources.txt' + ninja = (self.build / 'build.ninja').read_text(encoding='utf-8') + command = next(line for line in ninja.splitlines() if line.startswith(' COMMAND = ') and 'ShaderCacheVersion.cmake' in line) + self.extra = re.search(r'"-DEXTRA=([^"]*)"', command).group(1) + self.compiled = [] + for line in ninja.splitlines(): + match = re.match(r'build .*: CXX_COMPILER__shader_recompiler_probe_[^ ]+ (.*?) \|\|', line) + if match: + self.compiled.append(Path(match.group(1).replace('$:', ':').replace('$ ', ' '))) + + def version(self, success=True): + self.calls += 1 + output = self.build / f'probe-version-{self.calls}.cpp' + result = subprocess.run([CMAKE, f'-DROOT={self.root.as_posix()}', + f'-DRECOMPILER_DIR={self.recompiler.as_posix()}', + f'-DLIST_FILE={self.listing.as_posix()}', f'-DOUTPUT={output.as_posix()}', + f'-DEXTRA={self.extra}', '-P', str(self.recompiler / 'ShaderCacheVersion.cmake')], + capture_output=True, encoding='utf-8', errors='replace', timeout=10) + if not success: + if result.returncode == 0 or output.exists(): + raise AssertionError('The generator accepted an unkeyed dependency') + return result.stdout + result.stderr + if result.returncode: + raise AssertionError(result.stdout + result.stderr) + return re.search(r'SourceVersion = 0x([0-9a-f]+)ull', output.read_text(encoding='utf-8')).group(1) + + def sources(self): + return {Path(line.split(' ', 1)[1]) for line in self.listing.read_text(encoding='utf-8').splitlines() if line.startswith('sources ')} + + +class ShaderCacheRuntimeSourceTests(unittest.TestCase): + def fixture(self, spirv_tools=False): + temporary = tempfile.TemporaryDirectory(prefix='shader_cache_sources_') + self.addCleanup(temporary.cleanup) + return Fixture(Path(temporary.name), spirv_tools) + + def test_runtime_source_graph_off_and_on(self): + for enabled in (False, True): + with self.subTest(spirv_tools=enabled): + fixture = self.fixture(enabled) + generated = fixture.generated / 'ShaderCacheVersion.cpp' + selected = {Path(line) for line in (fixture.build / 'runtime-sources.txt').read_text(encoding='utf-8').splitlines()} + self.assertEqual(set(fixture.compiled), selected) + self.assertNotIn(generated, fixture.sources()) + self.assertEqual({path for path in fixture.sources() if path.suffix in ('.cpp', '.cc', '.cxx')}, selected - {generated}) + self.assertIn(fixture.recompiler / 'tests/DummyShaders.cpp', fixture.sources()) + self.assertEqual(fixture.recompiler / 'SpirvBackend/src/SpirvOptimizer.cpp' in fixture.sources(), enabled) + fixture.version() + + def test_uncompiled_test_sources_do_not_change_version(self): + fixture = self.fixture() + previous = fixture.version() + for name in ('CacheFileReadTests.cpp', 'RequestSerializerTests.cpp'): + path = fixture.recompiler / 'tests' / name + self.assertNotIn(path, fixture.sources()) + path.write_bytes(path.read_bytes() + b'\n') + self.assertEqual(fixture.version(), previous, name) + (fixture.recompiler / 'tests/NewFixture.cpp').write_text('int new_fixture;\n', encoding='utf-8') + fixture.configure() + self.assertEqual(fixture.version(), previous) + + def test_runtime_sources_headers_and_cmake_change_version(self): + fixture = self.fixture() + previous = fixture.version() + for name in ('Recompiler.cpp', 'tests/DummyShaders.cpp', 'tests/DummyShaders.hpp', 'BdaAbi.hpp', 'CMakeLists.txt', 'ShaderCacheVersion.cmake'): + with self.subTest(source=name): + path = fixture.recompiler / name + before = path.read_bytes() + path.write_bytes(before + b'\n') + self.assertNotEqual(fixture.version(), previous) + path.write_bytes(before) + self.assertEqual(fixture.version(), previous) + + def test_flags_change_version(self): + fixture = self.fixture() + previous = fixture.version() + fixture.configure('-DSHADER_CACHE_FIXTURE=1') + self.assertNotEqual(fixture.version(), previous) + + def test_optimizer_content_only_changes_enabled_version(self): + for enabled in (False, True): + with self.subTest(spirv_tools=enabled): + fixture = self.fixture(enabled) + previous = fixture.version() + optimizer = fixture.recompiler / 'SpirvBackend/src/SpirvOptimizer.cpp' + optimizer.write_bytes(optimizer.read_bytes() + b'\n') + self.assertEqual(fixture.version() == previous, not enabled) + + def test_uncompiled_internal_include_is_rejected(self): + fixture = self.fixture() + source = fixture.recompiler / 'Recompiler.cpp' + source.write_text('#include "tests/CacheFileReadTests.cpp"\n' + source.read_text(encoding='utf-8'), encoding='utf-8') + self.assertIn('does not hash', fixture.version(False)) + + def test_unhashed_external_include_is_rejected(self): + fixture = self.fixture() + (fixture.root / 'external.hpp').write_text('int external_fixture;\n', encoding='utf-8') + source = fixture.recompiler / 'Recompiler.cpp' + source.write_text('#include "../../../external.hpp"\n' + source.read_text(encoding='utf-8'), encoding='utf-8') + self.assertIn('does not hash', fixture.version(False)) + + def test_unreviewed_driver_call_is_rejected(self): + fixture = self.fixture() + fixture.guard.write_text('ShaderRecompiler::UnreviewedFixtureCall();\n', encoding='utf-8') + self.assertIn('has not been reviewed', fixture.version(False)) + + +unittest.main() From 3a5d7f65e1ec38a753fef1be0eb4ee897773d668 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Mon, 19 Oct 2026 06:01:26 +0200 Subject: [PATCH 104/176] test(libSceNetCtl): cover the plain callback registration in guest_sce_net sceNetCtlRegisterCallback and sceNetCtlUnregisterCallback share the callback table logic with the V6 pair, which guest_sce_net checks, but the plain pair is not called by any test. The new block mirrors the V6 one (null function, null output, eight slots, the ninth refused, invalid ids, slot reuse) with the guest-ABI IgnoreNetCtlEvent callback, and checks that the two tables are separate. Co-Authored-By: Claude Sonnet 5.5 (cherry picked from commit ba9dbc208aecf4fbb96ee472cdee0e936df793bd) --- core/libs/tests/GuestSceNet.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 293007c76e8c..c88cc9a3ccb3 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -66,6 +66,8 @@ int APS5_VABI sceNetResolverDestroy(int); int APS5_VABI sceNetResolverGetError(int, int*); int APS5_VABI sceNetCtlGetState(int*); int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat*); +int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*); +int APS5_VABI sceNetCtlUnregisterCallback(int); int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*); int APS5_VABI sceNetCtlUnregisterCallbackV6(int); std::uint64_t NetCtlReadByteCounter_nid_no_patch(const char*); @@ -1527,6 +1529,27 @@ int main(int argc, char** argv) { Require(sceNetCtlUnregisterCallbackV6(i) == 0); } } + { + int cid = -1; + Require(sceNetCtlRegisterCallback(nullptr, nullptr, &cid) == static_cast(0x80412107)); + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, nullptr) == static_cast(0x80412107)); + int cids[8] = {}; + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cids[i]) == 0); + Require(cids[i] == i); + } + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == static_cast(0x80412103)); + Require(sceNetCtlRegisterCallbackV6(IgnoreNetCtlEvent, nullptr, &cid) == 0); + Require(sceNetCtlUnregisterCallback(-1) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallback(8) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallback(3) == 0); + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == 0); + Require(cid == 3); + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlUnregisterCallback(i) == 0); + } + Require(sceNetCtlUnregisterCallbackV6(0) == 0); + } if (ipv6Unavailable) { std::puts("skipped the IPv6 socket checks, the host has no usable IPv6 loopback"); return 77; From 07934e982437fa2f9fcc94f3f4337086d2b7e885 Mon Sep 17 00:00:00 2001 From: fogydes Date: Thu, 15 Oct 2026 12:16:53 +0530 Subject: [PATCH 105/176] feat(libSceSysmodule): register missing user and internal sysmodule IDs (cherry picked from commit fcdf04685505735fe9d68a22d30ae370a82c042f) --- core/libs/prx/libSceSysmodule/ModuleTable.hpp | 62 +++++++++++++++++++ core/libs/tests/GuestSysmodule.cpp | 36 +++++++++++ docs/dev/debt/libSceSysmodule.md | 4 ++ 3 files changed, 102 insertions(+) diff --git a/core/libs/prx/libSceSysmodule/ModuleTable.hpp b/core/libs/prx/libSceSysmodule/ModuleTable.hpp index 890a993b2a7a..eba12e75ac66 100644 --- a/core/libs/prx/libSceSysmodule/ModuleTable.hpp +++ b/core/libs/prx/libSceSysmodule/ModuleTable.hpp @@ -7,6 +7,7 @@ inline const std::unordered_map kModuleTable = { {0x00000001, "libSceAudioOut"}, {0x00000002, "libSceAudioIn"}, + {0x00000005, "libScePerf"}, {0x00000006, "libSceFiber"}, {0x00000007, "libSceUlt"}, {0x0000000b, "libSceNgs2"}, @@ -147,9 +148,11 @@ inline const std::unordered_map kModuleTable = { {0x00000107, "libSceGic"}, {0x00000108, "libScePlayReady2"}, {0x00000109, "libSceHdrScopes"}, + {0x0000010a, "libSceDeci5"}, {0x0000010b, "libSceAcm"}, {0x0000010c, "libSceCesCs"}, {0x0000010d, "libScePlayerInvitationDialog"}, + {0x0000010e, "libScePlayerSelectionDialog"}, {0x0000010f, "libSceShare"}, {0x00000110, "libSceNpTrophy2"}, {0x00000112, "libSceNpSessionSignaling"}, @@ -158,8 +161,16 @@ inline const std::unordered_map kModuleTable = { {0x00000115, "libSceNpCppWebApi"}, {0x00000116, "libSceHubAppUtil"}, {0x0000011a, "libSceNpPartner001"}, + {0x0000011b, "libSceFrontPanelDisplay"}, {0x00000120, "libSceAudioPropagation"}, + {0x00000125, "libSceTextToSpeech2"}, + {0x00000126, "libSceCdlgG2P"}, + {0x00000127, "libSceCdlgPlayerReview"}, + {0x00000128, "libSceDepth2"}, + {0x0000012a, "libSceHmd2"}, + {0x0000012b, "libSceVrTracker2"}, {0x0000012f, "libSceFontGs"}, + {0x00000130, "libSceVrSetupDialog"}, {0x00000132, "libSceProprietaryVoiceChatHelperPrx"}, {0x00000135, "libSceFontGsm"}, {0x00000137, "libScePsml"}, @@ -274,23 +285,74 @@ inline const std::unordered_map kModuleTable = { {0x80000078, "libSceHttpCache"}, {0x80000079, "libSceNKWeb"}, {0x8000007a, "libSceNKWebKit"}, + {0x8000007b, "libSceGpuTrace"}, {0x8000007c, "libSceVnaInternal"}, {0x8000007d, "libSceVnaWebsocket"}, + {0x8000007e, "libSceAjmi"}, + {0x8000007f, "libSceGpuCapture"}, + {0x80000080, "libSceAgcDriver"}, + {0x80000082, "libSceAudiodecCpuTrhd"}, + {0x80000083, "libSceAudioSystem"}, + {0x80000084, "libSceVenc"}, + {0x80000085, "libSceAvcap2"}, + {0x80000086, "libSceAgcVsh"}, + {0x80000087, "libSceAgcVshDebug"}, + {0x80000088, "libSceSystemLogger2NativeQueueClient"}, + {0x80000089, "libSceSystemLogger2Delivery"}, {0x8000008a, "libSceComposite"}, {0x8000008b, "libSceCompositeExt"}, {0x8000008c, "libSceHttp2"}, {0x8000008d, "libSceNpGameIntent"}, + {0x8000008e, "libSceShareInternal"}, {0x8000008f, "libSceNpWebApi2"}, + {0x80000090, "libSceNgs2"}, + {0x80000091, "libSceVcodec"}, + {0x80000092, "libSceResourceArbitrator"}, + {0x80000093, "libSceAgcResourceRegistration"}, {0x80000094, "libSceAgc"}, + {0x80000095, "libSceMediaFrameworkInterface"}, + {0x80000097, "libSceSystemTts"}, {0x80000098, "libScePosixForWebKit"}, + {0x80000099, "libSceVoiceCommand"}, {0x8000009a, "libSceNpRemotePlaySessionSignaling"}, + {0x8000009b, "libSceEmbeddedTtsCoreG3"}, + {0x8000009c, "libSceEmbeddedTts"}, + {0x8000009e, "libSceJemspace"}, + {0x8000009f, "libSceSystemLogger2Game"}, + {0x800000a0, "libSceNpTcs"}, + {0x800000a1, "libSceAbstractTcs"}, + {0x800000a2, "libSceTEEClient"}, + {0x800000a3, "libSceBgsStorage"}, + {0x800000a4, "libSceWebmParserMdrw"}, + {0x800000a6, "libSceIdu"}, + {0x800000a7, "libSceAppDbShellCoreClient"}, + {0x800000a8, "libSceIcu"}, + {0x800000a9, "libSceGLSlimVSH"}, {0x800000aa, "libicu"}, {0x800000ab, "libpng16"}, {0x800000ac, "libcairo"}, + {0x800000ad, "libSceContentListController"}, + {0x800000ae, "libSceSocialScreen"}, + {0x800000af, "libSceVdecSvp9"}, + {0x800000b0, "libSceJsc"}, {0x800000b1, "libcurl"}, + {0x800000b2, "libSceVideoStreamingEngine_sys"}, + {0x800000b3, "libSceSystemLogger2"}, + {0x800000b4, "libSceJxr"}, + {0x800000b5, "libSceJxrParser"}, + {0x800000b6, "libSceMediaFrameworkUtil"}, + {0x800000b7, "libSceAmpr"}, {0x800000b8, "libSceLibreSsl3"}, + {0x800000b9, "libSceRemotePlayClientIpc"}, + {0x800000ba, "libSceEsvmEngine"}, + {0x800000bb, "libSceWorkspace"}, + {0x800000bc, "libSceAvcap2mvr"}, + {0x800000bd, "libSceHdrScopes"}, + {0x800000be, "libSceAppDbShellCoreClientForSystemService"}, + {0x800000bf, "libwvoec"}, {0x800000c0, "libSceGLSlimClientVSH"}, {0x800000c1, "libSceGLSlimServerVSH"}, + {0x800000c3, "libSceRnpsBundle"}, }; #endif diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index ac2d59886da8..3f30628676fb 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -51,9 +51,14 @@ constexpr std::uint32_t kInternalFlag = 0x80000000u; constexpr std::uint16_t kAcmModuleId = 0x010b; constexpr std::uint16_t kTrophy2ModuleId = 0x0110; constexpr std::uint16_t kVoiceChatModuleId = 0x0114; +constexpr std::uint16_t kPerfModuleId = 0x0005; +constexpr std::uint16_t kVrSetupDialogModuleId = 0x0130; constexpr std::uint16_t kProprietaryVoiceChatHelperModuleId = 0x0132; constexpr std::uint16_t kPsmlModuleId = 0x0137; +constexpr std::uint32_t kAgcDriverModuleId = 0x80000080; +constexpr std::uint32_t kNgs2InternalModuleId = 0x80000090; constexpr std::uint32_t kAgcModuleId = 0x80000094; +constexpr std::uint32_t kHdrScopesInternalModuleId = 0x800000bd; #ifdef __linux__ struct Mapping { @@ -246,6 +251,37 @@ int main() { Require(sceSysmoduleUnloadModule(kProprietaryVoiceChatHelperModuleId) == 0); Require(sceSysmoduleIsLoaded(kProprietaryVoiceChatHelperModuleId) == kModuleUnloaded); + Require(sceSysmoduleIsLoaded(kPerfModuleId) == kModuleUnloaded); + Require(sceSysmoduleUnloadModule(kPerfModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModule(kPerfModuleId) == 0); + Require(sceSysmoduleIsLoaded(kPerfModuleId) == 0); + Require(sceSysmoduleUnloadModule(kPerfModuleId) == 0); + Require(sceSysmoduleIsLoaded(kPerfModuleId) == kModuleUnloaded); + + Require(sceSysmoduleIsLoaded(kVrSetupDialogModuleId) == kModuleUnloaded); + Require(sceSysmoduleUnloadModule(kVrSetupDialogModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModule(kVrSetupDialogModuleId) == 0); + Require(sceSysmoduleIsLoaded(kVrSetupDialogModuleId) == 0); + Require(sceSysmoduleUnloadModule(kVrSetupDialogModuleId) == 0); + Require(sceSysmoduleIsLoaded(kVrSetupDialogModuleId) == kModuleUnloaded); + + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleLoadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == kModuleUnloaded); + + Require(sceSysmoduleUnloadModuleInternal(kNgs2InternalModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModuleInternal(kNgs2InternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kNgs2InternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kNgs2InternalModuleId) == kModuleUnloaded); + + Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModuleInternal(kHdrScopesInternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == kModuleUnloaded); + Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleLoadModuleInternal(kAgcModuleId) == 0); Require(sceSysmoduleLoadModuleInternal(kAgcModuleId) == 0); diff --git a/docs/dev/debt/libSceSysmodule.md b/docs/dev/debt/libSceSysmodule.md index 67b979439238..b25f89b71048 100644 --- a/docs/dev/debt/libSceSysmodule.md +++ b/docs/dev/debt/libSceSysmodule.md @@ -7,6 +7,10 @@ ### Known modules - [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module ID 0x00000132 maps to `libSceProprietaryVoiceChatHelperPrx` as verified in [#5017](https://github.com/boykopovar/AnyPS5/pull/5017) and reported in Discussion [#6344](https://github.com/boykopovar/AnyPS5/discussions/6344) for EA SPORTS FC 27 (PPSA34015). +- [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module id 0x00000132 is named `libSceProprietaryVoiceChatHelperPrx`, the name of the built module, because `sceSysmoduleGetModuleHandleInternal` opens `libs/.prx`. The name the console's table uses for this id is not verified +- [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module IDs registered from decrypted PS5 firmware 1.00–3.20 sysmodule lookup tables: + - User-space IDs (0x00000005, 0x0000010a, 0x0000010e, 0x0000011b, 0x00000125, 0x00000126, 0x00000127, 0x00000128, 0x0000012a, 0x0000012b, 0x00000130) and internal IDs (0x8000007b, 0x8000007e, 0x8000007f, 0x80000080, 0x80000082, 0x80000083, 0x80000084, 0x80000085, 0x80000086, 0x80000087, 0x80000088, 0x80000089, 0x8000008e, 0x80000090, 0x80000091, 0x80000092, 0x80000093, 0x80000095, 0x80000097, 0x80000099, 0x8000009b, 0x8000009c, 0x8000009e, 0x8000009f, 0x800000a0, 0x800000a1, 0x800000a2, 0x800000a3, 0x800000a4, 0x800000a6, 0x800000a7, 0x800000a8, 0x800000a9, 0x800000ad, 0x800000ae, 0x800000af, 0x800000b0, 0x800000b2, 0x800000b3, 0x800000b4, 0x800000b5, 0x800000b6, 0x800000b7, 0x800000b9, 0x800000ba, 0x800000bb, 0x800000bc, 0x800000bd, 0x800000be, 0x800000bf, 0x800000c3); whether all mapped PRX modules exist as separate runtime files or are embedded in system daemons on retail consoles is unverified + - Dual IDs for `libSceNgs2` (0x0000000b user / 0x80000090 internal) and `libSceHdrScopes` (0x00000109 user / 0x800000bd internal) reflect distinct public and internal sysmodule identifiers defined in PS5 firmware SDKs ### Functional From f46e9489a3469696bd8115f866f50752d4894a92 Mon Sep 17 00:00:00 2001 From: fogydes Date: Thu, 15 Oct 2026 16:08:39 +0530 Subject: [PATCH 106/176] feat(libSceContentDelete): export missing firmware content delete interfaces (cherry picked from commit 52ac9dc161c1c20c2699140bc9000803d87c5980) --- core/libs/prx/libSceContentDelete/Export.cpp | 20 ++++++++++++++++++++ docs/dev/debt/libSceContentDelete.md | 5 +++++ 2 files changed, 25 insertions(+) diff --git a/core/libs/prx/libSceContentDelete/Export.cpp b/core/libs/prx/libSceContentDelete/Export.cpp index 4c80330e3065..5216e53a4813 100644 --- a/core/libs/prx/libSceContentDelete/Export.cpp +++ b/core/libs/prx/libSceContentDelete/Export.cpp @@ -29,6 +29,26 @@ int APS5_VABI sceContentDeleteById(void) { return 0; } +int APS5_VABI sceContentDeleteByIdListForSf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceContentDeleteGlobalMstat(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceContentDeleteMemoryInitialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceContentDeleteMemoryTerminate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceContentDeleteTerminate(void) { bool expected = true; if (!g_initialized.compare_exchange_strong(expected, false)) throw std::logic_error(std::string(__func__) + ": not initialized"); diff --git a/docs/dev/debt/libSceContentDelete.md b/docs/dev/debt/libSceContentDelete.md index e771fdeec761..018189415586 100644 --- a/docs/dev/debt/libSceContentDelete.md +++ b/docs/dev/debt/libSceContentDelete.md @@ -3,3 +3,8 @@ ### Unknown function info - [sceContentDeleteById](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) - imported by PPSA06465, which fails to load without it; added without an implementation because its signature is unknown +- [sceContentDeleteById and sceContentDeleteByPath](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) delete from a content library that is empty on the host, as nothing is exported to it, so both throw for every content id and path once the arguments and the initialization are checked: no reference gives the console's error for content that is not in the library ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentDelete.cs#L25-L42) names only `0x809D5006`, no permission). The signatures, a 64-bit content id and a UTF-8 path, follow [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L541-L542) and SharpProspero, which agree. A null path and a call outside `sceContentDeleteInitialize` and `sceContentDeleteTerminate` throw, as the console's errors for them are unknown, and the order of the two checks is unverified. + +### Unknown function info + +- [sceContentDeleteByIdListForSf, sceContentDeleteGlobalMstat, sceContentDeleteMemoryInitialize, sceContentDeleteMemoryTerminate](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) - exported symbols from decrypted PS5 firmware 1.00–3.20 libSceContentDelete.sprx; parameter lists and return values are unknown/unverified and no reference (KytyPS5, shadPS4, fpPS4, SharpProspero) documents their signatures. Declared as stubs that throw via `NotImplemented_nid_no_patch` From 8a91c7d81123211ddd08248622224c6d825ac5f7 Mon Sep 17 00:00:00 2001 From: hypermist Date: Mon, 19 Oct 2026 18:33:35 +1300 Subject: [PATCH 107/176] fix(libc): walk program headers outside the unwind cache lock dl_iterate_phdr takes the loader lock. Calling it with the base cache mutex held inverts the order against a thread inside dlopen whose constructor throws and reaches FindFrameFast. Probe first, then lock only for the lookup and the insert; racing misses compute the same bases and emplace keeps the first. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 298fbdc9175be23f0e7158855aff5a6f1e152c0a) --- core/libs/prx/libc/src/exception/Unwind.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libc/src/exception/Unwind.cpp b/core/libs/prx/libc/src/exception/Unwind.cpp index 509e41bdf19c..3c08a5069b2d 100644 --- a/core/libs/prx/libc/src/exception/Unwind.cpp +++ b/core/libs/prx/libc/src/exception/Unwind.cpp @@ -105,15 +105,22 @@ bool FindFrameFast(Lookup& query) { if (header[0] != 1) return false; static std::mutex mutex; static std::map, std::pair> bases; + const std::tuple key {header, static_cast(object.dlfo_map_start), static_cast(object.dlfo_map_end)}; { std::lock_guard lock(mutex); - const std::tuple key {header, static_cast(object.dlfo_map_start), static_cast(object.dlfo_map_end)}; auto found = bases.find(key); - if (found == bases.end()) { - Lookup probe {query.pc}; - dl_iterate_phdr(FindFrame, &probe); - found = bases.emplace(key, std::pair {probe.text, probe.data}).first; + if (found != bases.end()) { + query.text = found->second.first; + query.data = found->second.second; + SearchHeader(header, query); + return true; } + } + Lookup probe {query.pc}; + dl_iterate_phdr(FindFrame, &probe); + { + std::lock_guard lock(mutex); + const auto found = bases.emplace(key, std::pair {probe.text, probe.data}).first; query.text = found->second.first; query.data = found->second.second; } From a73a38ad29358f0f54bd24b0143defb546bf30bb Mon Sep 17 00:00:00 2001 From: fogydes Date: Thu, 15 Oct 2026 14:58:09 +0530 Subject: [PATCH 108/176] feat(libSceVideoRecordingP): export missing firmware 3.20 video recording interfaces (cherry picked from commit 144c1a58cabcf17f7e1acf452a4157b8b03d57ea) --- .../libs/prx/libSceVideoRecordingP/Export.cpp | 98 +++++++++++++++++++ docs/dev/debt/libSceVideoRecordingP.md | 14 +++ 2 files changed, 112 insertions(+) create mode 100644 docs/dev/debt/libSceVideoRecordingP.md diff --git a/core/libs/prx/libSceVideoRecordingP/Export.cpp b/core/libs/prx/libSceVideoRecordingP/Export.cpp index 7d1cb61a2998..915a17728e79 100644 --- a/core/libs/prx/libSceVideoRecordingP/Export.cpp +++ b/core/libs/prx/libSceVideoRecordingP/Export.cpp @@ -16,12 +16,33 @@ std::map> g_info; extern "C" { +void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(void* param) { + (void)param; + NotImplemented_nid_no_patch("_sceVideoRecordingQueryParam"); +} + int APS5_VABI sceVideoRecordingClose(int discard) { (void)discard; NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceVideoRecordingCloseByHandle(int handle, int discard) { + (void)handle; + (void)discard; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingCopyBGRAtoNV12(void* dst, const void* src, uint32_t width, uint32_t height) { + (void)dst; + (void)src; + (void)width; + (void)height; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingGetInfo(int32_t info, void* data, size_t size) { if (data == nullptr) APS5_INVALID_ARG_EX; std::lock_guard lock(g_infoMutex); @@ -32,11 +53,43 @@ int APS5_VABI sceVideoRecordingGetInfo(int32_t info, void* data, size_t size) { return 0; } +int APS5_VABI sceVideoRecordingGetInfoByHandle(int handle, int32_t info, void* data, size_t size) { + (void)handle; + (void)info; + (void)data; + (void)size; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingGetStatus(void) { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceVideoRecordingGetSupportInfo(void* info) { + (void)info; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingGetVideoOutMode(void* mode) { + (void)mode; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingInitialize(void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* heap, int heapSize) { (void)path; (void)param; @@ -46,12 +99,52 @@ int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* h return 0; } +int APS5_VABI sceVideoRecordingOpen2(const char* path, const void* param, void* heap, int heapSize) { + (void)path; + (void)param; + (void)heap; + (void)heapSize; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingQueryHddSize2(const void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingQueryMemSize(const void* param) { (void)param; NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceVideoRecordingQueryMemSize2(const void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingRestModeProcessGetStatus(void* status) { + (void)status; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingRestModeProcessStart(void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingSendCommand(int cmd, const void* param) { + (void)cmd; + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingSetInfo(int32_t info, const void* data, size_t size) { if (data == nullptr && size != 0) APS5_INVALID_ARG_EX; const auto* bytes = static_cast(data); @@ -70,6 +163,11 @@ int APS5_VABI sceVideoRecordingStop(void) { return 0; } +int APS5_VABI sceVideoRecordingTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + APS5_EXPORT("iQS6DUtLybE", videoRecordingUnknown_iQS6DUtLybE); int APS5_VABI videoRecordingUnknown_iQS6DUtLybE(void) { NotImplemented_nid_no_patch("iQS6DUtLybE"); diff --git a/docs/dev/debt/libSceVideoRecordingP.md b/docs/dev/debt/libSceVideoRecordingP.md new file mode 100644 index 000000000000..79bb5888e16c --- /dev/null +++ b/docs/dev/debt/libSceVideoRecordingP.md @@ -0,0 +1,14 @@ +# libSceVideoRecordingP technical debt + +### Unknown function info + +- [_sceVideoRecordingQueryParam_nid_postfix](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - declared with `void(void* param)` signature following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/VideoRecording.cs#L22); unimplemented and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingCloseByHandle, sceVideoRecordingGetInfoByHandle](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - inferred handle-based recording control signatures taking an integer recording handle; unimplemented and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingCopyBGRAtoNV12](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - inferred pixel buffer conversion signature `(void* dst, const void* src, uint32_t width, uint32_t height)`; unimplemented and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingGetSupportInfo, sceVideoRecordingGetVideoOutMode](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - query signatures taking an output pointer; exact structure layouts and return values are unverified and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingInit, sceVideoRecordingTerm](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - parameterless initialization and termination stubs; unimplemented and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingInitialize](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - initialization stub taking an optional parameter structure pointer; unverified and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingOpen2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - extended recording open signature; parameters inferred from `sceVideoRecordingOpen` and [fpPS4](https://github.com/red-prig/fpPS4); unimplemented and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingQueryHddSize2, sceVideoRecordingQueryMemSize2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - memory and disk size query signatures taking parameter pointer; unverified and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingRestModeProcessGetStatus, sceVideoRecordingRestModeProcessStart](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - rest mode recording interfaces; unverified and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingSendCommand](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - command dispatch signature taking integer command ID and parameter pointer; unverified and throws `NotImplemented_nid_no_patch` From 086a63e1542a37a71e2856eafe1785c975eccffb Mon Sep 17 00:00:00 2001 From: hypermist Date: Mon, 19 Oct 2026 18:45:08 +1300 Subject: [PATCH 109/176] fix(libc): keep exact math results independent of the host libm drem, dremf, remainderl and remquol now set EDOM themselves for an infinite x or a zero y, as fmodl does. Apple's libm never sets errno and MinGW skips it for long double, so the host could not be relied on. Guest long is 64 bits, but the host long is 32 bits on Windows. The lrint, lround and scalbln exports now take and return std::int64_t, round through llrint and llround, and clamp scalbln exponents to the int range, where the result is already infinite or zero. The test uses INT64_MAX instead of LONG_MAX and covers remainderl, dremf and a 2^40 lrint result. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 1181068a68ef8ee50e833eb68621bcd4394acc85) --- core/libs/prx/libc/src/MathExact.cpp | 58 +++++++++++++++++++--------- core/libs/tests/GuestMathExact.cpp | 22 +++++++++-- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index 8f790c1a33db..1217ca4a4c74 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -60,6 +61,22 @@ short TestFloat(TFloat value) { } } +template +T Remainder(T x, T y) { + const bool domain = !std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == T(0)); + T result; + { + ErrnoGuard guard; + result = std::remainder(x, y); + } + if (domain) errno = EDOM; + return result; +} + +int Exponent(std::int64_t exponent) { + return static_cast(std::clamp(exponent, INT_MIN, INT_MAX)); +} + } extern "C" { @@ -81,13 +98,18 @@ long double APS5_VABI fmodl_nid_postfix(long double x, long double y) { } long double APS5_VABI remainderl_nid_postfix(long double x, long double y) { - return std::remainder(x, y); + return Remainder(x, y); } long double APS5_VABI remquol_nid_postfix(long double x, long double y, int* quotient) { - if (!std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == 0.0L)) errno = EDOM; + const bool domain = !std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == 0.0L); int bits = 0; - const long double result = std::remquo(x, y, &bits); + long double result; + { + ErrnoGuard guard; + result = std::remquo(x, y, &bits); + } + if (domain) errno = EDOM; if (!std::isnan(result)) *quotient = bits < 0 ? -(-bits & 7) : (bits & 7); return result; } @@ -106,14 +128,14 @@ int APS5_VABI ilogbl_nid_postfix(long double x) { long double APS5_VABI rintl_nid_postfix(long double x) { return std::rint(x); } long double APS5_VABI roundl_nid_postfix(long double x) { return std::isfinite(x) ? std::round(x) : x; } -long APS5_VABI lroundl_nid_postfix(long double x) { +std::int64_t APS5_VABI lroundl_nid_postfix(long double x) { ErrnoGuard guard; - return std::lround(x); + return std::llround(x); } -long APS5_VABI lrintl_nid_postfix(long double x) { +std::int64_t APS5_VABI lrintl_nid_postfix(long double x) { ErrnoGuard guard; - return std::lrint(x); + return std::llrint(x); } long long APS5_VABI llrintl_nid_postfix(long double x) { @@ -134,7 +156,7 @@ long double APS5_VABI modfl_nid_postfix(long double x, long double* integral) { } long double APS5_VABI nanl_nid_postfix(const char*) { return std::numeric_limits::quiet_NaN(); } -long double APS5_VABI scalblnl_nid_postfix(long double x, long exponent) { +long double APS5_VABI scalblnl_nid_postfix(long double x, std::int64_t exponent) { ErrnoGuard guard; return std::scalbn(x, static_cast(exponent)); } @@ -156,14 +178,14 @@ long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double double APS5_VABI rint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::rint(x); } float APS5_VABI rintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::rint(x); } -long APS5_VABI lrint_nid_postfix(double x) { +std::int64_t APS5_VABI lrint_nid_postfix(double x) { ErrnoGuard guard; - return std::lrint(x); + return std::llrint(x); } -long APS5_VABI lrintf_nid_postfix(float x) { +std::int64_t APS5_VABI lrintf_nid_postfix(float x) { ErrnoGuard guard; - return std::lrint(x); + return std::llrint(x); } long long APS5_VABI llrint_nid_postfix(double x) { @@ -177,21 +199,21 @@ long long APS5_VABI llrintf_nid_postfix(float x) { } double APS5_VABI drem_nid_postfix(double x, double y) { - return std::remainder(x, y); + return Remainder(x, y); } float APS5_VABI dremf_nid_postfix(float x, float y) { - return std::remainder(x, y); + return Remainder(x, y); } -double APS5_VABI scalbln_nid_postfix(double x, long exponent) { +double APS5_VABI scalbln_nid_postfix(double x, std::int64_t exponent) { ErrnoGuard guard; - return std::scalbln(x, exponent); + return std::scalbn(x, Exponent(exponent)); } -float APS5_VABI scalblnf_nid_postfix(float x, long exponent) { +float APS5_VABI scalblnf_nid_postfix(float x, std::int64_t exponent) { ErrnoGuard guard; - return std::scalbln(x, exponent); + return std::scalbn(x, Exponent(exponent)); } double APS5_VABI scalb_nid_postfix(double x, double exponent) { diff --git a/core/libs/tests/GuestMathExact.cpp b/core/libs/tests/GuestMathExact.cpp index b15cca64958b..b3afbca1acf1 100644 --- a/core/libs/tests/GuestMathExact.cpp +++ b/core/libs/tests/GuestMathExact.cpp @@ -14,11 +14,14 @@ int APS5_VABI ilogbl_nid_postfix(long double); long double APS5_VABI roundl_nid_postfix(long double); long double APS5_VABI modfl_nid_postfix(long double, long double*); long double APS5_VABI nanl_nid_postfix(const char*); -long double APS5_VABI scalblnl_nid_postfix(long double, long); +long double APS5_VABI scalblnl_nid_postfix(long double, std::int64_t); long double APS5_VABI fmal_nid_postfix(long double, long double, long double); double APS5_VABI scalb_nid_postfix(double, double); +double APS5_VABI scalbln_nid_postfix(double, std::int64_t); +long double APS5_VABI remainderl_nid_postfix(long double, long double); double APS5_VABI drem_nid_postfix(double, double); -long APS5_VABI lrint_nid_postfix(double); +float APS5_VABI dremf_nid_postfix(float, float); +std::int64_t APS5_VABI lrint_nid_postfix(double); int APS5_VABI finite_nid_postfix(double); int APS5_VABI __fpclassifyl_nid_postfix(long double); int APS5_VABI __isnanl_nid_postfix(long double); @@ -69,12 +72,25 @@ static void CheckErrno() { Require(scalb_nid_postfix(1.0, 3.0) == 8.0 && scalb_nid_postfix(4.0, -INFINITY) == 0.0); errno = 0; Require(std::isnan(drem_nid_postfix(1.0, 0.0)) && errno == EDOM); + errno = 0; + Require(std::isnan(drem_nid_postfix(INFINITY, 1.0)) && errno == EDOM); + errno = 0; + Require(std::isnan(dremf_nid_postfix(1.0f, 0.0f)) && errno == EDOM); + errno = 0; + Require(std::isnan(remainderl_nid_postfix(1.0L, 0.0L)) && errno == EDOM); + errno = 0; + Require(std::isnan(remainderl_nid_postfix(INFINITY, 1.0L)) && errno == EDOM); + errno = 0; + Require(std::isnan(remainderl_nid_postfix(NAN, 0.0L)) && errno == 0); + Require(remainderl_nid_postfix(7.0L, 2.0L) == -1.0L && errno == 0); Require(drem_nid_postfix(7.0, 2.0) == -1.0 && lrint_nid_postfix(2.5) == 2); } static void CheckConsoleQuirks() { Require(ilogbl_nid_postfix(NAN) == INT_MAX && ilogbl_nid_postfix(0.0L) == INT_MIN && ilogbl_nid_postfix(8.0L) == 3); - Require(scalblnl_nid_postfix(1.0L, 0x100000001L) == 2.0L && scalblnl_nid_postfix(1.0L, LONG_MAX) == 0.5L); + Require(scalblnl_nid_postfix(1.0L, INT64_C(0x100000001)) == 2.0L && scalblnl_nid_postfix(1.0L, INT64_MAX) == 0.5L); + Require(scalbln_nid_postfix(1.0, INT64_C(0x100000001)) == INFINITY && scalbln_nid_postfix(1.0, INT64_MIN) == 0.0); + Require(lrint_nid_postfix(0x1p40) == INT64_C(0x10000000000)); const long double product = Extended(0x4000, 0xe000000000000000ull) * Extended(0x4001, 0xda76b022816f6a94ull); Require(SameBits(fmal_nid_postfix(Extended(0x4000, 0xe000000000000000ull), Extended(0x4001, 0xda76b022816f6a94ull), Extended(0x9b45, 0xd3b49eac84283c95ull)), product)); Require(finite_nid_postfix(1.0) == 1 && finite_nid_postfix(INFINITY) == 0); From 39ed31add3462912921670e036955ddc186045fa Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 04:37:48 +0000 Subject: [PATCH 110/176] feat(prx): declare NetCtlAp exports (cherry picked from commit afc389c1bc88e609718bddc417bcb3c035b27af0) --- core/libs/prx/libSceNetCtlAp/Export.cpp | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 core/libs/prx/libSceNetCtlAp/Export.cpp diff --git a/core/libs/prx/libSceNetCtlAp/Export.cpp b/core/libs/prx/libSceNetCtlAp/Export.cpp new file mode 100644 index 000000000000..8791105967b4 --- /dev/null +++ b/core/libs/prx/libSceNetCtlAp/Export.cpp @@ -0,0 +1,72 @@ +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNetCtlApInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI sceNetCtlApTerm() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNetCtlApCheckCallback() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApClearEvent(int eventType) { + (void)eventType; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApRegisterCallback(void (*func)(int, void*), void* arg, int* cid) { + (void)func; + (void)arg; + (void)cid; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApUnregisterCallback(int cid) { + (void)cid; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetResult(int eventType, int* errorCode) { + (void)eventType; + (void)errorCode; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetInfo(void* info) { + (void)info; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetState(int* state) { + (void)state; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApStop() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetConnectInfo(std::uint8_t* optStr, void* connectInfo) { + (void)optStr; + (void)connectInfo; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} From 75e5354482ae249aeb6ac7a44281f3a2780027c4 Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 04:38:00 +0000 Subject: [PATCH 111/176] feat(prx): declare notification exports (cherry picked from commit 8f21bcbc98d4b49e1f0128a4bbbb0edf5f5d0957) --- core/libs/prx/libSceNotification/Export.cpp | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 core/libs/prx/libSceNotification/Export.cpp diff --git a/core/libs/prx/libSceNotification/Export.cpp b/core/libs/prx/libSceNotification/Export.cpp new file mode 100644 index 000000000000..90362f2991aa --- /dev/null +++ b/core/libs/prx/libSceNotification/Export.cpp @@ -0,0 +1,34 @@ +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNotificationSend(int type, std::uint8_t flag, std::uint8_t* message) { + (void)type; + (void)flag; + (void)message; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNotificationSendById(int type, std::uint8_t flag, std::uint8_t* id, std::uint8_t* jsonData) { + (void)type; + (void)flag; + (void)id; + (void)jsonData; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNotificationShowPsButtonPersistentBanner(std::uint8_t* jsonParam) { + (void)jsonParam; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI sceNotificationHidePsButtonPersistentBanner() { + NotImplemented_nid_no_patch(__func__); +} + +} From a15ad78ae80c4e057323b14c55fef890fc29f1a9 Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 04:49:39 +0000 Subject: [PATCH 112/176] feat(prx): declare charset and VDECSW exports (cherry picked from commit 753b03811cb1612fed69b2a723f3ec0348018c63) --- core/libs/prx/libSceCesCs/Export.cpp | 44 +++++++++++++++++++++++++++ core/libs/prx/libSceVdecsw/Export.cpp | 15 +++++++++ 2 files changed, 59 insertions(+) diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index d3c7cb79f72f..fcca18a28dc1 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -481,4 +481,48 @@ int APS5_VABI sceCesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* u return 0; } +int APS5_VABI sceCesEucJpToUtf8(void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)eucBuffer; + (void)eucMax; + (void)eucLength; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCesEucKrToUtf8(void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)eucBuffer; + (void)eucMax; + (void)eucLength; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCesBig5ToUtf8(void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Length, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)big5Buffer; + (void)big5Max; + (void)big5Length; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCesUhcToUtf8(void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)uhcBuffer; + (void)uhcMax; + (void)uhcLength; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceVdecsw/Export.cpp b/core/libs/prx/libSceVdecsw/Export.cpp index 8d394b9fb34c..cab091bb320b 100644 --- a/core/libs/prx/libSceVdecsw/Export.cpp +++ b/core/libs/prx/libSceVdecsw/Export.cpp @@ -373,6 +373,14 @@ int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t handle, OutputInfo* output } } +int APS5_VABI sceVdecswGetPictureInfo(const OutputInfo* output, void* first, void* second) { + (void)output; + (void)first; + (void)second; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) { if (output == nullptr || first == nullptr || first->thisSize < sizeof(std::uint64_t)) throw std::runtime_error("Vdecsw: invalid argument pointer"); if (output->pictureCount > 1) throw std::runtime_error("Vdecsw: second field picture info is not implemented"); @@ -401,6 +409,13 @@ int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo* output, void* pictur return 0; } +int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo* output, void* picture) { + (void)output; + (void)picture; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVdecswFinalizeDecodeSequence(std::uint64_t handle) { const auto decoder = findDecoder(handle); std::lock_guard lock(decoder->mutex); From 8000d86aaeeb0cb680dac5610e408df1f385d63b Mon Sep 17 00:00:00 2001 From: fogydes Date: Thu, 15 Oct 2026 16:14:09 +0530 Subject: [PATCH 113/176] feat(libSceVoiceChat): export missing firmware voice chat interfaces (cherry picked from commit 0f38f0fe58ebc22cae1b6a9a18a9ea2302e66564) --- core/libs/CMakeLists.txt | 10 + core/libs/prx/libSceVoiceChat/Export.cpp | 259 +++++++---------------- core/libs/tests/GuestVoiceChat.cpp | 56 +++++ docs/dev/debt/libSceVoiceChat.md | 6 +- 4 files changed, 146 insertions(+), 185 deletions(-) create mode 100644 core/libs/tests/GuestVoiceChat.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 4139cf7fbedb..53253c6c3ba7 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -508,4 +508,14 @@ if(BUILD_TESTING) set_tests_properties(guest_acm PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_voice_chat_tests tests/GuestVoiceChat.cpp) + target_include_directories(guest_voice_chat_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_voice_chat_tests PRIVATE libSceVoiceChat libc) + configure_windows_unwind(guest_voice_chat_tests) + add_test(NAME guest_voice_chat COMMAND guest_voice_chat_tests) + if(WIN32) + set_tests_properties(guest_voice_chat PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceVoiceChat/Export.cpp b/core/libs/prx/libSceVoiceChat/Export.cpp index aa3728e42e56..49e582df5fc6 100644 --- a/core/libs/prx/libSceVoiceChat/Export.cpp +++ b/core/libs/prx/libSceVoiceChat/Export.cpp @@ -66,209 +66,100 @@ VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestJoinVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestLeaveVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestDeleteVoiceChatGroup) -int APS5_VABI sceVoiceChatAbortRequest(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetActiveChannel(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChannelMemberActiveChannelMatchingState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChannelMemberVoiceConnectionState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChatChannelMemberInfoList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChatGroupMemberInfoList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetMicState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterDaemonEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterMemberEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterPartyUserStateChangedHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterVoiceEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequest0(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequest1(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestAcsTts(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestAction(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestSetMemberVolume(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartAccessibilityEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartChannelEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartDaemonEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartRangedMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartVoiceEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequest0) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequest1) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestAcsTts) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestAction) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestSetMemberVolume) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName) -int APS5_VABI sceVoiceChatStopAccessibilityEvent(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceChatAbortRequest(int requestId) { + (void)requestId; return 0; } -int APS5_VABI sceVoiceChatStopChannelEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopDaemonEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopRangedMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopVoiceEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetActiveChannel(void* channel) { + (void)channel; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterAccessibilityEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChannelMemberActiveChannelMatchingState(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterChannelEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChannelMemberVoiceConnectionState(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterDaemonEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChatChannelMemberInfoList(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterMemberEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChatGroupMemberInfoList(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetMicState(void* state) { + (void)state; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterPartyUserStateChangedHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void* a, void* gain) { + (void)a; + (void)gain; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterRangedMemberEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void* a, int32_t gain) { + (void)a; + (void)gain; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterVoiceEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} +int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterDaemonEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterMemberEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterPartyUserStateChangedHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterVoiceEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } + +int APS5_VABI sceVoiceChatStartAccessibilityEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartChannelEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartDaemonEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartRangedMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartVoiceEvent(void* a) { (void)a; return 0; } + +int APS5_VABI sceVoiceChatStopAccessibilityEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopChannelEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopDaemonEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopRangedMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopVoiceEvent(void* a) { (void)a; return 0; } + +int APS5_VABI sceVoiceChatUnregisterAccessibilityEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterChannelEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterDaemonEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterMemberEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterPartyUserStateChangedHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterRangedMemberEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterVoiceEventHandler(void* a) { (void)a; return 0; } } diff --git a/core/libs/tests/GuestVoiceChat.cpp b/core/libs/tests/GuestVoiceChat.cpp new file mode 100644 index 000000000000..0a94bcefa853 --- /dev/null +++ b/core/libs/tests/GuestVoiceChat.cpp @@ -0,0 +1,56 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceVoiceChatInitialize(void* param); +int APS5_VABI sceVoiceChatTerminate(void); +int APS5_VABI sceVoiceChatRegisterHandlers(void* a); +int APS5_VABI sceVoiceChatRegisterMicEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void* a); +int APS5_VABI sceVoiceChatProcessEvent(void); +int APS5_VABI sceVoiceChatStartChannelEvent(void* a); +int APS5_VABI sceVoiceChatStopChannelEvent(void* a); +int APS5_VABI sceVoiceChatGetMicState(int32_t* state); +int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void* a, int32_t* gain); +int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void* a, int32_t gain); +int APS5_VABI sceVoiceChatGetActiveChannel(void* channel); +int APS5_VABI sceVoiceChatCreateRequest(void* a, void* b); +int APS5_VABI sceVoiceChatRequestAcsTts(int requestId, void* a, void* b); +int APS5_VABI sceVoiceChatAbortRequest(int requestId); +} + +static void Require(bool value, const char* what) { + if (value) return; + std::printf("VoiceChat: %s\n", what); + std::abort(); +} + +int main() { + constexpr int kSignedOut = static_cast(0x80550006u); + + Require(sceVoiceChatInitialize(nullptr) == 0, "initialization failed"); + Require(sceVoiceChatProcessEvent() == 0, "event processing failed"); + Require(sceVoiceChatRegisterHandlers(nullptr) == 0, "register handlers failed"); + Require(sceVoiceChatRegisterMicEventHandler(nullptr, nullptr) == 0, "register mic event handler failed"); + Require(sceVoiceChatRegisterChannelEventHandler(nullptr, nullptr) == 0, "register channel event handler failed"); + Require(sceVoiceChatUnregisterMicEventHandler(nullptr) == 0, "unregister mic event handler failed"); + Require(sceVoiceChatStartChannelEvent(nullptr) == 0, "start channel event failed"); + Require(sceVoiceChatStopChannelEvent(nullptr) == 0, "stop channel event failed"); + + Require(sceVoiceChatGetMicState(nullptr) == kSignedOut, "get mic state did not return signed out"); + Require(sceVoiceChatGetUserVoiceVolumeGain(nullptr, nullptr) == kSignedOut, "get volume gain did not return signed out"); + Require(sceVoiceChatSetUserVoiceVolumeGain(nullptr, 50) == kSignedOut, "set volume gain did not return signed out"); + + Require(sceVoiceChatGetActiveChannel(nullptr) == kSignedOut, "get active channel did not return signed out"); + Require(sceVoiceChatCreateRequest(nullptr, nullptr) == kSignedOut, "create request did not return signed out"); + Require(sceVoiceChatRequestAcsTts(1, nullptr, nullptr) == kSignedOut, "tts request did not return signed out"); + Require(sceVoiceChatAbortRequest(1) == 0, "abort request failed"); + Require(sceVoiceChatTerminate() == 0, "termination failed"); + + std::puts("VoiceChat tests passed"); + return 0; +} diff --git a/docs/dev/debt/libSceVoiceChat.md b/docs/dev/debt/libSceVoiceChat.md index f112d0dff5c6..60ad4dc3bb5f 100644 --- a/docs/dev/debt/libSceVoiceChat.md +++ b/docs/dev/debt/libSceVoiceChat.md @@ -1,6 +1,10 @@ # libSceVoiceChat technical debt +### Silent stubs + +- [sceVoiceChatAbortRequest, sceVoiceChatRegisterChannelEventHandler, sceVoiceChatRegisterDaemonEventHandler, sceVoiceChatRegisterMemberEventHandler, sceVoiceChatRegisterPartyUserStateChangedHandler, sceVoiceChatRegisterVoiceEventHandler, sceVoiceChatStartAccessibilityEvent, sceVoiceChatStartChannelEvent, sceVoiceChatStartDaemonEvent, sceVoiceChatStartMemberEvent, sceVoiceChatStartRangedMemberEvent, sceVoiceChatStartVoiceEvent, sceVoiceChatStopAccessibilityEvent, sceVoiceChatStopChannelEvent, sceVoiceChatStopDaemonEvent, sceVoiceChatStopMemberEvent, sceVoiceChatStopRangedMemberEvent, sceVoiceChatStopVoiceEvent, sceVoiceChatUnregisterAccessibilityEventHandler, sceVoiceChatUnregisterChannelEventHandler, sceVoiceChatUnregisterDaemonEventHandler, sceVoiceChatUnregisterMemberEventHandler, sceVoiceChatUnregisterMicEventHandler, sceVoiceChatUnregisterPartyUserStateChangedHandler, sceVoiceChatUnregisterRangedMemberEventHandler, sceVoiceChatUnregisterVoiceEventHandler](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - offline handler registration and event controls return 0 without connecting to a voice chat daemon, because the host environment does not run a PSN voice chat service; titles that register event listeners or start/stop audio capture events proceed safely without throwing + ### Unknown function info -- [libSceVoiceChat](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - voice chat channel management, audio event, and gain control interfaces; stubs throw until voice chat daemon subsystem is implemented +- [sceVoiceChatAbortRequest, sceVoiceChatGetActiveChannel, sceVoiceChatGetChannelMemberActiveChannelMatchingState, sceVoiceChatGetChannelMemberVoiceConnectionState, sceVoiceChatGetChatChannelMemberInfoList, sceVoiceChatGetChatGroupMemberInfoList, sceVoiceChatGetMicState, sceVoiceChatGetUserVoiceVolumeGain, sceVoiceChatRegisterChannelEventHandler, sceVoiceChatRegisterDaemonEventHandler, sceVoiceChatRegisterMemberEventHandler, sceVoiceChatRegisterPartyUserStateChangedHandler, sceVoiceChatRegisterVoiceEventHandler, sceVoiceChatRequest0, sceVoiceChatRequest1, sceVoiceChatRequestAcsTts, sceVoiceChatRequestAction, sceVoiceChatRequestSetMemberVolume, sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName, sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName, sceVoiceChatSetUserVoiceVolumeGain, sceVoiceChatStartAccessibilityEvent, sceVoiceChatStartChannelEvent, sceVoiceChatStartDaemonEvent, sceVoiceChatStartMemberEvent, sceVoiceChatStartRangedMemberEvent, sceVoiceChatStartVoiceEvent, sceVoiceChatStopAccessibilityEvent, sceVoiceChatStopChannelEvent, sceVoiceChatStopDaemonEvent, sceVoiceChatStopMemberEvent, sceVoiceChatStopRangedMemberEvent, sceVoiceChatStopVoiceEvent, sceVoiceChatUnregisterAccessibilityEventHandler, sceVoiceChatUnregisterChannelEventHandler, sceVoiceChatUnregisterDaemonEventHandler, sceVoiceChatUnregisterMemberEventHandler, sceVoiceChatUnregisterMicEventHandler, sceVoiceChatUnregisterPartyUserStateChangedHandler, sceVoiceChatUnregisterRangedMemberEventHandler, sceVoiceChatUnregisterVoiceEventHandler](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - parameter layouts are inferred; offline, handlers and event controls return 0, query calls, volume gain controls, and requests return `SIGNED_OUT` (0x80550006) without modifying guest buffers - [sceVoiceChatRequestCreateVoiceChatGroup, sceVoiceChatRequestJoinVoiceChatGroup, sceVoiceChatRequestLeaveVoiceChatGroup, sceVoiceChatRequestDeleteVoiceChatGroup](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - assumed to take a request id and two pointers like the other `sceVoiceChatRequest*` calls; offline, every argument is ignored and `SIGNED_OUT` is returned, as for those From 4bf4556ad5900b1f0a75d828c54184f62877f8f6 Mon Sep 17 00:00:00 2001 From: Luis Gerardo Aguilar <100291529+LuisAguilarDev@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:33:05 -0500 Subject: [PATCH 114/176] feat(np-tus): add libSceNpTus with offline title user storage Titles that import libSceNpTus failed to load because the library did not exist. Title contexts and requests are tracked, arguments are validated, and every storage operation completes as signed out, matching how libSceNpManager reports PSN. (cherry picked from commit 8cf212a9d82403ba5d8ac30eeb0ba075947f5d18) --- core/libs/prx/libSceNpTus/CMakeLists.txt | 16 +++ core/libs/prx/libSceNpTus/Export.cpp | 129 +++++++++++++++++++++++ core/libs/tests/GuestNpTus.cpp | 73 +++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 core/libs/prx/libSceNpTus/CMakeLists.txt create mode 100644 core/libs/prx/libSceNpTus/Export.cpp create mode 100644 core/libs/tests/GuestNpTus.cpp diff --git a/core/libs/prx/libSceNpTus/CMakeLists.txt b/core/libs/prx/libSceNpTus/CMakeLists.txt new file mode 100644 index 000000000000..1cb8f4a6760c --- /dev/null +++ b/core/libs/prx/libSceNpTus/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpTus SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpTus PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpTus) + +if(BUILD_TESTING) + add_test_executable(guest_np_tus_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTus.cpp) + target_include_directories(guest_np_tus_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_tus_tests PRIVATE libSceNpTus) + configure_windows_unwind(guest_np_tus_tests) + add_test(NAME guest_np_tus COMMAND guest_np_tus_tests) + set_tests_properties(guest_np_tus PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_tus PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTus/Export.cpp b/core/libs/prx/libSceNpTus/Export.cpp new file mode 100644 index 000000000000..11b8a99218ba --- /dev/null +++ b/core/libs/prx/libSceNpTus/Export.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = static_cast(0x80550704); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = static_cast(0x80550706); +static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast(0x80550707); +static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = static_cast(0x80550718); +static constexpr int POLL_FINISHED = 0; +static constexpr int POLL_RUNNING = 1; +static constexpr std::size_t MAX_TITLE_CONTEXTS = 32; +static constexpr std::size_t MAX_REQUESTS = 256; +static constexpr int MAX_SLOTS = 64; + +namespace { +struct Request { + bool started = false; + bool aborted = false; + int result = 0; +}; + +std::mutex tusMutex; +std::unordered_set titleContexts; +std::unordered_map requests; +int nextTitleContext = 1; +int nextRequest = 1; + +int ValidateSlots(const std::int32_t* slotIds, int arrayLen, const void* option) { + if (!slotIds) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (arrayLen < 1 || option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + for (int index = 0; index < arrayLen; ++index) { + if (slotIds[index] < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return 0; +} + +int StartOffline(int reqId) { + std::lock_guard lock(tusMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + request->second.started = true; + request->second.result = request->second.aborted ? SCE_NP_COMMUNITY_ERROR_ABORTED : SCE_NP_ERROR_SIGNED_OUT; + return 0; +} +} + +extern "C" { + +int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId) { + (void)serviceLabel; + (void)userId; + std::lock_guard lock(tusMutex); + if (titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int contextId = nextTitleContext++; + titleContexts.insert(contextId); + return contextId; +} + +int APS5_VABI sceNpTusCreateRequest(int titleCtxId) { + std::lock_guard lock(tusMutex); + if (!titleContexts.contains(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (requests.size() >= MAX_REQUESTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int reqId = nextRequest++; + requests.emplace(reqId, Request{}); + return reqId; +} + +int APS5_VABI sceNpTusDeleteRequest(int reqId) { + std::lock_guard lock(tusMutex); + return requests.erase(reqId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; +} + +int APS5_VABI sceNpTusAbortRequest(int reqId) { + std::lock_guard lock(tusMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + request->second.aborted = true; + return 0; +} + +int APS5_VABI sceNpTusPollAsync(int reqId, int* result) { + std::lock_guard lock(tusMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (!request->second.started) return POLL_RUNNING; + if (result) *result = request->second.result; + return POLL_FINISHED; +} + +int APS5_VABI sceNpTusGetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)targetAccountId; + (void)variableArraySize; + if (!variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (const int error = ValidateSlots(slotIds, arrayLen, option)) return error; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusSetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option) { + (void)targetAccountId; + if (!variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (const int error = ValidateSlots(slotIds, arrayLen, option)) return error; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusDeleteMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option) { + (void)targetAccountId; + if (const int error = ValidateSlots(slotIds, arrayLen, option)) return error; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)targetAccountId; + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (!outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0 || option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +} diff --git a/core/libs/tests/GuestNpTus.cpp b/core/libs/tests/GuestNpTus.cpp new file mode 100644 index 000000000000..f2376a4113bf --- /dev/null +++ b/core/libs/tests/GuestNpTus.cpp @@ -0,0 +1,73 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); +int APS5_VABI sceNpTusCreateRequest(int titleCtxId); +int APS5_VABI sceNpTusDeleteRequest(int reqId); +int APS5_VABI sceNpTusAbortRequest(int reqId); +int APS5_VABI sceNpTusPollAsync(int reqId, int* result); +int APS5_VABI sceNpTusGetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +} + +static constexpr int SIGNED_OUT = static_cast(0x80550006); +static constexpr int INVALID_ARGUMENT = static_cast(0x80550704); +static constexpr int ABORTED = static_cast(0x80550707); +static constexpr int INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); +static constexpr int INVALID_ID = static_cast(0x8055070E); +static constexpr int TOO_MANY_SLOTID = static_cast(0x80550718); + +static void Require(bool value) { if (!value) std::abort(); } + +static void RequireFinished(int reqId, int expected) { + int result = 0; + Require(sceNpTusPollAsync(reqId, &result) == 0 && result == expected); +} + +int main() { + const int context = sceNpTusCreateNpTitleCtxA(0, 1); + Require(context > 0); + Require(sceNpTusCreateRequest(context + 1000) == INVALID_ID); + + const int request = sceNpTusCreateRequest(context); + Require(request > 0); + int result = 0; + Require(sceNpTusPollAsync(request, &result) == 1); + + std::int32_t slots[2] = {0, 3}; + std::int64_t values[2] = {5, 7}; + std::uint8_t variables[256] = {}; + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, nullptr, variables, sizeof(variables), 2, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, nullptr, sizeof(variables), 2, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 0, nullptr) == INVALID_ARGUMENT); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 65, nullptr) == TOO_MANY_SLOTID); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 2, variables) == INVALID_ARGUMENT); + std::int32_t negative[1] = {-1}; + Require(sceNpTusDeleteMultiSlotVariableAAsync(request, 0, negative, 1, nullptr) == INVALID_ARGUMENT); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, nullptr, 2, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusAddAndGetVariableAAsync(request, 0, 0, 1, nullptr, nullptr, nullptr, 0, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusAddAndGetVariableAAsync(request, 0, -1, 1, nullptr, nullptr, variables, sizeof(variables), nullptr) == INVALID_ARGUMENT); + + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 2, nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, values, 2, nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + Require(sceNpTusDeleteMultiSlotVariableAAsync(request, 0, slots, 2, nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + Require(sceNpTusAddAndGetVariableAAsync(request, 0, 0, 1, nullptr, nullptr, variables, sizeof(variables), nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + + Require(sceNpTusAbortRequest(request) == 0); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, values, 2, nullptr) == 0); + RequireFinished(request, ABORTED); + + Require(sceNpTusDeleteRequest(request) == 0); + Require(sceNpTusDeleteRequest(request) == INVALID_ID); + Require(sceNpTusAbortRequest(request) == INVALID_ID); + Require(sceNpTusPollAsync(request, &result) == INVALID_ID); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, values, 2, nullptr) == INVALID_ID); +} From 5bd175da36b9b3bdc0947a269cdbcd04c1d00a72 Mon Sep 17 00:00:00 2001 From: notaxeltv Date: Thu, 15 Oct 2026 23:53:05 +0200 Subject: [PATCH 115/176] fix(libSceNgs2): keep CalcListener basis when front and up are parallel PPSA15552 passes matching front/up after a listener move; pick a perpendicular up hint instead of throwing so Dead Cells can keep running. Co-authored-by: Cursor (cherry picked from commit 07d9f409a4f32cdeaa1f7ca6d432fa9b4ea5c1ff) --- docs/dev/debt/libSceNgs2.native.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index 77b9ef628263..59361e933cd5 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -29,6 +29,7 @@ - [sceNgs2PanInit / sceNgs2PanGetVolumeMatrix](../../../core/libs/prx/libSceNgs2.native/src/Pan.cpp) (libSceNgs2.native) - follow the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output for 2, 4, 5 and 7 speakers into 1, 2, 6 and 8 channels; not yet confirmed on the PS5 library. Speakers are taken in the order L R, L R Ls Rs, L R C Ls Rs or L R C Ls Rs Lb Rb and kept in a fixed circular order, not sorted; without angles the defaults are 90/270, 40/110/250/320, 0/40/110/250/320 and 0/40/110/150/210/250/320 degrees. As in that library, defaults converted to radians keep the wrap-around entry in degrees, so angles past the last speaker pan almost fully to it. Error codes are not known for the PS5, so invalid arguments throw, including a speaker angle equal to the unit angle, on which that library loops forever. Angles so large that adding or subtracting a turn leaves them unchanged throw for the same reason - [sceNgs2GeomResetListenerParam, sceNgs2GeomResetSourceParam](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) (libSceNgs2.native) - the structures (12, 16, 16, 60, 96, 108, 32 and 308 bytes) come from [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `ngs2.cpp` and match [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_geom.h` field for field, but only KytyPS5 gives the behaviour - shadPS4 logs and returns success without writing the output. The defaults (orient front +Z, orient up +Y, sound speed 343, cone 1/360/1/360, rolloff 0/1000000/1/1, doppler factor, full-band, LFE and maximum level 1, two speakers and matrix format 2) are unverified on a PS5 title; KytyPS5 and iStark/PS5PCEM agree on them. PPSA15552 (Dead Cells) calls `sceNgs2GeomResetSourceParam` after creating its racks. A null output address returns `INVALID_OUT_ADDRESS` as elsewhere in the library rather than throwing as KytyPS5 does. `sceNgs2GeomApply` is now a minimal pass-through that follows [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `Ngs2GeomApply` (shadPS4 only stubs it): pitch 1, source position, volume the larger of the source minimum and maximum level, and a diagonal of the source maximum level (1 when it is not positive) over `matrix_format` channels (0 is two, at most 8). The listener and `flags` are ignored, so source attenuation, distance rolloff, panning and Doppler stay unimplemented; neither KytyPS5 nor this has been measured on a PS5, and a null output address returns `INVALID_OUT_ADDRESS` where KytyPS5 exits - [sceNgs2GeomCalcListener](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) builds an orthonormal world-to-listener transform. The row-major matrix, front-axis priority and flag 1 for right-handed input follow [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/d2534bac6325a2c61c0fa88977e5321074304484/src/hle/ngs2_geom.zig); they have not been measured on PS5 hardware. Velocity stays in world coordinates and positive finite sound speed is preserved. Orientation vectors are normalised and up is made perpendicular to front, so non-unit and slightly tilted vectors are accepted. When front and up are parallel, up is replaced by `(0,1,0)` or `(1,0,0)` so the basis stays defined: PPSA15552 (Dead Cells) passes `orient_front={0,0,1}` with `orient_up={0,0,1}` after moving the listener, which PS5PCEM rejects; the console error code is unknown. Unknown flags, zero orientation vectors, nonfinite input and an unrepresentable translation still throw without changing the output; a nonpositive sound speed throws where PS5PCEM and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/ngs2.cpp) default it to 343. KytyPS5 always writes the identity matrix, so the transform rests on PS5PCEM plus that Dead Cells case. Dead Cells reaches this call after the custom sampler rack in #4334. This does not implement source attenuation, panning or Doppler in `sceNgs2GeomApply`. +- [sceNgs2GeomCalcListener](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) builds an orthonormal world-to-listener transform. The row-major matrix, front-axis priority and flag 1 for right-handed input follow [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/d2534bac6325a2c61c0fa88977e5321074304484/src/hle/ngs2_geom.zig); they have not been measured on PS5 hardware. Velocity stays in world coordinates and positive finite sound speed is preserved. Orientation vectors are normalised and up is made perpendicular to front, so non-unit and slightly tilted vectors are accepted. When front and up are parallel, up is replaced by `(0,1,0)` or `(1,0,0)` so the basis stays defined: PPSA15552 (Dead Cells) passes `orient_front={0,0,1}` with `orient_up={0,0,1}` after moving the listener, which PS5PCEM rejects; the console error code is unknown. Unknown flags, zero orientation vectors, nonfinite input and an unrepresentable translation still throw without changing the output; console error codes are unknown, and a nonpositive sound speed throws where PS5PCEM and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/ngs2.cpp) default it to 343. KytyPS5 always writes the identity matrix, so the transform rests on PS5PCEM plus that Dead Cells case. Dead Cells (PPSA15552) reaches this call after the custom sampler rack in #4334. This does not implement source attenuation, panning or Doppler in `sceNgs2GeomApply`. - [NGS2 custom sampler rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4001) - implements bounded support for the custom sampler rack and its built-in module 0x13 (Custom Voice Filter), unblocking PPSA15552 (Dead Cells) startup. Structure layouts come from [shadPS4's ngs2_custom.h](https://raw.githubusercontent.com/shadps4-emu/shadPS4/main/src/core/libraries/ngs2/ngs2_custom.h) (OrbisNgs2CustomVoiceFilterParam, 40 bytes) and live trace correlation from [prosper's hle_audio.cpp](https://raw.githubusercontent.com/mattias800/prosper/main/prosper/src/hle/audio/hle_audio.cpp) (0x40001300 command and parameters like 10000.0f and 0.707f). Routes parameter updates via the 0x40001300 command family into the Biquad filter engine. Filter type 0 clears the destination filter; type 1 configures the RBJ low-pass biquad frequency, Q and level. Other filter types and unmodeled configurations throw. - [sceNgs2VoiceGetState custom sampler](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native, rack 0x4001) - only the size and one field come from a title. PPSA15552 (Dead Cells) calls it on custom sampler voices with a 48-byte buffer at two sites (eboot `0x1740f90`, `0x1741009`) and reads only the 64-bit value at offset 16: the first site keeps it and the second subtracts the kept value, so it is filled with the voice's decoded sample count. Other sizes return `INVALID_OUT_SIZE` as for the other racks. The state flags fill the common voice state; the remaining fields stay zero. shadPS4's `OrbisNgs2CustomSamplerVoiceState` is about 80 bytes and does not match the size this title passes, so the field names and the console's value are unverified From 2ce07c0930040286ab3ffda01d0d49c9d62c4d01 Mon Sep 17 00:00:00 2001 From: ShinMalphur Date: Mon, 19 Oct 2026 13:11:00 +0630 Subject: [PATCH 116/176] fix(libc): reject oversized secure string destinations (cherry picked from commit 016f743c64f7b93578f82e1e7a788f5c2cd45bb6) --- core/libs/prx/libc/src/Strings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index a9408ae226b1..99b366f09aab 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -312,7 +312,9 @@ int APS5_VABI strcoll_nid_postfix(const char* s1, const char* s2) { int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count) { constexpr int GuestEinval = 22; constexpr int GuestErange = 34; + constexpr size_t RsizeMax = SIZE_MAX >> 1; if (!dest || destsz == 0) return GuestEinval; + if (destsz > RsizeMax) return GuestErange; if (!src) { dest[0] = '\0'; return GuestEinval; @@ -335,7 +337,9 @@ int APS5_VABI strcpy_s_nid_postfix(char* dest, size_t destsz, const char* src) { int APS5_VABI strncat_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count) { constexpr int GuestEinval = 22; constexpr int GuestErange = 34; + constexpr size_t RsizeMax = SIZE_MAX >> 1; if (!dest || destsz == 0) return GuestEinval; + if (destsz > RsizeMax) return GuestErange; size_t used = 0; while (used < destsz && dest[used] != '\0') ++used; if (used == destsz || !src) { From 21cb5a19b51f79b381cf22ccf158c810f8acc168 Mon Sep 17 00:00:00 2001 From: ShinMalphur Date: Mon, 19 Oct 2026 13:11:17 +0630 Subject: [PATCH 117/176] test(libc): cover oversized secure string destinations (cherry picked from commit cdd476fdfcc8d1cb7cf4dabcec91228e120a14c6) --- core/libs/tests/GuestStrings.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index ab8748450c8c..85a907e10346 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -27,6 +27,7 @@ char* APS5_VABI strtok_nid_postfix(char*, const char*); char* APS5_VABI strsep_nid_postfix(char**, const char*); char* APS5_VABI strcasestr_nid_postfix(const char*, const char*); int APS5_VABI strcpy_s_nid_postfix(char*, std::size_t, const char*); +int APS5_VABI strncpy_s_nid_postfix(char*, std::size_t, const char*, std::size_t); int APS5_VABI strcat_s_nid_postfix(char*, std::size_t, const char*); int APS5_VABI strncat_s_nid_postfix(char*, std::size_t, const char*, std::size_t); int APS5_VABI memcpy_s_nid_postfix(void*, std::size_t, const void*, std::size_t); @@ -137,6 +138,33 @@ static void CheckBoundsCheckedFunctions() { Require(snprintf_s_nid_postfix(formatted, sizeof(formatted), "%d-%s", 42, "x") == 4 && std::strcmp(formatted, "42-x") == 0); } +static void CheckSecureStringSizeLimits() { + constexpr std::size_t RsizeMax = SIZE_MAX >> 1; + const std::array original = {'!', 'a', 'b', '\0', 'x', 'y', 'z', '!'}; + for (const auto size : {RsizeMax + 1, SIZE_MAX}) { + for (const char* source : {"cd", "", static_cast(nullptr)}) { + for (const auto copy : {strcpy_s_nid_postfix, strcat_s_nid_postfix}) { + auto bytes = original; + Require(copy(bytes.data() + 1, size, source) != 0); + Require(bytes == original); + } + for (const auto copy : {strncpy_s_nid_postfix, strncat_s_nid_postfix}) { + for (const auto count : {std::size_t{0}, std::size_t{1}, std::size_t{3}}) { + auto bytes = original; + Require(copy(bytes.data() + 1, size, source, count) != 0); + Require(bytes == original); + } + } + } + } + auto bytes = original; + Require(strcpy_s_nid_postfix(bytes.data() + 1, RsizeMax, "cd") == 0); + Require(bytes[0] == '!' && std::strcmp(bytes.data() + 1, "cd") == 0 && bytes[4] == 'x'); + bytes = original; + Require(strcat_s_nid_postfix(bytes.data() + 1, RsizeMax, "cd") == 0); + Require(bytes[0] == '!' && std::strcmp(bytes.data() + 1, "abcd") == 0 && bytes[6] == 'z'); +} + static void CheckCaseInsensitiveComparison() { Require(strcasecmp_nid_postfix("", "") == 0 && strcasecmp_nid_postfix("AbC", "aBc") == 0); Require(strcasecmp_nid_postfix("A", "b") == 'a' - 'b' && strcasecmp_nid_postfix("b", "A") == 'b' - 'a'); @@ -380,5 +408,6 @@ int main() { const char highBytes[] = {static_cast(0xff), 'A', 0}; Require(strcasestr_nid_postfix(highBytes, "a") == highBytes + 1); CheckSecureCopySizeLimits(); + CheckSecureStringSizeLimits(); return CheckMemcpyOverlap() ? 0 : 1; } From 22fbbbe6c62c36583f8303cad2f4e07f9cd2c1ab Mon Sep 17 00:00:00 2001 From: Yuri Olive Date: Thu, 15 Oct 2026 19:28:02 -0300 Subject: [PATCH 118/176] feat(libSceNet): export missing firmware network interfaces (cherry picked from commit 4d6942f1e617a7892650ce779921a069b6c8e1b7) --- core/libs/prx/libSceNet/Export.cpp | 850 +++++++++++++++++++++++++++++ docs/dev/debt/libSceNet.md | 170 ++++++ 2 files changed, 1020 insertions(+) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index c9d9d916923a..5cd7c6b02ed5 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -1753,4 +1753,854 @@ int APS5_VABI sceNetShowIfconfig() { extern const std::uint8_t in6addr_any_nid_postfix[16] = {}; extern const std::uint8_t in6addr_loopback_nid_postfix[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +int APS5_VABI sceNetClearDnsCache(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetStatisticsInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetInterfaceStats(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDnsInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowIfconfigWithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowPolicy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowPolicyWithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRouteWithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverStartAton6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnectAbort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnectCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnectDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetSockInfo6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDns6Info(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute6WithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAllocateAllRouteInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAllocateAllRouteInfo6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetControl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDbgInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackGetError(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackWaitCB(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetFreeAllRouteInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetArpInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetAppMemInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetDnsInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfListEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfListOnce(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfnameNumList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetNameToIndex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetRandom(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetRouteInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetsockoptEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetSystemTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetSystemMemInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInitParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInetPtonEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetIoctl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetMemoryAllocate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetMemoryFree(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDnsInfoToKernel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetsockoptEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSocketInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncGet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncSignal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncWait(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSysctl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetThreadCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetThreadExit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetThreadJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetUsleep(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowIfconfigForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRouteForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowNetstatForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowNetstatEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowNetstatExForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInetNtopWithScopeId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInetPtonWithScopeId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetDns6Info(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDns6InfoToKernel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute6ForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetIfFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigUnsetIfFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDownInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultRouteDev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfLinkLocalAddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigUpInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigUpInterfaceWithFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigCleanUpAllInterfaces(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigFlushRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigFlushRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfmtu(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddArp(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddArpWithInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddMRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddRouteWithInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelArp(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelArpWithInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelDefaultRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelDefaultRouteDev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelMRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetDefaultRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpGetAutoipInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpGetInfoEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelDefaultRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfaddr6WithFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetDefaultRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAddrConfig6Start(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAddrConfig6Stop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAddrConfig6GetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInfoDumpStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInfoDumpStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpdStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpdStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDuplicateIpStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDuplicateIpStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocClearWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocLeave(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocScanJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocSetExtInfoElement(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocSetWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanApStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanApStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanBackgroundScanQuery(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanBackgroundScanStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanBackgroundScanStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanDiagGetDeviceInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanDiagSetAntenna(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanDiagSetTxFixedRate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanGetDeviceConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanInfraGetRssiInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanInfraLeave(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanInfraScanJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanScan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanSetDeviceConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigEtherGetLinkMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigEtherPostPlugInOutEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigEtherSetLinkMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetPppoeStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetPppoeStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetBandwidthControlSetDefaultParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetBandwidthControlGetDefaultParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetBandwidthControlGetDataTraffic(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultScope(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingShowRoutingConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingShowtCtlVar(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetStatisticsInfoInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpAbort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpSetFilterParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpRead(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationDebugSettingsSet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationDebugSettingsGet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationSet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationGet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 04ffc415c34d..9dce19071638 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -7,6 +7,176 @@ ### Unknown function info - [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation. Http2 and Net allocate nothing from these pools. Ssl counts its certificate backend allocations and returned data against its pool; C++ bookkeeping is outside that budget. All three initially report zero use, where the console already reports 0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool. Ssl's allocation sizes and peak usage do not reproduce the console allocator +- [sceNetClearDnsCache](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetStatisticsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetInterfaceStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowIfconfigWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowPolicy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowPolicyWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRouteWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverStartAton6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnectAbort](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnectCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnect](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnectDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetSockInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute6WithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAllocateAllRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAllocateAllRouteInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetControl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDbgInit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackGetError](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackWaitCB](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetFreeAllRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetArpInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetAppMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfList](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfListEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfListOnce](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfName](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfnameNumList](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetNameToIndex](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetSystemTime](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetSystemMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInitParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetIoctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetMemoryAllocate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDnsInfoToKernel](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSocketInternal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncSignal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSysctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetThreadCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetThreadExit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetThreadJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowIfconfigForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRouteForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowNetstatForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowNetstatEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowNetstatExForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetNtopWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetPtonWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDns6InfoToKernel](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute6ForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigUnsetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDownInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultRouteDev](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfLinkLocalAddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigUpInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigUpInterfaceWithFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigCleanUpAllInterfaces](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigFlushRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigFlushRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfmtu](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddArp](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddArpWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddMRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddRouteWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelArp](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelArpWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelDefaultRouteDev](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelMRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpGetAutoipInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpGetInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpGetInfoEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfaddr6WithFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAddrConfig6Start](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAddrConfig6Stop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAddrConfig6GetInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInfoDumpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInfoDumpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpdStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpdStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDuplicateIpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDuplicateIpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocClearWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocGetWakeOnWlanInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocLeave](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocPspEmuClearWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocPspEmuSetWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocScanJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocSetExtInfoElement](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocSetWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanApStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanApStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanBackgroundScanQuery](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanBackgroundScanStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanBackgroundScanStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanDiagGetDeviceInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanDiagSetAntenna](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanDiagSetTxFixedRate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanGetDeviceConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanInfraGetRssiInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanInfraLeave](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanInfraScanJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanScan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanSetDeviceConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigEtherGetLinkMode](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigEtherPostPlugInOutEvent](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigEtherSetLinkMode](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetPppoeStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetPppoeStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetBandwidthControlSetDefaultParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetBandwidthControlGetDefaultParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetBandwidthControlGetDataTraffic](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultScope](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingShowRoutingConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingShowtCtlVar](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetStatisticsInfoInternal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpAbort](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpSetFilterParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpRead](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationDebugSettingsSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationDebugSettingsGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature ### Functional From 70e24614402d1a252a03992fd6e8ceb40dd504aa Mon Sep 17 00:00:00 2001 From: Anton Ermolaev Date: Mon, 19 Oct 2026 11:58:09 +0500 Subject: [PATCH 119/176] fix(libSceHttp): add authentication and cookie cache flush exports (cherry picked from commit 4450bafec80f0eb1e68c584688cbc97a1d953604) --- core/libs/prx/libSceHttp/Export.cpp | 14 ++++++++++++++ core/libs/tests/GuestHttp.cpp | 26 ++++++++++++++++++++++++++ docs/dev/debt/libSceHttp.md | 1 + 3 files changed, 41 insertions(+) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index 86f20a8700d8..a1de4ee7467a 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -131,6 +131,20 @@ int APS5_VABI sceHttpAddRequestHeader(int id, const char* name, const char* valu return 0; } +int APS5_VABI sceHttpAuthCacheFlush(int httpCtxId) { + std::lock_guard lock(g_stateMutex); + if (g_contexts.empty()) return ERROR_BEFORE_INIT; + if (!g_contexts.contains(httpCtxId)) return ERROR_INVALID_ID; + return 0; +} + +int APS5_VABI sceHttpCookieFlush(int httpCtxId) { + std::lock_guard lock(g_stateMutex); + if (g_contexts.empty()) return ERROR_BEFORE_INIT; + if (!g_contexts.contains(httpCtxId)) return ERROR_INVALID_ID; + return 0; +} + int APS5_VABI sceHttpCreateConnection(int tmpl_id, const char* server_name, const char* scheme, uint16_t port, int enable_keep_alive) { (void)tmpl_id; (void)server_name; diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index e2e78d23fabd..bd3a9b01c96f 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -27,6 +27,8 @@ int APS5_VABI sceHttpWaitRequest(HttpEpollHandle, HttpNBEvent*, int, int); int APS5_VABI sceHttpAbortWaitRequest(HttpEpollHandle); int APS5_VABI sceHttpInit_nid_postfix(int, int, std::uint64_t); int APS5_VABI sceHttpTerm_nid_postfix(int); +int APS5_VABI sceHttpAuthCacheFlush(int); +int APS5_VABI sceHttpCookieFlush(int); int APS5_VABI sceHttpReadData(int, void*, std::size_t); int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t); int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); @@ -254,6 +256,13 @@ int main() { constexpr int beforeInit = static_cast(0x80431001); constexpr int aborted = static_cast(0x80431080); constexpr int invalidId = static_cast(0x80431100); + const auto flushCaches = [](int id, int expected) { + Require(sceHttpAuthCacheFlush(id) == expected); + Require(sceHttpCookieFlush(id) == expected); + }; + flushCaches(0, beforeInit); + flushCaches(-1, beforeInit); + flushCaches(request, beforeInit); HttpEpollHandle epoll = nullptr; HttpNBEvent events[2]{}; Require(sceHttpCreateEpoll(1, &epoll) == beforeInit); @@ -261,6 +270,12 @@ int main() { Require(sceHttpTerm_nid_postfix(1) == beforeInit); const int context = sceHttpInit_nid_postfix(0, 0, 0x10000); Require(context > 0); + flushCaches(context, 0); + flushCaches(context, 0); + flushCaches(0, invalidId); + flushCaches(-1, invalidId); + flushCaches(INT32_MAX, invalidId); + flushCaches(request, invalidId); Require(sceHttpCreateEpoll(-1, &epoll) == invalidId); Require(sceHttpCreateEpoll(context, nullptr) == invalidValue); Require(sceHttpCreateEpoll(context, &epoll) == 0 && epoll != nullptr); @@ -304,12 +319,23 @@ int main() { Require(sceHttpCreateEpoll(context, &epoll) == 0); const int otherContext = sceHttpInit_nid_postfix(0, 0, 0x10000); Require(otherContext > 0 && otherContext != context); + flushCaches(otherContext, 0); Require(sceHttpTerm_nid_postfix(otherContext) == 0); + flushCaches(otherContext, invalidId); + flushCaches(context, 0); Require(sceHttpTerm_nid_postfix(otherContext) == invalidId); Require(sceHttpWaitRequest(epoll, events, 2, 0) == 0); Require(waitWokenBy([&] { Require(sceHttpTerm_nid_postfix(context) == 0); }) == 0); Require(sceHttpWaitRequest(epoll, events, 2, 0) == beforeInit); Require(sceHttpTerm_nid_postfix(context) == beforeInit); + flushCaches(context, beforeInit); + const int newContext = sceHttpInit_nid_postfix(0, 0, 0x10000); + Require(newContext > 0 && newContext != context && newContext != otherContext); + flushCaches(context, invalidId); + flushCaches(otherContext, invalidId); + flushCaches(newContext, 0); + Require(sceHttpTerm_nid_postfix(newContext) == 0); + flushCaches(newContext, beforeInit); char data[16]; Require(sceHttpReadData(1, data, sizeof(data)) == network); diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index d59da9042b20..cfcbd069b677 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -24,4 +24,5 @@ - [sceHttpUriEscape](../../../core/libs/prx/libSceHttp/src/Uri.cpp) escapes every byte except the ASCII unreserved set in [RFC 3986 section 2.3](https://www.rfc-editor.org/rfc/rfc3986#section-2.3), independently of the host locale. Console behaviour for non-ASCII input remains unmeasured. +- [sceHttpAuthCacheFlush, sceHttpCookieFlush](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the context-id signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs), and the NIDs `zzB0StvRab4` and `seCvUt91WHY` match [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp). The offline backend never stores authentication credentials or cookies, so flushing a live context succeeds with empty caches. Calls return `BEFORE_INIT` (0x80431001) when no context is alive and `INVALID_ID` (0x80431100) for an unknown, deleted or non-context handle, following the existing context lifecycle checks. These error codes and their precedence have not been measured on PS5 hardware; shadPS4 only stubs these calls. Network-backed caches remain unimplemented. - [sceHttpAbortWaitRequest, sceHttpWaitRequest](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the abort takes only the epoll handle, as in [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); behaviour and error codes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), not confirmed on a PS5 title or hardware. An abort stays set: every later wait on the handle, a poll included, returns `SCE_HTTP_ERROR_ABORTED` (0x80431080) until the handle is destroyed. A wait woken by `sceHttpDestroyEpoll`, or by the `sceHttpTerm` that ends the last context, returns 0 events. The epoll calls return `BEFORE_INIT` (0x80431001) while no `sceHttpInit` context is alive and `INVALID_ID` (0x80431100) for an unknown context or handle, and `sceHttpTerm` returns the same errors instead of succeeding for any id. Handles are pointers, so a stale handle whose address a newer epoll reuses is taken as that epoll From 114604feabbe485ab077b4388655c180d3ec0d6c Mon Sep 17 00:00:00 2001 From: fogydes Date: Thu, 15 Oct 2026 16:04:41 +0530 Subject: [PATCH 120/176] feat(libSceNpGameIntent): export missing firmware game intent interfaces (cherry picked from commit fa94b2cfc8a236ae61fed42863e2ef9925d1a40e) --- core/libs/CMakeLists.txt | 10 +++ core/libs/SceTypes.hpp | 5 ++ core/libs/prx/libSceNpGameIntent/Export.cpp | 27 +++++++++ core/libs/tests/GuestNpGameIntent.cpp | 67 +++++++++++++++++++++ docs/dev/debt/libSceNpGameIntent.md | 10 +++ 5 files changed, 119 insertions(+) create mode 100644 core/libs/tests/GuestNpGameIntent.cpp create mode 100644 docs/dev/debt/libSceNpGameIntent.md diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 53253c6c3ba7..e34f190e52bf 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -518,4 +518,14 @@ if(BUILD_TESTING) set_tests_properties(guest_voice_chat PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_np_game_intent_tests tests/GuestNpGameIntent.cpp) + target_include_directories(guest_np_game_intent_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_game_intent_tests PRIVATE libSceNpGameIntent libc) + configure_windows_unwind(guest_np_game_intent_tests) + add_test(NAME guest_np_game_intent COMMAND guest_np_game_intent_tests) + if(WIN32) + set_tests_properties(guest_np_game_intent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index ae8ee7386375..5cb12eb095d6 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1410,6 +1410,11 @@ struct NpGameIntentData { std::uint8_t padding[7]; }; +struct NpGameIntentInitParam { + std::size_t size; + std::uint8_t reserved[32]; +}; + struct NpGameIntentInfo { std::size_t size; std::int32_t user_id; diff --git a/core/libs/prx/libSceNpGameIntent/Export.cpp b/core/libs/prx/libSceNpGameIntent/Export.cpp index 4df4d87fd4a8..22fd576faef0 100644 --- a/core/libs/prx/libSceNpGameIntent/Export.cpp +++ b/core/libs/prx/libSceNpGameIntent/Export.cpp @@ -46,4 +46,31 @@ int APS5_VABI sceNpGameIntentTerminate(void) { return 0; } +int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param) { + if (param == nullptr) { + APS5_INVALID_ARG_EX; + } + std::memset(param, 0, sizeof(NpGameIntentInitParam)); + param->size = sizeof(NpGameIntentInitParam); + return 0; +} + +int APS5_VABI sceNpGameIntentInfoInit(NpGameIntentInfo* info) { + if (info == nullptr) { + APS5_INVALID_ARG_EX; + } + std::memset(info, 0, sizeof(NpGameIntentInfo)); + info->size = sizeof(NpGameIntentInfo); + info->user_id = NpGameIntentUserIdInvalid; + return 0; +} + +int APS5_VABI sceNpGameIntentVshInitialize(void) { + return 0; +} + +int APS5_VABI sceNpGameIntentVshTerminate(void) { + return 0; +} + } diff --git a/core/libs/tests/GuestNpGameIntent.cpp b/core/libs/tests/GuestNpGameIntent.cpp new file mode 100644 index 000000000000..8d63fecbe1e0 --- /dev/null +++ b/core/libs/tests/GuestNpGameIntent.cpp @@ -0,0 +1,67 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param); +int APS5_VABI sceNpGameIntentInfoInit(NpGameIntentInfo* info); +int APS5_VABI sceNpGameIntentInitialize(const void* initParam); +int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo); +int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize); +int APS5_VABI sceNpGameIntentVshInitialize(void); +int APS5_VABI sceNpGameIntentVshTerminate(void); +int APS5_VABI sceNpGameIntentTerminate(void); +} + +static void Require(bool value, const char* what) { + if (value) return; + std::printf("NpGameIntent: %s\n", what); + std::abort(); +} + +template +static bool Throws(TCall call) { + try { + call(); + } catch (const TError&) { + return true; + } + return false; +} + +int main() { + constexpr int kIntentNotFound = -2141898746; + constexpr int kValueNotFound = -2141898745; + + Require(Throws([] { sceNpGameIntentInitParamInit(nullptr); }), + "null param init did not throw"); + + NpGameIntentInitParam initParam{}; + Require(sceNpGameIntentInitParamInit(&initParam) == 0, "param init failed"); + Require(initParam.size == sizeof(NpGameIntentInitParam), "param size mismatch"); + + Require(Throws([] { sceNpGameIntentInfoInit(nullptr); }), + "null info init did not throw"); + + NpGameIntentInfo info{}; + Require(sceNpGameIntentInfoInit(&info) == 0, "info init failed"); + Require(info.size == sizeof(NpGameIntentInfo), "info size mismatch"); + Require(info.user_id == -1, "info user_id mismatch"); + + Require(sceNpGameIntentInitialize(&initParam) == 0, "initialization failed"); + Require(sceNpGameIntentReceiveIntent(&info) == kIntentNotFound, "receive intent mismatch"); + + char buf[64]{}; + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kValueNotFound, + "get property value mismatch"); + + Require(sceNpGameIntentVshInitialize() == 0, "vsh init failed"); + Require(sceNpGameIntentVshTerminate() == 0, "vsh terminate failed"); + Require(sceNpGameIntentTerminate() == 0, "terminate failed"); + + std::puts("NpGameIntent tests passed"); + return 0; +} diff --git a/docs/dev/debt/libSceNpGameIntent.md b/docs/dev/debt/libSceNpGameIntent.md new file mode 100644 index 000000000000..681c6dbf8fe9 --- /dev/null +++ b/docs/dev/debt/libSceNpGameIntent.md @@ -0,0 +1,10 @@ +# libSceNpGameIntent technical debt + +### Silent stubs + +- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations, matching `sceNpGameIntentInitialize` and `sceNpGameIntentTerminate` + +### Unknown function info + +- [sceNpGameIntentInitParamInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L17) `SceNpGameIntentInitParam`; setting `size`, zeroing reserved bytes, and throwing on null pointer follow Sony SDK `*ParamInit` conventions but are unverified on retail hardware +- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size`, `user_id = -1`, zeroing fields, and throwing on null pointer match `sceNpGameIntentReceiveIntent` defaults but are unverified on retail hardware From caaf71e1678d91a0e051b59ec49808b54a70fae5 Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 04:18:13 +0000 Subject: [PATCH 121/176] feat(prx): inventory confirmed PS5 system libraries (cherry picked from commit bec9cf2e5c07e3a010a63e7551f49e29c0c1e1cf) --- core/libs/prx/libSceAbstractLocal/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAbstractStorage/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAbstractTcs/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAbstractTwitter/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAbstractYoutube/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAc3Enc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAgcResourceRegistration/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAgcVshDebug/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAppChecker/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAppDbShellCoreClient/Export.cpp | 10 ++++++++++ .../Export.cpp | 10 ++++++++++ core/libs/prx/libSceAsyncStorageInternal/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudioLatencyEstimation/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudioSystem/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodReport/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuAlac/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuDdp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuDts/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuFlac/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAvPlayerStreaming/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAvSetting/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAvcap2/Export.cpp | 10 ++++++++++ core/libs/prx/libSceAvcap2mvr/Export.cpp | 10 ++++++++++ core/libs/prx/libSceBackupRestoreUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceBeisobmf/Export.cpp | 10 ++++++++++ core/libs/prx/libSceBemp2sys/Export.cpp | 10 ++++++++++ core/libs/prx/libSceBgsStorage/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCdlgUtilServer/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCompanionHttpd/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCompanionUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceComposite/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCompositeExt/Export.cpp | 10 ++++++++++ core/libs/prx/libSceConnectAccountDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceContentListController/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCtrlp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCustomMusicAudioOut/Export.cpp | 10 ++++++++++ core/libs/prx/libSceCustomMusicCore/Export.cpp | 10 ++++++++++ .../prx/libSceCustomMusicSysCallWrapper/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDataTransfer/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDbg/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDbgAssist/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Appmp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5CpComm/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Ctrlp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Dbgp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Drfp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Eventp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Fsmp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Pgcmp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Rdrfp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Tsmp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5Ttyp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDeci5UlpCommon/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDepth/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDipsw/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDiscId/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDolbyVision/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDseehx/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDtcpIp/Export.cpp | 10 ++++++++++ core/libs/prx/libSceDtsEnc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceEditMp4/Export.cpp | 10 ++++++++++ core/libs/prx/libSceEmbeddedTts/Export.cpp | 10 ++++++++++ core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp | 10 ++++++++++ core/libs/prx/libSceEsvm/Export.cpp | 10 ++++++++++ core/libs/prx/libSceEsvmEngine/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFace/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFaceTracker/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFios2/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFontGs/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFreeTypeOl/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFreeTypeOptOl/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFreeTypeOt/Export.cpp | 10 ++++++++++ core/libs/prx/libSceFsInternalForVsh/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGLSlimVSH/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGic/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGifParser/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGnmDriverCompat1/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGpuCapture/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGpuTrace/Export.cpp | 10 ++++++++++ core/libs/prx/libSceGvMp4Parser/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHand/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHandTracker/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHdrScopes/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHeadTracker/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHidControl/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHmdSetupDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHttpCache/Export.cpp | 10 ++++++++++ core/libs/prx/libSceHubAppUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceIcu/Export.cpp | 10 ++++++++++ core/libs/prx/libSceIdu/Export.cpp | 10 ++++++++++ core/libs/prx/libSceImageUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceIpmi/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJemspace/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJitBridge/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJpegParser/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJsc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJscCompiler/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJson/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJxr/Export.cpp | 10 ++++++++++ core/libs/prx/libSceJxrParser/Export.cpp | 10 ++++++++++ core/libs/prx/libSceKbEmulate/Export.cpp | 10 ++++++++++ core/libs/prx/libSceLibreSsl/Export.cpp | 10 ++++++++++ core/libs/prx/libSceLibreSsl3/Export.cpp | 10 ++++++++++ core/libs/prx/libSceLoginMgrServer/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMarlin/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMat/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMediaAppSharedStorage/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMediaFrameworkInterface/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMediaFrameworkUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMetadataReaderWriter/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMoveTracker/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMusicCoreInterface/Export.cpp | 10 ++++++++++ core/libs/prx/libSceMusicFrameworkInterface/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNKWeb/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNKWebKit/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNKWebKitRequirements/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNotificationClient/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpEulaDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpFriendListDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpProfileDialog/Export.cpp | 10 ++++++++++ .../prx/libSceNpRemotePlaySessionSignaling/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpSns/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpSpeechToText/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpTcs/Export.cpp | 10 ++++++++++ core/libs/prx/libSceNpTextToSpeech/Export.cpp | 10 ++++++++++ core/libs/prx/libSceOpusCeltEnc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceOpusSilkEnc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceOrbisCompat/Export.cpp | 10 ++++++++++ core/libs/prx/libScePadTracker/Export.cpp | 10 ++++++++++ core/libs/prx/libScePatchCheckerClient/Export.cpp | 10 ++++++++++ core/libs/prx/libScePerf/Export.cpp | 10 ++++++++++ core/libs/prx/libScePlayReady/Export.cpp | 10 ++++++++++ core/libs/prx/libScePlayReady2/Export.cpp | 10 ++++++++++ core/libs/prx/libScePlayReady4/Export.cpp | 10 ++++++++++ core/libs/prx/libScePngParser/Export.cpp | 10 ++++++++++ core/libs/prx/libScePs2EmuMenuDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libScePsm/Export.cpp | 10 ++++++++++ core/libs/prx/libScePsmUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceRazorCpu_debug/Export.cpp | 10 ++++++++++ core/libs/prx/libSceRemotePlayClientIpc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceResourceArbitrator/Export.cpp | 10 ++++++++++ core/libs/prx/libSceRnpsAppMgr/Export.cpp | 10 ++++++++++ core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp | 10 ++++++++++ core/libs/prx/libSceS3DConversion/Export.cpp | 10 ++++++++++ core/libs/prx/libSceS3da/Export.cpp | 10 ++++++++++ core/libs/prx/libSceScm/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSdma/Export.cpp | 10 ++++++++++ core/libs/prx/libSceShareFactoryUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceShareInternal.native/Export.cpp | 10 ++++++++++ core/libs/prx/libSceShareUtility/Export.cpp | 10 ++++++++++ core/libs/prx/libSceShellUIUtil/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSmart/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSocialScreen/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSocialScreenDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSpSysCallWrapper/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSrcUtl/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSulphaDrv/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSysBridge/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSystemLogger/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSystemLogger2/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSystemLogger2Delivery/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSystemLogger2Game/Export.cpp | 10 ++++++++++ .../libSceSystemLogger2NativeQueueClient/Export.cpp | 10 ++++++++++ core/libs/prx/libSceSystemTts/Export.cpp | 10 ++++++++++ core/libs/prx/libSceTEEClient/Export.cpp | 10 ++++++++++ core/libs/prx/libSceTextToSpeech/Export.cpp | 10 ++++++++++ core/libs/prx/libSceTtsCoreG3Bc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceUpdateService/Export.cpp | 10 ++++++++++ core/libs/prx/libSceUrlConfigResolver/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVcodec/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecCore.native/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecCore/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecSavc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecSavc2.native/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecSavc2/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecShevc.native/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecShevc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecSvp9.native/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVdecwrap/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVenc/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoArbitration/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoCoreInterface/Export.cpp | 10 ++++++++++ .../libs/prx/libSceVideoCoreServerInterface/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoDecoderArbitration/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoNativeExtEssential/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoOutSecondary/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoRecording.native/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoRecording/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVideoStreamingEngine/Export.cpp | 10 ++++++++++ .../libs/prx/libSceVideoStreamingEngine_sys/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVisionManager/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVnaInternal/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVnaWebsocket/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVoiceCommand/Export.cpp | 10 ++++++++++ core/libs/prx/libSceVrServiceDialog/Export.cpp | 10 ++++++++++ core/libs/prx/libSceWeb/Export.cpp | 10 ++++++++++ core/libs/prx/libSceWebKit2/Export.cpp | 10 ++++++++++ core/libs/prx/libSceWebKit2Secure/Export.cpp | 10 ++++++++++ core/libs/prx/libSceWebmParserMdrw/Export.cpp | 10 ++++++++++ core/libs/prx/libSceWkFontConfig/Export.cpp | 10 ++++++++++ core/libs/prx/libSceWorkspace/Export.cpp | 10 ++++++++++ core/libs/prx/libmdbg_syscore/Export.cpp | 10 ++++++++++ 210 files changed, 2100 insertions(+) create mode 100644 core/libs/prx/libSceAbstractLocal/Export.cpp create mode 100644 core/libs/prx/libSceAbstractStorage/Export.cpp create mode 100644 core/libs/prx/libSceAbstractTcs/Export.cpp create mode 100644 core/libs/prx/libSceAbstractTwitter/Export.cpp create mode 100644 core/libs/prx/libSceAbstractYoutube/Export.cpp create mode 100644 core/libs/prx/libSceAc3Enc/Export.cpp create mode 100644 core/libs/prx/libSceAgcResourceRegistration/Export.cpp create mode 100644 core/libs/prx/libSceAgcVshDebug/Export.cpp create mode 100644 core/libs/prx/libSceAppChecker/Export.cpp create mode 100644 core/libs/prx/libSceAppDbShellCoreClient/Export.cpp create mode 100644 core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp create mode 100644 core/libs/prx/libSceAsyncStorageInternal/Export.cpp create mode 100644 core/libs/prx/libSceAudioLatencyEstimation/Export.cpp create mode 100644 core/libs/prx/libSceAudioSystem/Export.cpp create mode 100644 core/libs/prx/libSceAudiodReport/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuAlac/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuDdp/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuDts/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuFlac/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp create mode 100644 core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp create mode 100644 core/libs/prx/libSceAvPlayerStreaming/Export.cpp create mode 100644 core/libs/prx/libSceAvSetting/Export.cpp create mode 100644 core/libs/prx/libSceAvcap2/Export.cpp create mode 100644 core/libs/prx/libSceAvcap2mvr/Export.cpp create mode 100644 core/libs/prx/libSceBackupRestoreUtil/Export.cpp create mode 100644 core/libs/prx/libSceBeisobmf/Export.cpp create mode 100644 core/libs/prx/libSceBemp2sys/Export.cpp create mode 100644 core/libs/prx/libSceBgsStorage/Export.cpp create mode 100644 core/libs/prx/libSceCdlgUtilServer/Export.cpp create mode 100644 core/libs/prx/libSceCompanionHttpd/Export.cpp create mode 100644 core/libs/prx/libSceCompanionUtil/Export.cpp create mode 100644 core/libs/prx/libSceComposite/Export.cpp create mode 100644 core/libs/prx/libSceCompositeExt/Export.cpp create mode 100644 core/libs/prx/libSceConnectAccountDialog/Export.cpp create mode 100644 core/libs/prx/libSceContentListController/Export.cpp create mode 100644 core/libs/prx/libSceCtrlp/Export.cpp create mode 100644 core/libs/prx/libSceCustomMusicAudioOut/Export.cpp create mode 100644 core/libs/prx/libSceCustomMusicCore/Export.cpp create mode 100644 core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp create mode 100644 core/libs/prx/libSceDataTransfer/Export.cpp create mode 100644 core/libs/prx/libSceDbg/Export.cpp create mode 100644 core/libs/prx/libSceDbgAssist/Export.cpp create mode 100644 core/libs/prx/libSceDeci5/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Appmp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5CpComm/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Ctrlp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Dbgp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Drfp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Eventp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Fsmp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Pgcmp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Rdrfp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Tsmp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5Ttyp/Export.cpp create mode 100644 core/libs/prx/libSceDeci5UlpCommon/Export.cpp create mode 100644 core/libs/prx/libSceDepth/Export.cpp create mode 100644 core/libs/prx/libSceDipsw/Export.cpp create mode 100644 core/libs/prx/libSceDiscId/Export.cpp create mode 100644 core/libs/prx/libSceDolbyVision/Export.cpp create mode 100644 core/libs/prx/libSceDseehx/Export.cpp create mode 100644 core/libs/prx/libSceDtcpIp/Export.cpp create mode 100644 core/libs/prx/libSceDtsEnc/Export.cpp create mode 100644 core/libs/prx/libSceEditMp4/Export.cpp create mode 100644 core/libs/prx/libSceEmbeddedTts/Export.cpp create mode 100644 core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp create mode 100644 core/libs/prx/libSceEsvm/Export.cpp create mode 100644 core/libs/prx/libSceEsvmEngine/Export.cpp create mode 100644 core/libs/prx/libSceFace/Export.cpp create mode 100644 core/libs/prx/libSceFaceTracker/Export.cpp create mode 100644 core/libs/prx/libSceFios2/Export.cpp create mode 100644 core/libs/prx/libSceFontGs/Export.cpp create mode 100644 core/libs/prx/libSceFreeTypeOl/Export.cpp create mode 100644 core/libs/prx/libSceFreeTypeOptOl/Export.cpp create mode 100644 core/libs/prx/libSceFreeTypeOt/Export.cpp create mode 100644 core/libs/prx/libSceFsInternalForVsh/Export.cpp create mode 100644 core/libs/prx/libSceGLSlimVSH/Export.cpp create mode 100644 core/libs/prx/libSceGic/Export.cpp create mode 100644 core/libs/prx/libSceGifParser/Export.cpp create mode 100644 core/libs/prx/libSceGnmDriverCompat1/Export.cpp create mode 100644 core/libs/prx/libSceGpuCapture/Export.cpp create mode 100644 core/libs/prx/libSceGpuTrace/Export.cpp create mode 100644 core/libs/prx/libSceGvMp4Parser/Export.cpp create mode 100644 core/libs/prx/libSceHand/Export.cpp create mode 100644 core/libs/prx/libSceHandTracker/Export.cpp create mode 100644 core/libs/prx/libSceHdrScopes/Export.cpp create mode 100644 core/libs/prx/libSceHeadTracker/Export.cpp create mode 100644 core/libs/prx/libSceHidControl/Export.cpp create mode 100644 core/libs/prx/libSceHmdSetupDialog/Export.cpp create mode 100644 core/libs/prx/libSceHttpCache/Export.cpp create mode 100644 core/libs/prx/libSceHubAppUtil/Export.cpp create mode 100644 core/libs/prx/libSceIcu/Export.cpp create mode 100644 core/libs/prx/libSceIdu/Export.cpp create mode 100644 core/libs/prx/libSceImageUtil/Export.cpp create mode 100644 core/libs/prx/libSceIpmi/Export.cpp create mode 100644 core/libs/prx/libSceJemspace/Export.cpp create mode 100644 core/libs/prx/libSceJitBridge/Export.cpp create mode 100644 core/libs/prx/libSceJpegParser/Export.cpp create mode 100644 core/libs/prx/libSceJsc/Export.cpp create mode 100644 core/libs/prx/libSceJscCompiler/Export.cpp create mode 100644 core/libs/prx/libSceJson/Export.cpp create mode 100644 core/libs/prx/libSceJxr/Export.cpp create mode 100644 core/libs/prx/libSceJxrParser/Export.cpp create mode 100644 core/libs/prx/libSceKbEmulate/Export.cpp create mode 100644 core/libs/prx/libSceLibreSsl/Export.cpp create mode 100644 core/libs/prx/libSceLibreSsl3/Export.cpp create mode 100644 core/libs/prx/libSceLoginMgrServer/Export.cpp create mode 100644 core/libs/prx/libSceMarlin/Export.cpp create mode 100644 core/libs/prx/libSceMat/Export.cpp create mode 100644 core/libs/prx/libSceMediaAppSharedStorage/Export.cpp create mode 100644 core/libs/prx/libSceMediaFrameworkInterface/Export.cpp create mode 100644 core/libs/prx/libSceMediaFrameworkUtil/Export.cpp create mode 100644 core/libs/prx/libSceMetadataReaderWriter/Export.cpp create mode 100644 core/libs/prx/libSceMoveTracker/Export.cpp create mode 100644 core/libs/prx/libSceMusicCoreInterface/Export.cpp create mode 100644 core/libs/prx/libSceMusicFrameworkInterface/Export.cpp create mode 100644 core/libs/prx/libSceNKWeb/Export.cpp create mode 100644 core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp create mode 100644 core/libs/prx/libSceNKWebKit/Export.cpp create mode 100644 core/libs/prx/libSceNKWebKitRequirements/Export.cpp create mode 100644 core/libs/prx/libSceNotificationClient/Export.cpp create mode 100644 core/libs/prx/libSceNpEulaDialog/Export.cpp create mode 100644 core/libs/prx/libSceNpFriendListDialog/Export.cpp create mode 100644 core/libs/prx/libSceNpProfileDialog/Export.cpp create mode 100644 core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp create mode 100644 core/libs/prx/libSceNpSns/Export.cpp create mode 100644 core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp create mode 100644 core/libs/prx/libSceNpSpeechToText/Export.cpp create mode 100644 core/libs/prx/libSceNpTcs/Export.cpp create mode 100644 core/libs/prx/libSceNpTextToSpeech/Export.cpp create mode 100644 core/libs/prx/libSceOpusCeltEnc/Export.cpp create mode 100644 core/libs/prx/libSceOpusSilkEnc/Export.cpp create mode 100644 core/libs/prx/libSceOrbisCompat/Export.cpp create mode 100644 core/libs/prx/libScePadTracker/Export.cpp create mode 100644 core/libs/prx/libScePatchCheckerClient/Export.cpp create mode 100644 core/libs/prx/libScePerf/Export.cpp create mode 100644 core/libs/prx/libScePlayReady/Export.cpp create mode 100644 core/libs/prx/libScePlayReady2/Export.cpp create mode 100644 core/libs/prx/libScePlayReady4/Export.cpp create mode 100644 core/libs/prx/libScePngParser/Export.cpp create mode 100644 core/libs/prx/libScePs2EmuMenuDialog/Export.cpp create mode 100644 core/libs/prx/libScePsm/Export.cpp create mode 100644 core/libs/prx/libScePsmUtil/Export.cpp create mode 100644 core/libs/prx/libSceRazorCpu_debug/Export.cpp create mode 100644 core/libs/prx/libSceRemotePlayClientIpc/Export.cpp create mode 100644 core/libs/prx/libSceResourceArbitrator/Export.cpp create mode 100644 core/libs/prx/libSceRnpsAppMgr/Export.cpp create mode 100644 core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp create mode 100644 core/libs/prx/libSceS3DConversion/Export.cpp create mode 100644 core/libs/prx/libSceS3da/Export.cpp create mode 100644 core/libs/prx/libSceScm/Export.cpp create mode 100644 core/libs/prx/libSceSdma/Export.cpp create mode 100644 core/libs/prx/libSceShareFactoryUtil/Export.cpp create mode 100644 core/libs/prx/libSceShareInternal.native/Export.cpp create mode 100644 core/libs/prx/libSceShareUtility/Export.cpp create mode 100644 core/libs/prx/libSceShellUIUtil/Export.cpp create mode 100644 core/libs/prx/libSceSmart/Export.cpp create mode 100644 core/libs/prx/libSceSocialScreen/Export.cpp create mode 100644 core/libs/prx/libSceSocialScreenDialog/Export.cpp create mode 100644 core/libs/prx/libSceSpSysCallWrapper/Export.cpp create mode 100644 core/libs/prx/libSceSrcUtl/Export.cpp create mode 100644 core/libs/prx/libSceSulphaDrv/Export.cpp create mode 100644 core/libs/prx/libSceSysBridge/Export.cpp create mode 100644 core/libs/prx/libSceSystemLogger/Export.cpp create mode 100644 core/libs/prx/libSceSystemLogger2/Export.cpp create mode 100644 core/libs/prx/libSceSystemLogger2Delivery/Export.cpp create mode 100644 core/libs/prx/libSceSystemLogger2Game/Export.cpp create mode 100644 core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp create mode 100644 core/libs/prx/libSceSystemTts/Export.cpp create mode 100644 core/libs/prx/libSceTEEClient/Export.cpp create mode 100644 core/libs/prx/libSceTextToSpeech/Export.cpp create mode 100644 core/libs/prx/libSceTtsCoreG3Bc/Export.cpp create mode 100644 core/libs/prx/libSceUpdateService/Export.cpp create mode 100644 core/libs/prx/libSceUrlConfigResolver/Export.cpp create mode 100644 core/libs/prx/libSceVcodec/Export.cpp create mode 100644 core/libs/prx/libSceVdecCore.native/Export.cpp create mode 100644 core/libs/prx/libSceVdecCore/Export.cpp create mode 100644 core/libs/prx/libSceVdecSavc/Export.cpp create mode 100644 core/libs/prx/libSceVdecSavc2.native/Export.cpp create mode 100644 core/libs/prx/libSceVdecSavc2/Export.cpp create mode 100644 core/libs/prx/libSceVdecShevc.native/Export.cpp create mode 100644 core/libs/prx/libSceVdecShevc/Export.cpp create mode 100644 core/libs/prx/libSceVdecSvp9.native/Export.cpp create mode 100644 core/libs/prx/libSceVdecwrap/Export.cpp create mode 100644 core/libs/prx/libSceVenc/Export.cpp create mode 100644 core/libs/prx/libSceVideoArbitration/Export.cpp create mode 100644 core/libs/prx/libSceVideoCoreInterface/Export.cpp create mode 100644 core/libs/prx/libSceVideoCoreServerInterface/Export.cpp create mode 100644 core/libs/prx/libSceVideoDecoderArbitration/Export.cpp create mode 100644 core/libs/prx/libSceVideoNativeExtEssential/Export.cpp create mode 100644 core/libs/prx/libSceVideoOutSecondary/Export.cpp create mode 100644 core/libs/prx/libSceVideoRecording.native/Export.cpp create mode 100644 core/libs/prx/libSceVideoRecording/Export.cpp create mode 100644 core/libs/prx/libSceVideoStreamingEngine/Export.cpp create mode 100644 core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp create mode 100644 core/libs/prx/libSceVisionManager/Export.cpp create mode 100644 core/libs/prx/libSceVnaInternal/Export.cpp create mode 100644 core/libs/prx/libSceVnaWebsocket/Export.cpp create mode 100644 core/libs/prx/libSceVoiceCommand/Export.cpp create mode 100644 core/libs/prx/libSceVrServiceDialog/Export.cpp create mode 100644 core/libs/prx/libSceWeb/Export.cpp create mode 100644 core/libs/prx/libSceWebKit2/Export.cpp create mode 100644 core/libs/prx/libSceWebKit2Secure/Export.cpp create mode 100644 core/libs/prx/libSceWebmParserMdrw/Export.cpp create mode 100644 core/libs/prx/libSceWkFontConfig/Export.cpp create mode 100644 core/libs/prx/libSceWorkspace/Export.cpp create mode 100644 core/libs/prx/libmdbg_syscore/Export.cpp diff --git a/core/libs/prx/libSceAbstractLocal/Export.cpp b/core/libs/prx/libSceAbstractLocal/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractLocal/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractStorage/Export.cpp b/core/libs/prx/libSceAbstractStorage/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractStorage/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractTcs/Export.cpp b/core/libs/prx/libSceAbstractTcs/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractTcs/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractTwitter/Export.cpp b/core/libs/prx/libSceAbstractTwitter/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractTwitter/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractYoutube/Export.cpp b/core/libs/prx/libSceAbstractYoutube/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractYoutube/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAc3Enc/Export.cpp b/core/libs/prx/libSceAc3Enc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAc3Enc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAgcResourceRegistration/Export.cpp b/core/libs/prx/libSceAgcResourceRegistration/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAgcResourceRegistration/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAgcVshDebug/Export.cpp b/core/libs/prx/libSceAgcVshDebug/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAgcVshDebug/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAppChecker/Export.cpp b/core/libs/prx/libSceAppChecker/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAppChecker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAppDbShellCoreClient/Export.cpp b/core/libs/prx/libSceAppDbShellCoreClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAppDbShellCoreClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp b/core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAsyncStorageInternal/Export.cpp b/core/libs/prx/libSceAsyncStorageInternal/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAsyncStorageInternal/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp b/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudioSystem/Export.cpp b/core/libs/prx/libSceAudioSystem/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAudioSystem/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodReport/Export.cpp b/core/libs/prx/libSceAudiodReport/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodReport/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuAlac/Export.cpp b/core/libs/prx/libSceAudiodecCpuAlac/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuAlac/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDdp/Export.cpp b/core/libs/prx/libSceAudiodecCpuDdp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDdp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDts/Export.cpp b/core/libs/prx/libSceAudiodecCpuDts/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDts/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp b/core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp b/core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuFlac/Export.cpp b/core/libs/prx/libSceAudiodecCpuFlac/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuFlac/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp b/core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp b/core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp b/core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvPlayerStreaming/Export.cpp b/core/libs/prx/libSceAvPlayerStreaming/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAvPlayerStreaming/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvSetting/Export.cpp b/core/libs/prx/libSceAvSetting/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAvSetting/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvcap2/Export.cpp b/core/libs/prx/libSceAvcap2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAvcap2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvcap2mvr/Export.cpp b/core/libs/prx/libSceAvcap2mvr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAvcap2mvr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBackupRestoreUtil/Export.cpp b/core/libs/prx/libSceBackupRestoreUtil/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceBackupRestoreUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBeisobmf/Export.cpp b/core/libs/prx/libSceBeisobmf/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceBeisobmf/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBemp2sys/Export.cpp b/core/libs/prx/libSceBemp2sys/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceBemp2sys/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBgsStorage/Export.cpp b/core/libs/prx/libSceBgsStorage/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceBgsStorage/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCdlgUtilServer/Export.cpp b/core/libs/prx/libSceCdlgUtilServer/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceCdlgUtilServer/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCompanionHttpd/Export.cpp b/core/libs/prx/libSceCompanionHttpd/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCompanionHttpd/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCompanionUtil/Export.cpp b/core/libs/prx/libSceCompanionUtil/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCompanionUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceComposite/Export.cpp b/core/libs/prx/libSceComposite/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceComposite/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCompositeExt/Export.cpp b/core/libs/prx/libSceCompositeExt/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCompositeExt/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceConnectAccountDialog/Export.cpp b/core/libs/prx/libSceConnectAccountDialog/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceConnectAccountDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceContentListController/Export.cpp b/core/libs/prx/libSceContentListController/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceContentListController/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCtrlp/Export.cpp b/core/libs/prx/libSceCtrlp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceCtrlp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp b/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCustomMusicCore/Export.cpp b/core/libs/prx/libSceCustomMusicCore/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCustomMusicCore/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDataTransfer/Export.cpp b/core/libs/prx/libSceDataTransfer/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDataTransfer/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDbg/Export.cpp b/core/libs/prx/libSceDbg/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDbg/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDbgAssist/Export.cpp b/core/libs/prx/libSceDbgAssist/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDbgAssist/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5/Export.cpp b/core/libs/prx/libSceDeci5/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Appmp/Export.cpp b/core/libs/prx/libSceDeci5Appmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Appmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5CpComm/Export.cpp b/core/libs/prx/libSceDeci5CpComm/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5CpComm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Ctrlp/Export.cpp b/core/libs/prx/libSceDeci5Ctrlp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Ctrlp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Dbgp/Export.cpp b/core/libs/prx/libSceDeci5Dbgp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Dbgp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Drfp/Export.cpp b/core/libs/prx/libSceDeci5Drfp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Drfp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Eventp/Export.cpp b/core/libs/prx/libSceDeci5Eventp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Eventp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Fsmp/Export.cpp b/core/libs/prx/libSceDeci5Fsmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Fsmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Pgcmp/Export.cpp b/core/libs/prx/libSceDeci5Pgcmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Pgcmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Rdrfp/Export.cpp b/core/libs/prx/libSceDeci5Rdrfp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Rdrfp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Tsmp/Export.cpp b/core/libs/prx/libSceDeci5Tsmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Tsmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Ttyp/Export.cpp b/core/libs/prx/libSceDeci5Ttyp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Ttyp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5UlpCommon/Export.cpp b/core/libs/prx/libSceDeci5UlpCommon/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5UlpCommon/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDepth/Export.cpp b/core/libs/prx/libSceDepth/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDepth/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDipsw/Export.cpp b/core/libs/prx/libSceDipsw/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDipsw/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDiscId/Export.cpp b/core/libs/prx/libSceDiscId/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDiscId/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDolbyVision/Export.cpp b/core/libs/prx/libSceDolbyVision/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDolbyVision/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDseehx/Export.cpp b/core/libs/prx/libSceDseehx/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDseehx/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDtcpIp/Export.cpp b/core/libs/prx/libSceDtcpIp/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDtcpIp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDtsEnc/Export.cpp b/core/libs/prx/libSceDtsEnc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDtsEnc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEditMp4/Export.cpp b/core/libs/prx/libSceEditMp4/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceEditMp4/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEmbeddedTts/Export.cpp b/core/libs/prx/libSceEmbeddedTts/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEmbeddedTts/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp b/core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEsvm/Export.cpp b/core/libs/prx/libSceEsvm/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEsvm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEsvmEngine/Export.cpp b/core/libs/prx/libSceEsvmEngine/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEsvmEngine/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFace/Export.cpp b/core/libs/prx/libSceFace/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceFace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFaceTracker/Export.cpp b/core/libs/prx/libSceFaceTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceFaceTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFios2/Export.cpp b/core/libs/prx/libSceFios2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFios2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFontGs/Export.cpp b/core/libs/prx/libSceFontGs/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFontGs/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFreeTypeOl/Export.cpp b/core/libs/prx/libSceFreeTypeOl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFreeTypeOptOl/Export.cpp b/core/libs/prx/libSceFreeTypeOptOl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOptOl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFsInternalForVsh/Export.cpp b/core/libs/prx/libSceFsInternalForVsh/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFsInternalForVsh/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGLSlimVSH/Export.cpp b/core/libs/prx/libSceGLSlimVSH/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGic/Export.cpp b/core/libs/prx/libSceGic/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGic/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGifParser/Export.cpp b/core/libs/prx/libSceGifParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGifParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGnmDriverCompat1/Export.cpp b/core/libs/prx/libSceGnmDriverCompat1/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGnmDriverCompat1/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGpuCapture/Export.cpp b/core/libs/prx/libSceGpuCapture/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGpuCapture/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGpuTrace/Export.cpp b/core/libs/prx/libSceGpuTrace/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGpuTrace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGvMp4Parser/Export.cpp b/core/libs/prx/libSceGvMp4Parser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGvMp4Parser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHand/Export.cpp b/core/libs/prx/libSceHand/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHand/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHandTracker/Export.cpp b/core/libs/prx/libSceHandTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHandTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHdrScopes/Export.cpp b/core/libs/prx/libSceHdrScopes/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceHdrScopes/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHeadTracker/Export.cpp b/core/libs/prx/libSceHeadTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHeadTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHidControl/Export.cpp b/core/libs/prx/libSceHidControl/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHidControl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHmdSetupDialog/Export.cpp b/core/libs/prx/libSceHmdSetupDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHmdSetupDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHttpCache/Export.cpp b/core/libs/prx/libSceHttpCache/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHttpCache/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHubAppUtil/Export.cpp b/core/libs/prx/libSceHubAppUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceHubAppUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceIcu/Export.cpp b/core/libs/prx/libSceIcu/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceIcu/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceIdu/Export.cpp b/core/libs/prx/libSceIdu/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceIdu/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceImageUtil/Export.cpp b/core/libs/prx/libSceImageUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceImageUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceIpmi/Export.cpp b/core/libs/prx/libSceIpmi/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceIpmi/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJemspace/Export.cpp b/core/libs/prx/libSceJemspace/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJemspace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJitBridge/Export.cpp b/core/libs/prx/libSceJitBridge/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJitBridge/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJpegParser/Export.cpp b/core/libs/prx/libSceJpegParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJpegParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJsc/Export.cpp b/core/libs/prx/libSceJsc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJsc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJscCompiler/Export.cpp b/core/libs/prx/libSceJscCompiler/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJscCompiler/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJson/Export.cpp b/core/libs/prx/libSceJson/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJson/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJxrParser/Export.cpp b/core/libs/prx/libSceJxrParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJxrParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceKbEmulate/Export.cpp b/core/libs/prx/libSceKbEmulate/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceKbEmulate/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceLoginMgrServer/Export.cpp b/core/libs/prx/libSceLoginMgrServer/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceLoginMgrServer/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMarlin/Export.cpp b/core/libs/prx/libSceMarlin/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceMarlin/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMat/Export.cpp b/core/libs/prx/libSceMat/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceMat/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMediaAppSharedStorage/Export.cpp b/core/libs/prx/libSceMediaAppSharedStorage/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMediaAppSharedStorage/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMediaFrameworkInterface/Export.cpp b/core/libs/prx/libSceMediaFrameworkInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMediaFrameworkInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMediaFrameworkUtil/Export.cpp b/core/libs/prx/libSceMediaFrameworkUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMediaFrameworkUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMetadataReaderWriter/Export.cpp b/core/libs/prx/libSceMetadataReaderWriter/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMetadataReaderWriter/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMoveTracker/Export.cpp b/core/libs/prx/libSceMoveTracker/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMoveTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMusicCoreInterface/Export.cpp b/core/libs/prx/libSceMusicCoreInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMusicCoreInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp b/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWeb/Export.cpp b/core/libs/prx/libSceNKWeb/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWeb/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp b/core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWebKitRequirements/Export.cpp b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNotificationClient/Export.cpp b/core/libs/prx/libSceNotificationClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNotificationClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpEulaDialog/Export.cpp b/core/libs/prx/libSceNpEulaDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpEulaDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpFriendListDialog/Export.cpp b/core/libs/prx/libSceNpFriendListDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpFriendListDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpProfileDialog/Export.cpp b/core/libs/prx/libSceNpProfileDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpProfileDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp b/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpSns/Export.cpp b/core/libs/prx/libSceNpSns/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpSns/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp b/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpSpeechToText/Export.cpp b/core/libs/prx/libSceNpSpeechToText/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpSpeechToText/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpTcs/Export.cpp b/core/libs/prx/libSceNpTcs/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpTcs/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpTextToSpeech/Export.cpp b/core/libs/prx/libSceNpTextToSpeech/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpTextToSpeech/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceOpusCeltEnc/Export.cpp b/core/libs/prx/libSceOpusCeltEnc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceOpusCeltEnc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceOpusSilkEnc/Export.cpp b/core/libs/prx/libSceOpusSilkEnc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceOpusSilkEnc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceOrbisCompat/Export.cpp b/core/libs/prx/libSceOrbisCompat/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceOrbisCompat/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePadTracker/Export.cpp b/core/libs/prx/libScePadTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePadTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePatchCheckerClient/Export.cpp b/core/libs/prx/libScePatchCheckerClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePatchCheckerClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePerf/Export.cpp b/core/libs/prx/libScePerf/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePerf/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePlayReady/Export.cpp b/core/libs/prx/libScePlayReady/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePlayReady/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePlayReady2/Export.cpp b/core/libs/prx/libScePlayReady2/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePlayReady2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePlayReady4/Export.cpp b/core/libs/prx/libScePlayReady4/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePlayReady4/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePngParser/Export.cpp b/core/libs/prx/libScePngParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePngParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp b/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePsm/Export.cpp b/core/libs/prx/libScePsm/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePsm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePsmUtil/Export.cpp b/core/libs/prx/libScePsmUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePsmUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRazorCpu_debug/Export.cpp b/core/libs/prx/libSceRazorCpu_debug/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRazorCpu_debug/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRemotePlayClientIpc/Export.cpp b/core/libs/prx/libSceRemotePlayClientIpc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRemotePlayClientIpc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceResourceArbitrator/Export.cpp b/core/libs/prx/libSceResourceArbitrator/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceResourceArbitrator/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRnpsAppMgr/Export.cpp b/core/libs/prx/libSceRnpsAppMgr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRnpsAppMgr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp b/core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceS3DConversion/Export.cpp b/core/libs/prx/libSceS3DConversion/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceS3DConversion/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceS3da/Export.cpp b/core/libs/prx/libSceS3da/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceS3da/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceScm/Export.cpp b/core/libs/prx/libSceScm/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceScm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSdma/Export.cpp b/core/libs/prx/libSceSdma/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSdma/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShareFactoryUtil/Export.cpp b/core/libs/prx/libSceShareFactoryUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceShareFactoryUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShareInternal.native/Export.cpp b/core/libs/prx/libSceShareInternal.native/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceShareInternal.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShareUtility/Export.cpp b/core/libs/prx/libSceShareUtility/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceShareUtility/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShellUIUtil/Export.cpp b/core/libs/prx/libSceShellUIUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceShellUIUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSmart/Export.cpp b/core/libs/prx/libSceSmart/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSmart/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSocialScreen/Export.cpp b/core/libs/prx/libSceSocialScreen/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSocialScreen/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSocialScreenDialog/Export.cpp b/core/libs/prx/libSceSocialScreenDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSocialScreenDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSpSysCallWrapper/Export.cpp b/core/libs/prx/libSceSpSysCallWrapper/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSpSysCallWrapper/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSrcUtl/Export.cpp b/core/libs/prx/libSceSrcUtl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSrcUtl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSulphaDrv/Export.cpp b/core/libs/prx/libSceSulphaDrv/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSulphaDrv/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSysBridge/Export.cpp b/core/libs/prx/libSceSysBridge/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSysBridge/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger/Export.cpp b/core/libs/prx/libSceSystemLogger/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSystemLogger/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2/Export.cpp b/core/libs/prx/libSceSystemLogger2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp b/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2Game/Export.cpp b/core/libs/prx/libSceSystemLogger2Game/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Game/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp b/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemTts/Export.cpp b/core/libs/prx/libSceSystemTts/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemTts/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceTEEClient/Export.cpp b/core/libs/prx/libSceTEEClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceTEEClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceTextToSpeech/Export.cpp b/core/libs/prx/libSceTextToSpeech/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceTextToSpeech/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceTtsCoreG3Bc/Export.cpp b/core/libs/prx/libSceTtsCoreG3Bc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceTtsCoreG3Bc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceUpdateService/Export.cpp b/core/libs/prx/libSceUpdateService/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceUpdateService/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceUrlConfigResolver/Export.cpp b/core/libs/prx/libSceUrlConfigResolver/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceUrlConfigResolver/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVcodec/Export.cpp b/core/libs/prx/libSceVcodec/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVcodec/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecCore.native/Export.cpp b/core/libs/prx/libSceVdecCore.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecCore.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecCore/Export.cpp b/core/libs/prx/libSceVdecCore/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecCore/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSavc/Export.cpp b/core/libs/prx/libSceVdecSavc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecSavc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSavc2.native/Export.cpp b/core/libs/prx/libSceVdecSavc2.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSavc2/Export.cpp b/core/libs/prx/libSceVdecSavc2/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecShevc.native/Export.cpp b/core/libs/prx/libSceVdecShevc.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecShevc.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecShevc/Export.cpp b/core/libs/prx/libSceVdecShevc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecShevc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSvp9.native/Export.cpp b/core/libs/prx/libSceVdecSvp9.native/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVdecSvp9.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecwrap/Export.cpp b/core/libs/prx/libSceVdecwrap/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecwrap/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVenc/Export.cpp b/core/libs/prx/libSceVenc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVenc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoArbitration/Export.cpp b/core/libs/prx/libSceVideoArbitration/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoArbitration/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoCoreInterface/Export.cpp b/core/libs/prx/libSceVideoCoreInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoCoreInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp b/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp b/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp b/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoOutSecondary/Export.cpp b/core/libs/prx/libSceVideoOutSecondary/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoOutSecondary/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoRecording.native/Export.cpp b/core/libs/prx/libSceVideoRecording.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVideoRecording.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoRecording/Export.cpp b/core/libs/prx/libSceVideoRecording/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVideoRecording/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoStreamingEngine/Export.cpp b/core/libs/prx/libSceVideoStreamingEngine/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp b/core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVisionManager/Export.cpp b/core/libs/prx/libSceVisionManager/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVisionManager/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVnaInternal/Export.cpp b/core/libs/prx/libSceVnaInternal/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVnaInternal/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVnaWebsocket/Export.cpp b/core/libs/prx/libSceVnaWebsocket/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVnaWebsocket/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVoiceCommand/Export.cpp b/core/libs/prx/libSceVoiceCommand/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVoiceCommand/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVrServiceDialog/Export.cpp b/core/libs/prx/libSceVrServiceDialog/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVrServiceDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWeb/Export.cpp b/core/libs/prx/libSceWeb/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWeb/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWebKit2/Export.cpp b/core/libs/prx/libSceWebKit2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWebKit2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWebKit2Secure/Export.cpp b/core/libs/prx/libSceWebKit2Secure/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWebKit2Secure/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWebmParserMdrw/Export.cpp b/core/libs/prx/libSceWebmParserMdrw/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWebmParserMdrw/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWkFontConfig/Export.cpp b/core/libs/prx/libSceWkFontConfig/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceWkFontConfig/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWorkspace/Export.cpp b/core/libs/prx/libSceWorkspace/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWorkspace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libmdbg_syscore/Export.cpp b/core/libs/prx/libmdbg_syscore/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libmdbg_syscore/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} From b3efb78ebfae463892faca2b2cf169a9b1c31464 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Mon, 19 Oct 2026 08:28:43 +0100 Subject: [PATCH 122/176] fix(prx): omit title-shipped face modules from inventory (cherry picked from commit b3ab7931911321772922601e7510453453f77e3f) --- core/libs/prx/libSceFace/Export.cpp | 10 ---------- core/libs/prx/libSceFaceTracker/Export.cpp | 10 ---------- 2 files changed, 20 deletions(-) delete mode 100644 core/libs/prx/libSceFace/Export.cpp delete mode 100644 core/libs/prx/libSceFaceTracker/Export.cpp diff --git a/core/libs/prx/libSceFace/Export.cpp b/core/libs/prx/libSceFace/Export.cpp deleted file mode 100644 index fb34206c211f..000000000000 --- a/core/libs/prx/libSceFace/Export.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -APS5_DUMMY_FUN - -} diff --git a/core/libs/prx/libSceFaceTracker/Export.cpp b/core/libs/prx/libSceFaceTracker/Export.cpp deleted file mode 100644 index fb34206c211f..000000000000 --- a/core/libs/prx/libSceFaceTracker/Export.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -APS5_DUMMY_FUN - -} From 47a4c298277396447d2a3a7ca87cbadccffd07e0 Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 04:18:15 +0000 Subject: [PATCH 123/176] feat(prx): declare verified PS5 pending exports (cherry picked from commit 78fc2c17598eeb5e4b85aeda8ec9cbdc47c2b110) --- core/libs/prx/libSceAppInstUtil/Export.cpp | 85 ++++++++++++ core/libs/prx/libSceAudioIn/Export.cpp | 13 ++ .../prx/libSceAutoMounterClient/Export.cpp | 21 +++ core/libs/prx/libSceBgft/Export.cpp | 121 ++++++++++++++++++ core/libs/prx/libSceMbus/Export.cpp | 4 + core/libs/prx/libSceMove/Export.cpp | 33 +++++ core/libs/prx/libSceNpParty/Export.cpp | 13 ++ .../libScePlayerInvitationDialog/Export.cpp | 22 ++++ core/libs/prx/libSceRazorCpu/Export.cpp | 61 +++++++++ core/libs/prx/libSceRegMgr/Export.cpp | 26 ++++ core/libs/prx/libSceSaveData/Export.cpp | 1 + core/libs/prx/libSceScreenShot/Export.cpp | 17 +++ core/libs/prx/libSceSysCore/Export.cpp | 13 ++ core/libs/prx/libSceSysUtil/Export.cpp | 12 ++ core/libs/prx/libSceUsbStorage/Export.cpp | 71 +++++++++- core/libs/prx/libSceVideodec/Export.cpp | 29 +++++ core/libs/prx/libSceVrTracker/Export.cpp | 33 +++++ 17 files changed, 574 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAppInstUtil/Export.cpp create mode 100644 core/libs/prx/libSceAutoMounterClient/Export.cpp create mode 100644 core/libs/prx/libSceBgft/Export.cpp create mode 100644 core/libs/prx/libSceMove/Export.cpp create mode 100644 core/libs/prx/libSceNpParty/Export.cpp create mode 100644 core/libs/prx/libSceScreenShot/Export.cpp create mode 100644 core/libs/prx/libSceSysCore/Export.cpp create mode 100644 core/libs/prx/libSceSysUtil/Export.cpp create mode 100644 core/libs/prx/libSceVideodec/Export.cpp create mode 100644 core/libs/prx/libSceVrTracker/Export.cpp diff --git a/core/libs/prx/libSceAppInstUtil/Export.cpp b/core/libs/prx/libSceAppInstUtil/Export.cpp new file mode 100644 index 000000000000..eaca4c464362 --- /dev/null +++ b/core/libs/prx/libSceAppInstUtil/Export.cpp @@ -0,0 +1,85 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAppInstUtilInitialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilTerminate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppInstallAll(void* reserved) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppInstallTitleDir(const char* titleId, const char* titleDir, void* reserved) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppIsInInstalling(int32_t* installing) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppIsInUpdating(const char* titleId, int32_t* updating) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppExists(const char* titleId, int32_t* exists) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppGetSize(const char* titleId, uint64_t* size) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppInstallPkg(const char* pkgPath, void* reserved) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppPrepareOverwritePkg(const char* pkgPath) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppRecoverApp(const char* titleId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstall(const char* titleId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallAddcont(const char* titleId, const char* addcontName) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallPat(const char* titleId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallTheme(const char* contentId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallTypes(const char* titleId, uint32_t deleteTypes) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilGetInstallProgressInfo(const char* contentId, uint32_t* state, uint32_t* progress, uint64_t* progressSize, uint64_t* totalSize, uint32_t* restSec) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilGetInstallProgress(const char* contentId, uint32_t* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilGetPrimaryAppSlot(const char* titleId, int32_t* slot) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceAudioIn/Export.cpp b/core/libs/prx/libSceAudioIn/Export.cpp index b07ad2150688..fb53fc101010 100644 --- a/core/libs/prx/libSceAudioIn/Export.cpp +++ b/core/libs/prx/libSceAudioIn/Export.cpp @@ -204,4 +204,17 @@ int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) { return 0; } +int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) { + (void)handle; + (void)gain; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int32_t APS5_VABI sceAudioInGetRerouteCount(int32_t handle) { + (void)handle; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceAutoMounterClient/Export.cpp b/core/libs/prx/libSceAutoMounterClient/Export.cpp new file mode 100644 index 000000000000..fa738be156dc --- /dev/null +++ b/core/libs/prx/libSceAutoMounterClient/Export.cpp @@ -0,0 +1,21 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo(const char* device, void* info) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAutoMounterClientGetUsbDeviceList(char (*devices)[64], int32_t* count) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAutoMounterClientInit(uint64_t* value) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceBgft/Export.cpp b/core/libs/prx/libSceBgft/Export.cpp new file mode 100644 index 000000000000..c99ad57c08f7 --- /dev/null +++ b/core/libs/prx/libSceBgft/Export.cpp @@ -0,0 +1,121 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceBgftServiceInit(void* params) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadPauseTaskAll(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadResumeTaskAll(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStartTaskAll(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStopTaskAll(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadFindActivePatchTask(const char* unk, int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadFindActivePupTask(int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadFindTaskByContentId(const char* contentId, int32_t subType, int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadGetProgress(int32_t taskId, void* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadPauseTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadResumeTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStartTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStopTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask(const char* contentId, int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadFindActiveTask(const char* contentId, int32_t subType, int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress(const char* contentId, void* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress(const char* contentId, void* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress(const char* contentId, void* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetProgress(int32_t taskId, void* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo(int32_t taskId, void* taskInfo) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadPauseTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx(void* params, int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadResumeTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadStopTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadStartTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadUnregisterTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntInit(void* params) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceMbus/Export.cpp b/core/libs/prx/libSceMbus/Export.cpp index 9bfe48812efb..2107420e01fe 100644 --- a/core/libs/prx/libSceMbus/Export.cpp +++ b/core/libs/prx/libSceMbus/Export.cpp @@ -34,4 +34,8 @@ std::int32_t APS5_VABI sceDeviceServiceQueryDeviceInfo_(std::int32_t deviceClass return 0; } +std::int32_t APS5_VABI sceDeviceServiceGetGeneration(void) { + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceMove/Export.cpp b/core/libs/prx/libSceMove/Export.cpp new file mode 100644 index 000000000000..6c2199ff94f2 --- /dev/null +++ b/core/libs/prx/libSceMove/Export.cpp @@ -0,0 +1,33 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceMoveClose(int32_t handle) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveGetExtensionPortInfo(int32_t handle, void* data) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveReadStateLatest(int32_t handle, void* data) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveReadStateRecent(int32_t handle, int64_t timestamp, void* data, int32_t* out_count) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveResetLightSphere(int32_t handle) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveSetLightSphere(int32_t handle, uint8_t red, uint8_t green, uint8_t blue) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceNpParty/Export.cpp b/core/libs/prx/libSceNpParty/Export.cpp new file mode 100644 index 000000000000..8440dbddcf86 --- /dev/null +++ b/core/libs/prx/libSceNpParty/Export.cpp @@ -0,0 +1,13 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNpPartyGetState(void* state) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp index 64a5328b6eda..aa88766c0c0b 100644 --- a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp +++ b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp @@ -80,4 +80,26 @@ int APS5_VABI scePlayerInvitationDialogParamInitialize(void* param) { return 0; } +int APS5_VABI scePlayerInvitationDialogInitialize(void) { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI scePlayerInvitationDialogOpen(const void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI scePlayerInvitationDialogGetStatus(void) { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI scePlayerInvitationDialogGetResult(void* result) { + (void)result; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI scePlayerInvitationDialogClose(void) { + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 116c784a6f26..c47fa0c77c97 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -68,4 +68,65 @@ int APS5_VABI sceRazorCpuSync(void) { return 0; } +void APS5_VABI sceRazorCpuDisableFiberUserMarkers(void) { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuInitDataTags(void* buffer, size_t size) { + (void)buffer; + (void)size; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuShutdownDataTags(void) { + NotImplemented_nid_no_patch(__func__); +} + +size_t APS5_VABI sceRazorCpuGetDataTagStorageSize(uint32_t tagCount) { + (void)tagCount; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuTagArray(const char* label, uint32_t category, void* array, size_t bufferSize, size_t elementSize) { + (void)label; + (void)category; + (void)array; + (void)bufferSize; + (void)elementSize; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* buffer, size_t bufferSize) { + (void)label; + (void)category; + (void)buffer; + (void)bufferSize; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuResizeTaggedBuffer(uint32_t category, void* buffer, size_t newBufferSize) { + (void)category; + (void)buffer; + (void)newBufferSize; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuUnTagBuffer(uint32_t category, void* buffer) { + (void)category; + (void)buffer; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t tag, uint64_t size, uint8_t operation) { + (void)filename; + (void)tag; + (void)size; + (void)operation; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuEndLogicalFileAccess(void) { + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceRegMgr/Export.cpp b/core/libs/prx/libSceRegMgr/Export.cpp index d71daed4b071..915b839633e2 100644 --- a/core/libs/prx/libSceRegMgr/Export.cpp +++ b/core/libs/prx/libSceRegMgr/Export.cpp @@ -1,8 +1,34 @@ #include +#include +#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { +int APS5_VABI sceRegMgrGetBin(int32_t optionId, void* outData, std::size_t dataLength) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrGetInt(int32_t optionId, int32_t* outInt) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrGetStr(int32_t optionId, char* outString, std::size_t stringLength) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrSetBin(int32_t optionId, const void* inData, std::size_t dataLength) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrSetInt(int32_t optionId, int32_t inInt) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrSetStr(int32_t optionId, const char* inStirng, std::size_t stringLength) { + NotImplemented_nid_no_patch(__func__); + return 0; +} std::int32_t APS5_VABI sceRegMgrNonSysGetInt(std::int64_t key, std::int32_t unk, std::int32_t* data) { (void)key; (void)unk; diff --git a/core/libs/prx/libSceSaveData/Export.cpp b/core/libs/prx/libSceSaveData/Export.cpp index 0d44da8d1bbf..6b10aaa36e0e 100644 --- a/core/libs/prx/libSceSaveData/Export.cpp +++ b/core/libs/prx/libSceSaveData/Export.cpp @@ -91,4 +91,5 @@ int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* moun SAVEDATA_TRACE("sceSaveDataTransferringMount -> 0x%08x", static_cast(rc)); return rc; } + } diff --git a/core/libs/prx/libSceScreenShot/Export.cpp b/core/libs/prx/libSceScreenShot/Export.cpp new file mode 100644 index 000000000000..cfc0268330c5 --- /dev/null +++ b/core/libs/prx/libSceScreenShot/Export.cpp @@ -0,0 +1,17 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int32_t x, int32_t y) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int32_t x, int32_t y, int32_t origin) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceSysCore/Export.cpp b/core/libs/prx/libSceSysCore/Export.cpp new file mode 100644 index 000000000000..914f522c51db --- /dev/null +++ b/core/libs/prx/libSceSysCore/Export.cpp @@ -0,0 +1,13 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceApplicationGetAppInfoByAppId(int32_t appId, void* info) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceSysUtil/Export.cpp b/core/libs/prx/libSceSysUtil/Export.cpp new file mode 100644 index 000000000000..a4fff2ce8bfe --- /dev/null +++ b/core/libs/prx/libSceSysUtil/Export.cpp @@ -0,0 +1,12 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +void APS5_VABI sceSysUtilSendSystemNotificationWithText(int32_t type, const char* message) { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceUsbStorage/Export.cpp b/core/libs/prx/libSceUsbStorage/Export.cpp index fb34206c211f..befdd3e768db 100644 --- a/core/libs/prx/libSceUsbStorage/Export.cpp +++ b/core/libs/prx/libSceUsbStorage/Export.cpp @@ -5,6 +5,75 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceUsbStorageInit(void* attr) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRegisterCallback(int32_t eventType, void* callback, void* arg) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageUnregisterCallback(int32_t eventType, void* callback) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRegisterCallbackForMapAvailable(uint32_t device, const char* directory, void* callback, void* arg) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageUnregisterCallbackForMapAvailable(uint32_t device, const char* directory, void* callback) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRegisterCallbackForMapUnavailable(uint32_t device, const char* directory, void* callback, void* arg) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageUnregisterCallbackForMapUnavailable(uint32_t device, const char* directory, void* callback) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetDeviceInfo(uint32_t device, void* info) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetDeviceList(uint32_t* devices, int32_t* count) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetMountPointOfShellCore(uint32_t device, char* mountPoint) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageIsExist(uint32_t device) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRequestMap(uint32_t device, const char* directory, int32_t flags, + uint64_t volumeSize, char* mountPoint, uint64_t* mountId, + const void* reserved, size_t reservedSize) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRequestUnmap(uint32_t device, const char* directory) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetdentsOpen(uint32_t device, int32_t* fd, uint64_t* bufferSize) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetdentsRead(int32_t fd, void* buffer, uint64_t bufferSize, uint64_t* bytesRead) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetdentsClose(int32_t fd) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp new file mode 100644 index 000000000000..8f20f3fc6989 --- /dev/null +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -0,0 +1,29 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceVideodecCreateDecoder(const void* pCfgInfoIn, const void* pRsrcInfoIn, void* pCtrlOut) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecDecode(void* pCtrlIn, const void* pInputDataIn, void* pFrameBufferInOut, void* pPictureInfoOut) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecDeleteDecoder(void* pCtrlIn) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecFlush(void* pCtrlIn, void* pFrameBufferInOut, void* pPictureInfoOut) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecReset(void* pCtrlIn) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceVrTracker/Export.cpp b/core/libs/prx/libSceVrTracker/Export.cpp new file mode 100644 index 000000000000..88937665aba3 --- /dev/null +++ b/core/libs/prx/libSceVrTracker/Export.cpp @@ -0,0 +1,33 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceVrTrackerCpuProcess(const void* param) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGetResult(const void* param, void* result) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGetTime(uint64_t* time) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGpuSubmit(const void* param) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGpuWait(const void* param) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerInit(const void* param) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} From 049418b9054734099bc748f93f065850f4df5db8 Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 05:26:30 +0000 Subject: [PATCH 124/176] fix(prx): mark unsupported export prototypes as unknown (cherry picked from commit 1b5004d77f3637c01745dba01ef6082caa646f3e) --- core/libs/prx/libSceAppInstUtil/Export.cpp | 24 +++++------ .../prx/libSceAutoMounterClient/Export.cpp | 6 +-- core/libs/prx/libSceBgft/Export.cpp | 40 +++++++++---------- core/libs/prx/libSceRegMgr/Export.cpp | 4 +- core/libs/prx/libSceScreenShot/Export.cpp | 4 +- core/libs/prx/libSceSysCore/Export.cpp | 2 +- core/libs/prx/libSceSysUtil/Export.cpp | 3 +- core/libs/prx/libSceVrTracker/Export.cpp | 12 +++--- docs/dev/debt/libSceAppInstUtil.md | 5 +++ docs/dev/debt/libSceAutoMounterClient.md | 5 +++ docs/dev/debt/libSceBgft.md | 5 +++ docs/dev/debt/libSceRegMgr.md | 2 +- docs/dev/debt/libSceScreenShot.md | 5 +++ docs/dev/debt/libSceSysCore.md | 5 +++ docs/dev/debt/libSceVrTracker.md | 5 +++ 15 files changed, 79 insertions(+), 48 deletions(-) create mode 100644 docs/dev/debt/libSceAppInstUtil.md create mode 100644 docs/dev/debt/libSceAutoMounterClient.md create mode 100644 docs/dev/debt/libSceBgft.md create mode 100644 docs/dev/debt/libSceScreenShot.md create mode 100644 docs/dev/debt/libSceSysCore.md create mode 100644 docs/dev/debt/libSceVrTracker.md diff --git a/core/libs/prx/libSceAppInstUtil/Export.cpp b/core/libs/prx/libSceAppInstUtil/Export.cpp index eaca4c464362..845fd759b0aa 100644 --- a/core/libs/prx/libSceAppInstUtil/Export.cpp +++ b/core/libs/prx/libSceAppInstUtil/Export.cpp @@ -25,7 +25,7 @@ int APS5_VABI sceAppInstUtilAppIsInInstalling(int32_t* installing) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppIsInUpdating(const char* titleId, int32_t* updating) { +int APS5_VABI sceAppInstUtilAppIsInUpdating() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -33,19 +33,19 @@ int APS5_VABI sceAppInstUtilAppExists(const char* titleId, int32_t* exists) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppGetSize(const char* titleId, uint64_t* size) { +int APS5_VABI sceAppInstUtilAppGetSize() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppInstallPkg(const char* pkgPath, void* reserved) { +int APS5_VABI sceAppInstUtilAppInstallPkg() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppPrepareOverwritePkg(const char* pkgPath) { +int APS5_VABI sceAppInstUtilAppPrepareOverwritePkg() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppRecoverApp(const char* titleId) { +int APS5_VABI sceAppInstUtilAppRecoverApp() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -53,31 +53,31 @@ int APS5_VABI sceAppInstUtilAppUnInstall(const char* titleId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallAddcont(const char* titleId, const char* addcontName) { +int APS5_VABI sceAppInstUtilAppUnInstallAddcont() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallPat(const char* titleId) { +int APS5_VABI sceAppInstUtilAppUnInstallPat() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallTheme(const char* contentId) { +int APS5_VABI sceAppInstUtilAppUnInstallTheme() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilAppUnInstallTypes(const char* titleId, uint32_t deleteTypes) { +int APS5_VABI sceAppInstUtilAppUnInstallTypes() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilGetInstallProgressInfo(const char* contentId, uint32_t* state, uint32_t* progress, uint64_t* progressSize, uint64_t* totalSize, uint32_t* restSec) { +int APS5_VABI sceAppInstUtilGetInstallProgressInfo() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilGetInstallProgress(const char* contentId, uint32_t* progress) { +int APS5_VABI sceAppInstUtilGetInstallProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAppInstUtilGetPrimaryAppSlot(const char* titleId, int32_t* slot) { +int APS5_VABI sceAppInstUtilGetPrimaryAppSlot() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAutoMounterClient/Export.cpp b/core/libs/prx/libSceAutoMounterClient/Export.cpp index fa738be156dc..e55d28bc6af5 100644 --- a/core/libs/prx/libSceAutoMounterClient/Export.cpp +++ b/core/libs/prx/libSceAutoMounterClient/Export.cpp @@ -5,15 +5,15 @@ extern "C" { -int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo(const char* device, void* info) { +int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAutoMounterClientGetUsbDeviceList(char (*devices)[64], int32_t* count) { +int APS5_VABI sceAutoMounterClientGetUsbDeviceList() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceAutoMounterClientInit(uint64_t* value) { +int APS5_VABI sceAutoMounterClientInit() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceBgft/Export.cpp b/core/libs/prx/libSceBgft/Export.cpp index c99ad57c08f7..1a1a66de1bf9 100644 --- a/core/libs/prx/libSceBgft/Export.cpp +++ b/core/libs/prx/libSceBgft/Export.cpp @@ -13,27 +13,27 @@ int APS5_VABI sceBgftServiceTerm(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadPauseTaskAll(void) { +int APS5_VABI sceBgftServiceDownloadPauseTaskAll() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadResumeTaskAll(void) { +int APS5_VABI sceBgftServiceDownloadResumeTaskAll() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadStartTaskAll(void) { +int APS5_VABI sceBgftServiceDownloadStartTaskAll() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadStopTaskAll(void) { +int APS5_VABI sceBgftServiceDownloadStopTaskAll() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadFindActivePatchTask(const char* unk, int32_t* taskId) { +int APS5_VABI sceBgftServiceDownloadFindActivePatchTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceDownloadFindActivePupTask(int32_t* taskId) { +int APS5_VABI sceBgftServiceDownloadFindActivePupTask() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -61,59 +61,59 @@ int APS5_VABI sceBgftServiceDownloadStopTask(int32_t taskId) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask(const char* contentId, int32_t* taskId) { +int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadFindActiveTask(const char* contentId, int32_t subType, int32_t* taskId) { +int APS5_VABI sceBgftServiceIntDownloadFindActiveTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress(const char* contentId, void* progress) { +int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress(const char* contentId, void* progress) { +int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress(const char* contentId, void* progress) { +int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetProgress(int32_t taskId, void* progress) { +int APS5_VABI sceBgftServiceIntDownloadGetProgress() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo(int32_t taskId, void* taskInfo) { +int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadPauseTask(int32_t taskId) { +int APS5_VABI sceBgftServiceIntDownloadPauseTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx(void* params, int32_t* taskId) { +int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadResumeTask(int32_t taskId) { +int APS5_VABI sceBgftServiceIntDownloadResumeTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadStopTask(int32_t taskId) { +int APS5_VABI sceBgftServiceIntDownloadStopTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadStartTask(int32_t taskId) { +int APS5_VABI sceBgftServiceIntDownloadStartTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntDownloadUnregisterTask(int32_t taskId) { +int APS5_VABI sceBgftServiceIntDownloadUnregisterTask() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceBgftServiceIntInit(void* params) { +int APS5_VABI sceBgftServiceIntInit() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceRegMgr/Export.cpp b/core/libs/prx/libSceRegMgr/Export.cpp index 915b839633e2..4626f92e6b03 100644 --- a/core/libs/prx/libSceRegMgr/Export.cpp +++ b/core/libs/prx/libSceRegMgr/Export.cpp @@ -5,7 +5,7 @@ extern "C" { -int APS5_VABI sceRegMgrGetBin(int32_t optionId, void* outData, std::size_t dataLength) { +int APS5_VABI sceRegMgrGetBin() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -17,7 +17,7 @@ int APS5_VABI sceRegMgrGetStr(int32_t optionId, char* outString, std::size_t str NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceRegMgrSetBin(int32_t optionId, const void* inData, std::size_t dataLength) { +int APS5_VABI sceRegMgrSetBin() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceScreenShot/Export.cpp b/core/libs/prx/libSceScreenShot/Export.cpp index cfc0268330c5..41e538d9cce3 100644 --- a/core/libs/prx/libSceScreenShot/Export.cpp +++ b/core/libs/prx/libSceScreenShot/Export.cpp @@ -5,11 +5,11 @@ extern "C" { -int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int32_t x, int32_t y) { +int APS5_VABI sceScreenShotSetOverlayImage() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int32_t x, int32_t y, int32_t origin) { +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceSysCore/Export.cpp b/core/libs/prx/libSceSysCore/Export.cpp index 914f522c51db..7c5cd4bbd6b8 100644 --- a/core/libs/prx/libSceSysCore/Export.cpp +++ b/core/libs/prx/libSceSysCore/Export.cpp @@ -5,7 +5,7 @@ extern "C" { -int APS5_VABI sceApplicationGetAppInfoByAppId(int32_t appId, void* info) { +int APS5_VABI sceApplicationGetAppInfoByAppId() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceSysUtil/Export.cpp b/core/libs/prx/libSceSysUtil/Export.cpp index a4fff2ce8bfe..8cdc46a18251 100644 --- a/core/libs/prx/libSceSysUtil/Export.cpp +++ b/core/libs/prx/libSceSysUtil/Export.cpp @@ -5,8 +5,9 @@ extern "C" { -void APS5_VABI sceSysUtilSendSystemNotificationWithText(int32_t type, const char* message) { +int APS5_VABI sceSysUtilSendSystemNotificationWithText(int32_t type, const char* message) { NotImplemented_nid_no_patch(__func__); + return 0; } } diff --git a/core/libs/prx/libSceVrTracker/Export.cpp b/core/libs/prx/libSceVrTracker/Export.cpp index 88937665aba3..c2e4e7c8f34f 100644 --- a/core/libs/prx/libSceVrTracker/Export.cpp +++ b/core/libs/prx/libSceVrTracker/Export.cpp @@ -5,27 +5,27 @@ extern "C" { -int APS5_VABI sceVrTrackerCpuProcess(const void* param) { +int APS5_VABI sceVrTrackerCpuProcess() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGetResult(const void* param, void* result) { +int APS5_VABI sceVrTrackerGetResult() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGetTime(uint64_t* time) { +int APS5_VABI sceVrTrackerGetTime() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGpuSubmit(const void* param) { +int APS5_VABI sceVrTrackerGpuSubmit() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerGpuWait(const void* param) { +int APS5_VABI sceVrTrackerGpuWait() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceVrTrackerInit(const void* param) { +int APS5_VABI sceVrTrackerInit() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/docs/dev/debt/libSceAppInstUtil.md b/docs/dev/debt/libSceAppInstUtil.md new file mode 100644 index 000000000000..ecf5fb2a82b1 --- /dev/null +++ b/docs/dev/debt/libSceAppInstUtil.md @@ -0,0 +1,5 @@ +# libSceAppInstUtil technical debt + +### Unknown function info + +- [sceAppInstUtilAppIsInUpdating, sceAppInstUtilAppGetSize, sceAppInstUtilAppInstallPkg, sceAppInstUtilAppPrepareOverwritePkg, sceAppInstUtilAppRecoverApp, sceAppInstUtilAppUnInstallAddcont, sceAppInstUtilAppUnInstallPat, sceAppInstUtilAppUnInstallTheme, sceAppInstUtilAppUnInstallTypes, sceAppInstUtilGetInstallProgressInfo, sceAppInstUtilGetInstallProgress and sceAppInstUtilGetPrimaryAppSlot](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppInstUtil.c), but no credible prototype was found. They are declared without parameters only to resolve the known imports; this is not an ABI claim and callers must not call them until a prototype is verified. The other seven declarations follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppInstUtil/AppInstUtil.cs) diff --git a/docs/dev/debt/libSceAutoMounterClient.md b/docs/dev/debt/libSceAutoMounterClient.md new file mode 100644 index 000000000000..10f8710cf9a3 --- /dev/null +++ b/docs/dev/debt/libSceAutoMounterClient.md @@ -0,0 +1,5 @@ +# libSceAutoMounterClient technical debt + +### Unknown function info + +- [libSceAutoMounterClient](../../../core/libs/prx/libSceAutoMounterClient/Export.cpp) - the three names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAutoMounterClient.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and the exports must not be called until their signatures are verified diff --git a/docs/dev/debt/libSceBgft.md b/docs/dev/debt/libSceBgft.md new file mode 100644 index 000000000000..8d8e3ccbe7cb --- /dev/null +++ b/docs/dev/debt/libSceBgft.md @@ -0,0 +1,5 @@ +# libSceBgft technical debt + +### Unknown function info + +- [sceBgftServiceDownloadPauseTaskAll, sceBgftServiceDownloadResumeTaskAll, sceBgftServiceDownloadStartTaskAll, sceBgftServiceDownloadStopTaskAll, sceBgftServiceDownloadFindActivePatchTask, sceBgftServiceDownloadFindActivePupTask, sceBgftServiceIntDownloadFindActiveGameAndGameAcTask, sceBgftServiceIntDownloadFindActiveTask, sceBgftServiceIntDownloadGetGameAndGameAcProgress, sceBgftServiceIntDownloadGetPatchProgress, sceBgftServiceIntDownloadGetPlayGoProgress, sceBgftServiceIntDownloadGetProgress, sceBgftServiceIntDownloadGetTaskInfo, sceBgftServiceIntDownloadPauseTask, sceBgftServiceIntDownloadRegisterTaskByStorageEx, sceBgftServiceIntDownloadResumeTask, sceBgftServiceIntDownloadStopTask, sceBgftServiceIntDownloadStartTask, sceBgftServiceIntDownloadUnregisterTask and sceBgftServiceIntInit](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. The eight task-control declarations not listed here follow [SharpProspero's DownloadService bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/DownloadService.cs) diff --git a/docs/dev/debt/libSceRegMgr.md b/docs/dev/debt/libSceRegMgr.md index 467ad2850e96..f5d805b3ffe9 100644 --- a/docs/dev/debt/libSceRegMgr.md +++ b/docs/dev/debt/libSceRegMgr.md @@ -2,4 +2,4 @@ ### Unknown function info -- [sceRegMgrNonSysGetInt](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr, NID `dKeshzt29G4`) - imported by Astro Bot (PPSA21564), which fails to load without libSceRegMgr.prx (issue 4720); the `(int64_t key, int32_t unk, int32_t* data)` signature follows [ChonkyStation4](https://github.com/liuk7071/ChonkyStation4/blob/c101a23ecbc15e56c6e30d5dd24d3ed8880a957a/ChonkyStation4/OS/Libraries/SceRegMgr/SceRegMgr.cpp#L38-L47), not measured on a console, so every call throws +- [sceRegMgrGetBin and sceRegMgrSetBin](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRegMgr.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. The string and integer prototypes follow [SharpProspero's SystemSettings bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/SystemSettings.cs) diff --git a/docs/dev/debt/libSceScreenShot.md b/docs/dev/debt/libSceScreenShot.md new file mode 100644 index 000000000000..89867a84f60f --- /dev/null +++ b/docs/dev/debt/libSceScreenShot.md @@ -0,0 +1,5 @@ +# libSceScreenShot technical debt + +### Unknown function info + +- [sceScreenShotSetOverlayImage and sceScreenShotSetOverlayImageWithOrigin](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceScreenShot.c). The PS4 compatibility implementation also exposes the names but declares no parameters; no PS5 prototype was found. The declarations have no parameters solely to resolve known imports and make no ABI claim diff --git a/docs/dev/debt/libSceSysCore.md b/docs/dev/debt/libSceSysCore.md new file mode 100644 index 000000000000..0ebd352ebdd5 --- /dev/null +++ b/docs/dev/debt/libSceSysCore.md @@ -0,0 +1,5 @@ +# libSceSysCore technical debt + +### Unknown function info + +- [sceApplicationGetAppInfoByAppId](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - the name and NID are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c), but no credible prototype was found. The declaration has no parameters solely to resolve a known import and makes no ABI claim diff --git a/docs/dev/debt/libSceVrTracker.md b/docs/dev/debt/libSceVrTracker.md new file mode 100644 index 000000000000..918f6cc2d434 --- /dev/null +++ b/docs/dev/debt/libSceVrTracker.md @@ -0,0 +1,5 @@ +# libSceVrTracker technical debt + +### Unknown function info + +- [sceVrTrackerCpuProcess, sceVrTrackerGetResult, sceVrTrackerGetTime, sceVrTrackerGpuSubmit, sceVrTrackerGpuWait and sceVrTrackerInit](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified From dd3acd4b7fa8956f71934b79f878dd78a7814e7b Mon Sep 17 00:00:00 2001 From: xdCloudy Date: Mon, 19 Oct 2026 05:34:07 +0000 Subject: [PATCH 125/176] fix(prx): drop rebased invitation duplicates (cherry picked from commit 896704b7f28614e29d987f02e753c39715be0b74) --- .../libScePlayerInvitationDialog/Export.cpp | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp index aa88766c0c0b..64a5328b6eda 100644 --- a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp +++ b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp @@ -80,26 +80,4 @@ int APS5_VABI scePlayerInvitationDialogParamInitialize(void* param) { return 0; } -int APS5_VABI scePlayerInvitationDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI scePlayerInvitationDialogOpen(const void* param) { - (void)param; - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI scePlayerInvitationDialogGetStatus(void) { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI scePlayerInvitationDialogGetResult(void* result) { - (void)result; - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI scePlayerInvitationDialogClose(void) { - NotImplemented_nid_no_patch(__func__); -} - } From ec5ca9e5b70431edcb51e4ba48bdf38536f43694 Mon Sep 17 00:00:00 2001 From: xdCloudy <52116030+xdCloudy@users.noreply.github.com> Date: Mon, 19 Oct 2026 08:29:42 +0100 Subject: [PATCH 126/176] fix(prx): retain RegMgr debt and avoid SaveData overlap (cherry picked from commit 91c931896eb232b720ff3d6d8105a9a4d6fe376d) --- core/libs/prx/libSceSaveData/Export.cpp | 1 - docs/dev/debt/libSceRegMgr.md | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceSaveData/Export.cpp b/core/libs/prx/libSceSaveData/Export.cpp index 6b10aaa36e0e..0d44da8d1bbf 100644 --- a/core/libs/prx/libSceSaveData/Export.cpp +++ b/core/libs/prx/libSceSaveData/Export.cpp @@ -91,5 +91,4 @@ int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* moun SAVEDATA_TRACE("sceSaveDataTransferringMount -> 0x%08x", static_cast(rc)); return rc; } - } diff --git a/docs/dev/debt/libSceRegMgr.md b/docs/dev/debt/libSceRegMgr.md index f5d805b3ffe9..e599cc296dbe 100644 --- a/docs/dev/debt/libSceRegMgr.md +++ b/docs/dev/debt/libSceRegMgr.md @@ -2,4 +2,5 @@ ### Unknown function info +- [sceRegMgrNonSysGetInt](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr, NID `dKeshzt29G4`) - observed as an import needed for module loading (issue 4720); its `(int64_t key, int32_t unk, int32_t* data)` signature follows [ChonkyStation4](https://github.com/liuk7071/ChonkyStation4/blob/c101a23ecbc15e56c6e30d5dd24d3ed8880a957a/ChonkyStation4/OS/Libraries/SceRegMgr/SceRegMgr.cpp#L38-L47), not measured on a console, so every call throws - [sceRegMgrGetBin and sceRegMgrSetBin](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRegMgr.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. The string and integer prototypes follow [SharpProspero's SystemSettings bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/SystemSettings.cs) From 672aeb347abc3c4b18ab3f449fa6b2f185d44879 Mon Sep 17 00:00:00 2001 From: Advait Palve Date: Sat, 17 Oct 2026 12:20:07 +0530 Subject: [PATCH 127/176] feat(netctl): implement Wi-Fi info codes in sceNetCtlGetInfo (cherry picked from commit d4fdc030e17b8d6d7019e75e8199fe2b01fc6060) --- docs/dev/debt/libSceNetCtl.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/dev/debt/libSceNetCtl.md b/docs/dev/debt/libSceNetCtl.md index 427e526f3531..396309b1daee 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -7,3 +7,8 @@ ### Unknown function info - [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0 +- [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0; this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/network.cpp) and is not measured on hardware + +### Functional + +- [sceNetCtlCheckCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the state comes from the host interfaces, as in `sceNetCtlGetState`: `IPOBTAINED` (3) while the host has a non-loopback IPv4 or IPv6 address, `DISCONNECTED` (1) otherwise; the event values follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_codes.h#L27-L29), and no other event is delivered. Each callback registered with `sceNetCtlRegisterCallback` is called on the checking thread with its registration argument when the state differs from the last one it received, so a new registration receives the current state at the next check. This edge-triggered model follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3648-L3661); [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_obj.cpp#L47-L57) delivers the state on every check instead, and neither is measured on hardware. KytyPS5 calls the callbacks under its lock; here they are called after the lock is released, from a copy of their slots, so a `sceNetCtlUnregisterCallback` or `sceNetCtlTerm` on another thread that returns while a check is in flight does not stop that check from calling the callback once more with its old argument. Callbacks registered with `sceNetCtlRegisterCallbackV6` keep their argument but receive no event, because no source says which call delivers them From f061364907b29feb4cf1169d839629c87498cf4d Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 19 Oct 2026 13:44:41 +0530 Subject: [PATCH 128/176] fix(libkernel): increase pipe buffer size to match FreeBSD BIG_PIPE_SIZE On Windows, pipe_nid_postfix configured the anonymous pipe with a 4096-byte buffer. When guest executables redirect standard error to a pipe via dup2 and host logging writes to standard error, the pipe buffer fills during startup before or between guest reader drains, blocking writer threads in ZwWriteFile and deadlocking the title. Increase the Windows pipe buffer capacity to 65536 bytes to match FreeBSD BIG_PIPE_SIZE used by the PS5 kernel, and cover multi-page single-shot pipe writes in guest posix scalar IO tests. (cherry picked from commit 9f0485c4cc25c8c296f580b05e0ef147d268247a) --- core/libs/prx/libkernel/File/src/Stdio.cpp | 2 +- core/libs/tests/GuestPosixScalarIo.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 683046a9953e..01aaf6f5d74e 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -494,7 +494,7 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) { if (!destination.Open()) return PosixFailure(GUEST_EFAULT); int native[2]; #ifdef _WIN32 - const int result = ::_pipe(native, 4096, _O_BINARY); + const int result = ::_pipe(native, 65536, _O_BINARY); #else const int result = ::pipe(native); #endif diff --git a/core/libs/tests/GuestPosixScalarIo.cpp b/core/libs/tests/GuestPosixScalarIo.cpp index 938c32f67b98..90cafe8ea9c1 100644 --- a/core/libs/tests/GuestPosixScalarIo.cpp +++ b/core/libs/tests/GuestPosixScalarIo.cpp @@ -191,6 +191,15 @@ int main() { Require(_read_nid_postfix(descriptors[0], pipeBuffer, sizeof(pipeBuffer)) == sizeof(pipeBuffer)); Require(std::memcmp(pipeBuffer, pipePayload, sizeof(pipePayload)) == 0); Require(close_nid_postfix(descriptors[0]) == 0); + int capacityDescriptors[2] = {-1, -1}; + Require(pipe_nid_postfix(capacityDescriptors) == 0); + std::string largePayload(8192, 'x'); + Require(write_nid_postfix(capacityDescriptors[1], largePayload.data(), largePayload.size()) == static_cast(largePayload.size())); + Require(close_nid_postfix(capacityDescriptors[1]) == 0); + std::string largeBuffer(8192, '\0'); + Require(_read_nid_postfix(capacityDescriptors[0], largeBuffer.data(), largeBuffer.size()) == static_cast(largeBuffer.size())); + Require(largeBuffer == largePayload); + Require(close_nid_postfix(capacityDescriptors[0]) == 0); #ifndef _WIN32 int nonBlockingPipe[2] = {-1, -1}; Require(pipe_nid_postfix(nonBlockingPipe) == 0); From 20f759989cad6f36bdd8249a81a518011ad2f494 Mon Sep 17 00:00:00 2001 From: al3w0f205 Date: Fri, 16 Oct 2026 07:14:57 -0500 Subject: [PATCH 129/176] test(libSceGameLiveStreaming): add guest game live streaming test coverage (cherry picked from commit 58d0d539664572ce1e4ceb769a6ba456416860dd) --- core/libs/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index e34f190e52bf..0ada1259dc6c 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -305,6 +305,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_game_live_streaming_tests tests/GuestGameLiveStreaming.cpp) + target_include_directories(guest_game_live_streaming_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_game_live_streaming_tests PRIVATE libSceGameLiveStreaming libc) + configure_windows_unwind(guest_game_live_streaming_tests) + add_test(NAME guest_game_live_streaming COMMAND guest_game_live_streaming_tests) + if(WIN32) + set_tests_properties(guest_game_live_streaming PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_netctl_ap_dialog_tests tests/GuestNetCtlApDialog.cpp) target_include_directories(guest_netctl_ap_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_netctl_ap_dialog_tests PRIVATE libSceNetCtlApDialog libc) From d977bb5bf5d9b21542d0af3307b06a080601d725 Mon Sep 17 00:00:00 2001 From: Chikimaro <49518807+Chikimaro@users.noreply.github.com> Date: Mon, 19 Oct 2026 08:51:09 +0000 Subject: [PATCH 130/176] feat(libSceNet): implement sceNetUsleep and sceNetInetPtonEx sceNetUsleep fails with EINVAL for a negative count and otherwise sleeps through libkernel's sceKernelUsleep, a cancellation point. sceNetInetPtonEx converts as sceNetInetPton does when its flags are 0 and throws for any other flags, whose meaning is unknown. The signatures follow shadPS4 and are recorded as unverified in the libSceNet technical debt. (cherry picked from commit a84a2b124089ced3d1fee58027ec68cb4d4b3cf5) --- core/libs/prx/libSceNet/Export.cpp | 13 ++++++------ core/libs/tests/GuestSceNet.cpp | 32 ++++++++++++++++++++++++++++++ docs/dev/debt/libSceNet.md | 4 ++-- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 5cd7c6b02ed5..26d1a01e5335 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -37,6 +37,7 @@ #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" +#include "prx/libkernel/Time/include/Time.hpp" // Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets. @@ -1963,9 +1964,9 @@ int APS5_VABI sceNetInitParam(void) { return 0; } -int APS5_VABI sceNetInetPtonEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags) { + if (flags != 0) throw std::runtime_error("sceNetInetPtonEx: flags " + std::to_string(flags) + " are not supported"); + return sceNetInetPton(af, src, dst); } int APS5_VABI sceNetIoctl(void) { @@ -2043,9 +2044,9 @@ int APS5_VABI sceNetThreadJoin(void) { return 0; } -int APS5_VABI sceNetUsleep(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNetUsleep(int microseconds) { + if (microseconds < 0) return fail(NET_EINVAL); + return sceKernelUsleep_nid_postfix(static_cast(microseconds)); } int APS5_VABI sceNetShowIfconfigForBuffer(void) { diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index c88cc9a3ccb3..d162f7202495 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -87,6 +87,8 @@ struct GuestPollEntry { int APS5_VABI poll_nid_postfix(GuestPollEntry*, std::uint32_t, int); int* APS5_VABI __error_nid_postfix(); int APS5_VABI sceNetInetPton(int, const char*, void*); +int APS5_VABI sceNetInetPtonEx(int, const char*, void*, int); +int APS5_VABI sceNetUsleep(int); const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); int* APS5_VABI sceNetErrnoLoc(void); } @@ -709,6 +711,34 @@ static void CheckAddressText(int family, const char* text) { Require(sceNetInetNtop(family, address.data(), nullptr, output.size()) == nullptr && *sceNetErrnoLoc() == 22); } +static void CheckInetPtonEx() { + std::array address{}; + Require(sceNetInetPtonEx(2, "192.168.1.20", address.data(), 0) == 1); + Require(address[0] == 192 && address[1] == 168 && address[2] == 1 && address[3] == 20); + std::array expected{}; + Require(sceNetInetPton(28, "fe80::1:2", expected.data()) == 1); + address.fill(0); + Require(sceNetInetPtonEx(28, "fe80::1:2", address.data(), 0) == 1 && address == expected); + Require(sceNetInetPtonEx(2, "not an address", address.data(), 0) == 0); + Require(Failed(sceNetInetPtonEx(99, "127.0.0.1", address.data(), 0), 47)); + Require(Failed(sceNetInetPtonEx(2, nullptr, address.data(), 0), 22)); + Require(Failed(sceNetInetPtonEx(2, "127.0.0.1", nullptr, 0), 22)); + for (const int flags : {1, -1}) { + bool threw = false; + try { sceNetInetPtonEx(2, "127.0.0.1", address.data(), flags); } catch (const std::runtime_error&) { threw = true; } + Require(threw); + } +} + +static void CheckUsleep() { + Require(sceNetUsleep(0) == 0); + const auto start = std::chrono::steady_clock::now(); + Require(sceNetUsleep(2000) == 0); + Require(std::chrono::steady_clock::now() - start >= std::chrono::microseconds(2000)); + *sceNetErrnoLoc() = 0; + Require(Failed(sceNetUsleep(-1), 22)); +} + static void CheckEpollIdent() { std::array address{16, 2, 0, 0, 127, 0, 0, 1}; const int receiver = sceNetSocket("epoll-ident", 2, 2, 17); @@ -1148,6 +1178,8 @@ int main(int argc, char** argv) { CheckAddressText(2, "255.255.255.255"); CheckAddressText(28, "::1"); CheckAddressText(28, "1234:5678:9abc:def0:1234:5678:9abc:def0"); + CheckInetPtonEx(); + CheckUsleep(); CheckEpollIdent(); CheckLinkLocalAllNodesIpv6(); CheckUnboundSocketName(); diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 9dce19071638..769d959f1520 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -49,7 +49,7 @@ - [sceNetGetSystemTime](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetGetSystemMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetInitParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int af, const char* src, void* dst, int flags)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp), which forwards it to `sceNetInetPton` and does not know the flags either. Flags 0 converts as `sceNetInetPton` does; any other flags throw, since their meaning (an IPv6 scope or otherwise) is not known. Unverified on hardware - [sceNetIoctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetMemoryAllocate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature @@ -65,7 +65,7 @@ - [sceNetThreadCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetThreadExit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetThreadJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature -- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int microseconds)` signature and the `EINVAL` failure for a negative count follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp); other counts sleep through libkernel's `sceKernelUsleep`, a cancellation point, and return 0. Whether the console takes the count as signed, and what it returns, is unverified on hardware - [sceNetShowIfconfigForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetShowRouteForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNetShowNetstatForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature From fd9cd0732ad2a82440e85d0d8509c1b2385e67b3 Mon Sep 17 00:00:00 2001 From: fogydes Date: Mon, 19 Oct 2026 14:32:12 +0530 Subject: [PATCH 131/176] fix(libkernel): prevent use-after-free on event flag deletion and multi-waiter race (cherry picked from commit ee7280c873d82f5e207e655027fef87ffaf197db) --- .../prx/libkernel/EventFlag/src/EventFlag.cpp | 157 ++++++++-- core/libs/tests/GuestAprRead.cpp | 267 ++++++++++++++++++ core/libs/tests/GuestKernelErrors.cpp | 110 ++++++++ docs/dev/debt/libkernel.md | 1 + 4 files changed, 511 insertions(+), 24 deletions(-) create mode 100644 core/libs/tests/GuestAprRead.cpp diff --git a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp index 4381004897e9..604d0d64c7f2 100644 --- a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp +++ b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp @@ -1,12 +1,18 @@ #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include +#include #include +#include +#include #include +#include +#include #include #include @@ -27,6 +33,15 @@ static constexpr uint32_t EVF_WAITMODE_OR = 0x02; static constexpr uint32_t EVF_WAITMODE_CLEAR_ALL = 0x10; static constexpr uint32_t EVF_WAITMODE_CLEAR_PAT = 0x20; +struct WaiterNode { + uint64_t bitPattern = 0; + uint32_t waitMode = 0; + uint64_t resultPattern = 0; + bool satisfied = false; + WaiterNode* prev = nullptr; + WaiterNode* next = nullptr; +}; + struct KernelEventFlagPrivate { std::mutex _mutex; TimedWait::Condition _changed; @@ -36,8 +51,56 @@ struct KernelEventFlagPrivate { bool _deleted = false; int _waiters = 0; uint64_t _cancelGeneration = 0; + WaiterNode* _waiterHead = nullptr; + WaiterNode* _waiterTail = nullptr; + + void pushWaiter(WaiterNode* node) { + node->prev = _waiterTail; + node->next = nullptr; + if (_waiterTail) { + _waiterTail->next = node; + } else { + _waiterHead = node; + } + _waiterTail = node; + } + + void removeWaiter(WaiterNode* node) { + if (node->prev) { + node->prev->next = node->next; + } else if (_waiterHead == node) { + _waiterHead = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } else if (_waiterTail == node) { + _waiterTail = node->prev; + } + node->prev = nullptr; + node->next = nullptr; + } }; +namespace { + +std::shared_mutex eventFlagRegistryMutex; +std::unordered_map> eventFlagRegistry; + +std::shared_ptr ResolveEventFlag(KernelEventFlag handle) { + if (!handle) return {}; + std::shared_lock lock(eventFlagRegistryMutex); + const auto it = eventFlagRegistry.find(handle); + return it == eventFlagRegistry.end() ? std::shared_ptr{} : it->second; +} + +void RetireEventFlag(KernelEventFlag handle, const std::shared_ptr& owner) { + std::unique_lock lock(eventFlagRegistryMutex); + const auto it = eventFlagRegistry.find(handle); + if (it != eventFlagRegistry.end() && it->second == owner) eventFlagRegistry.erase(it); +} + +} + static bool TraceSync() { static const bool enabled = std::getenv("APS5_TRACE_SYNC") != nullptr; return enabled; @@ -67,43 +130,63 @@ int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, ui if (!ef || !name) return SCE_KERNEL_ERROR_EINVAL; if ((attr & EVF_ATTR_SINGLE) && (attr & EVF_ATTR_MULTI)) return SCE_KERNEL_ERROR_EINVAL; if ((attr & ~0x33u) != 0) return SCE_KERNEL_ERROR_EINVAL; - auto* flag = new (std::nothrow) KernelEventFlagPrivate(); - if (!flag) return SCE_KERNEL_ERROR_ENOMEM; - flag->_name = name; - flag->_pattern = init_pattern; - flag->_multipleWaiters = (attr & EVF_ATTR_MULTI) != 0; - *ef = flag; - if (TraceSync()) std::fprintf(stderr, "[evf] create %p '%s' attr=0x%x init=0x%llx\n", static_cast(flag), name, attr, static_cast(init_pattern)); + std::shared_ptr flag; + try { + flag = std::make_shared(); + flag->_name = name; + flag->_pattern = init_pattern; + flag->_multipleWaiters = (attr & EVF_ATTR_MULTI) != 0; + std::unique_lock lock(eventFlagRegistryMutex); + eventFlagRegistry.emplace(flag.get(), flag); + } catch (const std::bad_alloc&) { + return SCE_KERNEL_ERROR_ENOMEM; + } + *ef = flag.get(); + if (TraceSync()) std::fprintf(stderr, "[evf] create %p '%s' attr=0x%x init=0x%llx\n", static_cast(flag.get()), name, attr, static_cast(init_pattern)); return SCE_OK; } int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; - { - std::lock_guard lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - ef->_deleted = true; - if (ef->_waiters != 0) { - ef->_changed.NotifyAll(); - return SCE_OK; - } - } - delete ef; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; + std::unique_lock lock(ef->_mutex); + if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; + ef->_deleted = true; + ef->_changed.NotifyAll(); + ef->_changed.Wait(lock, [&] { return ef->_waiters == 0; }); + lock.unlock(); + RetireEventFlag(ef, keepAlive); return SCE_OK; } int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, uint64_t bit_pattern) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; ef->_pattern |= bit_pattern; if (TraceSync()) std::fprintf(stderr, "[evf] set %p '%s' |=0x%llx -> 0x%llx caller=%p\n", static_cast(ef), ef->_name.c_str(), static_cast(bit_pattern), static_cast(ef->_pattern), __builtin_return_address(0)); + for (auto* waiter = ef->_waiterHead; waiter != nullptr; waiter = waiter->next) { + if (!waiter->satisfied && IsSatisfied(ef->_pattern, waiter->bitPattern, waiter->waitMode)) { + waiter->satisfied = true; + waiter->resultPattern = ef->_pattern; + if (waiter->waitMode & EVF_WAITMODE_CLEAR_ALL) { + ef->_pattern = 0; + } else if (waiter->waitMode & EVF_WAITMODE_CLEAR_PAT) { + ef->_pattern &= ~waiter->bitPattern; + } + } + } ef->_changed.NotifyAll(); return SCE_OK; } int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, uint64_t bit_pattern) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; ef->_pattern &= bit_pattern; @@ -112,6 +195,8 @@ int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, uint64_t bit_pattern) int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, uint64_t set_pattern, int* num_wait_threads) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; if (num_wait_threads) *num_wait_threads = ef->_waiters; @@ -123,6 +208,8 @@ int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, uint64_t set_pattern, int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, uint64_t bit_pattern, uint32_t wait_mode, uint64_t* result_pat) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; if (bit_pattern == 0 || !IsValidWaitMode(wait_mode)) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; @@ -137,14 +224,33 @@ int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, uint32_t wait_mode, uint64_t* result_pat, KernelUseconds* timeout) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; if (bit_pattern == 0 || !IsValidWaitMode(wait_mode)) return SCE_KERNEL_ERROR_EINVAL; std::unique_lock lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; if (!ef->_multipleWaiters && ef->_waiters != 0) return SCE_KERNEL_ERROR_EPERM; + if (IsSatisfied(ef->_pattern, bit_pattern, wait_mode)) { + Consume(ef, bit_pattern, wait_mode, result_pat); + return SCE_OK; + } const uint64_t generation = ef->_cancelGeneration; if (TraceSync()) std::fprintf(stderr, "[evf] wait %p '%s' want=0x%llx mode=0x%x have=0x%llx timeout=%d caller=%p outer=%p\n", static_cast(ef), ef->_name.c_str(), static_cast(bit_pattern), wait_mode, static_cast(ef->_pattern), timeout ? static_cast(*timeout) : -1, __builtin_return_address(0), static_cast(static_cast(__builtin_frame_address(0))[0])[1]); - const auto released = [&] { return ef->_deleted || ef->_cancelGeneration != generation || IsSatisfied(ef->_pattern, bit_pattern, wait_mode); }; ++ef->_waiters; + WaiterNode waiter; + waiter.bitPattern = bit_pattern; + waiter.waitMode = wait_mode; + ef->pushWaiter(&waiter); + struct WaiterGuard { + KernelEventFlagPrivate* flag; + WaiterNode* node; + ~WaiterGuard() { + flag->removeWaiter(node); + --flag->_waiters; + flag->_changed.NotifyAll(); + } + } waiterGuard{ef, &waiter}; + const auto released = [&] { return ef->_deleted || ef->_cancelGeneration != generation || waiter.satisfied; }; bool timedOut = false; const auto waitStart = std::chrono::steady_clock::now(); if (timeout) { @@ -155,23 +261,26 @@ int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u ef->_changed.Wait(lock, released); } KernelTraceWait_nid_postfix("evf", __builtin_return_address(0), static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart).count()), timedOut); - --ef->_waiters; if (ef->_deleted) { - const bool last = ef->_waiters == 0; - lock.unlock(); - if (last) delete ef; return SCE_KERNEL_ERROR_EACCES; } if (ef->_cancelGeneration != generation) { if (result_pat) *result_pat = ef->_pattern; return SCE_KERNEL_ERROR_ECANCELED; } - if (timedOut) { + if (timedOut && !waiter.satisfied) { if (result_pat) *result_pat = ef->_pattern; return SCE_KERNEL_ERROR_ETIMEDOUT; } - Consume(ef, bit_pattern, wait_mode, result_pat); + if (result_pat) *result_pat = waiter.resultPattern; return SCE_OK; } } + +extern "C" int APS5_Test_GetEventFlagWaiterCount(KernelEventFlag handle) { + const auto keepAlive = ResolveEventFlag(handle); + if (!keepAlive) return -1; + std::lock_guard lock(keepAlive->_mutex); + return keepAlive->_waiters; +} diff --git a/core/libs/tests/GuestAprRead.cpp b/core/libs/tests/GuestAprRead.cpp new file mode 100644 index 000000000000..d2f12108a7dd --- /dev/null +++ b/core/libs/tests/GuestAprRead.cpp @@ -0,0 +1,267 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +extern "C" { +int APS5_VABI sceKernelAprResolveFilepathsToIds(const char**, std::uint32_t, std::uint32_t*, std::uint32_t*); +int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject*, std::uint32_t); +int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); +int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMunmap(void*, std::size_t); +int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); +int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); +void* APS5_VABI fopen_nid_postfix(const char*, const char*); +int APS5_VABI fclose_nid_postfix(void*); +} + +namespace { + +constexpr std::size_t FileBytes = (16u << 20u) + 257u; +constexpr std::size_t MappingBytes = 17u << 20u; +constexpr const char* FilePath = "apr_read_generated.bin"; + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +std::uint8_t FileByte(std::uint64_t offset) { + return static_cast((offset * 37u) ^ (offset >> 8u) ^ (offset >> 16u)); +} + +class Fixture { +public: + Fixture() { + std::ofstream stream(FilePath, std::ios::binary | std::ios::trunc); + std::array chunk; + for (std::size_t offset = 0; offset < FileBytes; offset += chunk.size()) { + const auto bytes = std::min(chunk.size(), FileBytes - offset); + for (std::size_t index = 0; index < bytes; ++index) chunk[index] = static_cast(FileByte(offset + index)); + stream.write(chunk.data(), static_cast(bytes)); + } + stream.close(); + Require(!stream.fail(), "cannot create the synthetic APR file"); + const char* path = FilePath; + std::uint32_t failed = 0; + Require(sceKernelAprResolveFilepathsToIds(&path, 1, &id, &failed) == 0 && failed == 0, "cannot resolve the synthetic APR file"); + } + + ~Fixture() { + std::error_code ignored; + std::filesystem::remove(FilePath, ignored); + } + + void Read(void* destination, std::uint64_t bytes, std::uint64_t offset) const { + const Apr::ReadFileCommand command{{Apr::Opcode::ReadFile, sizeof(Apr::ReadFileCommand)}, id, 0, + reinterpret_cast(destination), bytes, offset}; + alignas(8) std::array memory{}; + std::memcpy(memory.data(), &command, sizeof(command)); + Apr::CommandBufferObject buffer{Apr::BufferType::Generic, 0, sizeof(memory), 1, sizeof(memory), memory.data()}; + Require(sceKernelAprSubmitCommandBuffer(&buffer, 0) == 0, "APR read submission failed"); + } + +private: + std::uint32_t id = 0; +}; + +class DirectBuffer { +public: + DirectBuffer() { + Require(sceKernelAllocateDirectMemory(0, 13824ll << 20, MappingBytes, 0x4000, 0, &physical) == 0, "cannot allocate direct memory"); + if (sceKernelMapDirectMemory(&pointer, MappingBytes, 0x33, 0, physical, 0x4000) != 0) { + sceKernelReleaseDirectMemory(physical, MappingBytes); + throw std::runtime_error("cannot map direct memory"); + } + } + + ~DirectBuffer() { + sceKernelMunmap(pointer, MappingBytes); + sceKernelReleaseDirectMemory(physical, MappingBytes); + } + + std::uint8_t* Data() const { return static_cast(pointer); } + +private: + void* pointer = nullptr; + std::int64_t physical = 0; +}; + +void CheckRead(const Fixture& fixture, std::uint8_t* data, std::size_t bytes, std::uint64_t offset) { + data[-1] = 0xA5; + data[bytes] = 0x5A; + fixture.Read(data, bytes, offset); + Require(data[-1] == 0xA5 && data[bytes] == 0x5A, "APR wrote outside the requested range"); + for (std::size_t index = 0; index < bytes; ++index) { + const auto expected = offset + index < FileBytes ? FileByte(offset + index) : std::uint8_t{0}; + Require(data[index] == expected, "APR returned incorrect bytes or did not zero-fill EOF"); + } +} + +void TestRead(const Fixture& fixture, std::uint8_t* data) { + CheckRead(fixture, data + 17, (16u << 20u) + 13u, 7); + for (const auto bytes : {std::size_t{0}, std::size_t{1}, std::size_t{65535}, std::size_t{65536}, std::size_t{65537}}) { + CheckRead(fixture, data + 17, bytes, 31); + } +} + +void TestEof(const Fixture& fixture) { + std::vector storage(131200, 0xCC); + auto* data = storage.data() + 17; + CheckRead(fixture, data, 131073, FileBytes - 65537); + CheckRead(fixture, data, 65537, FileBytes); + CheckRead(fixture, data, 65537, FileBytes + 19); +} + +void TestErrors(const Fixture& fixture) { + std::array data; + data.fill(0xA5); + bool rejected = false; + try { + fixture.Read(data.data(), data.size(), std::uint64_t{1} << 63u); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected, "APR accepted an unseekable offset"); + for (const auto byte : data) Require(byte == 0xA5, "failed APR read modified the destination"); +#ifdef _WIN32 + { + const DirectBuffer storage; + storage.Data()[17] = 0xA5; + Require(sceKernelMprotect(storage.Data(), MappingBytes, 0x11) == 0, "cannot protect the direct read buffer"); + rejected = false; + try { + fixture.Read(storage.Data() + 17, 65537, 0); + } catch (const std::runtime_error& error) { + rejected = std::string(error.what()).find("not writable guest memory") != std::string::npos; + } + Require(rejected && storage.Data()[17] == 0xA5, "APR did not reject a read-only guest destination"); + } +#endif + Require(std::filesystem::remove(FilePath), "cannot remove the synthetic APR file"); + rejected = false; + try { + fixture.Read(data.data(), data.size(), 0); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected, "APR accepted a missing file"); + for (const auto byte : data) Require(byte == 0xA5, "missing-file APR read modified the destination"); +} + +void TestStreams(const Fixture& fixture) { + constexpr int StreamCount = 600; + std::vector streams; + for (int index = 0; index < StreamCount; ++index) { + void* stream = fopen_nid_postfix(("apr_stream_" + std::to_string(index) + ".tmp").c_str(), "w"); + if (stream == nullptr) break; + streams.push_back(stream); + } + const bool held = streams.size() == StreamCount; + std::vector storage(131200, 0xCC); + std::string error; + try { + CheckRead(fixture, storage.data() + 17, 65537, 0); + } catch (const std::runtime_error& failure) { + error = failure.what(); + } + for (void* stream : streams) fclose_nid_postfix(stream); + for (int index = 0; index < StreamCount; ++index) std::filesystem::remove("apr_stream_" + std::to_string(index) + ".tmp"); + Require(error.empty(), ("APR read failed while " + std::to_string(streams.size()) + " guest streams were open: " + error).c_str()); + Require(held, "cannot hold 600 guest fopen streams"); +} + +#ifdef _WIN32 +std::uintptr_t faultBegin = 0; +std::size_t faultBytes = 0; +std::size_t faultPage = 0; +std::size_t committedPages = 0; + +LONG CALLBACK CommitOnWrite(EXCEPTION_POINTERS* exception) { + const auto& record = *exception->ExceptionRecord; + if (record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION || record.NumberParameters < 2 || record.ExceptionInformation[0] != 1) return EXCEPTION_CONTINUE_SEARCH; + const auto address = static_cast(record.ExceptionInformation[1]); + if (address < faultBegin || address - faultBegin >= faultBytes) return EXCEPTION_CONTINUE_SEARCH; + const auto page = address & ~(static_cast(faultPage) - 1); + if (VirtualAlloc(reinterpret_cast(page), faultPage, MEM_COMMIT, PAGE_READWRITE) == nullptr) return EXCEPTION_CONTINUE_SEARCH; + ++committedPages; + return EXCEPTION_CONTINUE_EXECUTION; +} + +class FaultBuffer { +public: + FaultBuffer() { + SYSTEM_INFO system{}; + GetSystemInfo(&system); + pointer = VirtualAlloc(nullptr, MappingBytes, MEM_RESERVE, PAGE_NOACCESS); + Require(pointer != nullptr, "cannot reserve the fault-backed read buffer"); + handler = AddVectoredExceptionHandler(1, CommitOnWrite); + if (handler == nullptr) { + VirtualFree(pointer, 0, MEM_RELEASE); + throw std::runtime_error("cannot install the synthetic page fault handler"); + } + faultPage = system.dwPageSize; + faultBytes = MappingBytes; + faultBegin = reinterpret_cast(pointer); + committedPages = 0; + } + + ~FaultBuffer() { + faultBytes = 0; + RemoveVectoredExceptionHandler(handler); + VirtualFree(pointer, 0, MEM_RELEASE); + } + + std::uint8_t* Data() const { return static_cast(pointer); } + +private: + void* pointer = nullptr; + void* handler = nullptr; +}; +#endif + +} + +int main(int argc, char** argv) { + try { + Require(argc == 2, "expected host, direct, eof, errors or fault"); + const Fixture fixture; + const std::string mode = argv[1]; + if (mode == "host") { + std::vector storage(MappingBytes, 0xCC); + TestRead(fixture, storage.data()); + } else if (mode == "direct") { + const DirectBuffer storage; + TestRead(fixture, storage.Data()); + } else if (mode == "eof") { + TestEof(fixture); + } else if (mode == "errors") { + TestErrors(fixture); + } else if (mode == "streams") { + TestStreams(fixture); +#ifdef _WIN32 + } else if (mode == "fault") { + const FaultBuffer storage; + TestRead(fixture, storage.Data()); + Require(committedPages > 2, "APR did not exercise demand-committed destination pages"); +#endif + } else { + throw std::runtime_error("unknown APR read case"); + } + std::puts("APR synthetic read checks passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestKernelErrors.cpp b/core/libs/tests/GuestKernelErrors.cpp index 44bb942f032a..b1981c7ee029 100644 --- a/core/libs/tests/GuestKernelErrors.cpp +++ b/core/libs/tests/GuestKernelErrors.cpp @@ -1,8 +1,11 @@ #include "SceTypes.hpp" +#include +#include #include #include #include #include +#include extern "C" { int APS5_VABI sceKernelCreateSema(KernelSema*, const char*, std::uint32_t, int, int, void*); @@ -36,6 +39,7 @@ int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, std::uint64_t bit_patt int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, std::uint64_t set_pattern, int* num_wait_threads); int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern, std::uint32_t wait_mode, std::uint64_t* result_pat); int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern, std::uint32_t wait_mode, std::uint64_t* result_pat, KernelUseconds* timeout); +int APS5_Test_GetEventFlagWaiterCount(KernelEventFlag handle); } static constexpr int SCE_OK = 0; @@ -45,6 +49,7 @@ static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast(0x80020009); static constexpr int SCE_KERNEL_ERROR_EDEADLK = static_cast(0x8002000B); static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); +static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast(0x8002000D); static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010); static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); @@ -64,6 +69,14 @@ static constexpr int POSIX_EDEADLK = 11; static void Require(bool value) { if (!value) std::abort(); } +static void WaitForEventFlagWaiters(KernelEventFlag handle, int expected) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (APS5_Test_GetEventFlagWaiterCount(handle) != expected) { + if (std::chrono::steady_clock::now() >= deadline) Require(false); + std::this_thread::yield(); + } +} + int main() { constexpr int maximum = std::numeric_limits::max(); KernelSema semaphore = nullptr; @@ -236,4 +249,101 @@ int main() { Require(sceKernelCreateEventFlag(&ef, "single", EVF_ATTR_SINGLE | 0x02u, 0, nullptr) == SCE_OK); Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "imm_sat", EVF_ATTR_MULTI, 0x05, nullptr) == SCE_OK); + pattern = 0; + Require(sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_PAT, &pattern, nullptr) == SCE_OK); + Require(pattern == 0x05); + Require(sceKernelPollEventFlag(ef, 0x01, EVF_WAITMODE_AND, &pattern) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelPollEventFlag(ef, 0x04, EVF_WAITMODE_AND, &pattern) == SCE_OK); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "multi_order_nonclear_first", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic a1Res{-1}; + std::atomic a2Res{-1}; + std::uint64_t a1Pat = 0; + std::uint64_t a2Pat = 0; + std::thread a1([&] { + a1Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, &a1Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 1); + std::thread a2([&] { + a2Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_PAT, &a2Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + Require(sceKernelSetEventFlag(ef, 0x01) == SCE_OK); + a1.join(); + a2.join(); + Require(a1Res == SCE_OK); + Require(a2Res == SCE_OK); + Require(a1Pat == 0x01); + Require(a2Pat == 0x01); + Require(sceKernelPollEventFlag(ef, 0x01, EVF_WAITMODE_AND, &pattern) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "multi_order_clear_first", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic b1Res{-1}; + std::atomic b2Res{-1}; + std::uint64_t b1Pat = 0; + std::uint64_t b2Pat = 0; + std::thread b1([&] { + b1Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_PAT, &b1Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 1); + std::thread b2([&] { + b2Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, &b2Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + Require(sceKernelSetEventFlag(ef, 0x01) == SCE_OK); + b1.join(); + Require(b1Res == SCE_OK); + Require(b1Pat == 0x01); + Require(APS5_Test_GetEventFlagWaiterCount(ef) == 1); + Require(b2Res == -1); + Require(sceKernelSetEventFlag(ef, 0x01) == SCE_OK); + b2.join(); + Require(b2Res == SCE_OK); + Require(b2Pat == 0x01); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "del_waiters", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic delRes1{-1}; + std::atomic delRes2{-1}; + std::thread delT1([&] { + delRes1 = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, nullptr, nullptr); + }); + std::thread delT2([&] { + delRes2 = sceKernelWaitEventFlag(ef, 0x02, EVF_WAITMODE_AND, nullptr, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + delT1.join(); + delT2.join(); + Require(delRes1 == SCE_KERNEL_ERROR_EACCES); + Require(delRes2 == SCE_KERNEL_ERROR_EACCES); + + for (int iter = 0; iter < 50; ++iter) { + KernelEventFlag raceEf = nullptr; + Require(sceKernelCreateEventFlag(&raceEf, "set_del_race", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic setResult{-1}; + std::thread concurrentSet([&] { + setResult = sceKernelSetEventFlag(raceEf, 0x01); + }); + Require(sceKernelDeleteEventFlag(raceEf) == SCE_OK); + concurrentSet.join(); + Require(setResult == SCE_OK || setResult == SCE_KERNEL_ERROR_ESRCH); + } + + for (int iter = 0; iter < 50; ++iter) { + KernelEventFlag pollEf = nullptr; + Require(sceKernelCreateEventFlag(&pollEf, "poll_del_race", EVF_ATTR_MULTI, 0x01, nullptr) == SCE_OK); + std::atomic pollResult{-1}; + std::thread concurrentPoll([&] { + std::uint64_t observed = 0; + pollResult = sceKernelPollEventFlag(pollEf, 0x01, EVF_WAITMODE_AND, &observed); + }); + Require(sceKernelDeleteEventFlag(pollEf) == SCE_OK); + concurrentPoll.join(); + Require(pollResult == SCE_OK || pollResult == SCE_KERNEL_ERROR_ESRCH); + } } diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 9bf84a83af0f..87b80d0d5b1f 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -58,6 +58,7 @@ - [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed - [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - measured on a PS5 (firmware 13.60): destroying a rwlock returns 0 whether it is free or held for reading or writing, and leaves 1 in the handle; every call on that handle then returns `EINVAL` until it is initialized again, and a zero handle is initialized on first use by the `scePthreadRwlock` calls as by the POSIX ones. A rwlock destroyed while held is not freed, so its memory leaks. Destroying a rwlock while another thread is acquiring it is not guarded - [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - before waiting, FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029) checks ownership through [`_mutex_owned`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_mutex.c#L1166-L1178): an unowned mutex returns `EPERM`, while its destroyed marker returns `EINVAL`. An untouched adaptive static initializer (slot value 1) therefore returns `EPERM`; the test covers this, an uninitialized slot, a mutex owned by another thread, and the destroyed marker for both timed and untimed sce and POSIX waits. A recursive mutex locked several times is released completely for the wait and relocked to the same depth afterwards, as FreeBSD libthr does (`_mutex_cv_unlock`, `_mutex_cv_lock`). These FreeBSD-derived behaviors remain unverified on console hardware +- [sceKernelSetEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - multi-waiter satisfaction follows fpPS4 in-order evaluation: registered waiters are evaluated in FIFO order and each satisfied waiter's clear mode is applied immediately to the pattern before evaluating subsequent waiters; this ordering is unverified on PS5 console hardware - [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed, poll copies every state without waiting); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written - [scePthreadAttr setters and getters](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) (libkernel) - measured on a PS5 (firmware 13.60): a null or uninitialized attribute returns `EINVAL`, as does a null output of the stack size, detach state and scheduling parameter getters; a policy other than 1 and 3, an inheritance other than 0 and 4, or a priority outside 256 to 767 (or a null parameter) returns `ENOTSUP` (`0x8002002D`); a detach state other than 0 and 1 and a stack size below 16384 return `EINVAL`; any affinity mask is accepted. A null attribute pointer to `scePthreadAttrInit` and a null output of `scePthreadAttrGetaffinity` crashed the payload, so they still throw. A new attribute holds a 64 KiB stack size on the console and 1 MiB here, which the host code that runs on guest threads needs - [sceKernelGetAvailableCpumask](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - returns the default thread affinity, 0x1FFF (CPUs 0 to 12), as KytyPS5 does. Titles pin threads within it (PPSA02664 passes 0x1FFB, Hades II PPSA36082 0x3 and 0x3F); whether CPU 13 is available to a title is not confirmed From 8ccd6ce7a2f6f0917213170419572aff034b9a25 Mon Sep 17 00:00:00 2001 From: Nithishvelan M Date: Fri, 16 Oct 2026 18:58:48 +0530 Subject: [PATCH 132/176] feat(libSceSysmodule): implement sceSysmoduleIsLoadedInternal (cherry picked from commit de1d73c50dcd4362d1d16c61e5e3b72a7a079873) --- core/libs/prx/libSceSysmodule/Export.cpp | 15 +++++++++++++++ core/libs/tests/GuestSysmodule.cpp | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/core/libs/prx/libSceSysmodule/Export.cpp b/core/libs/prx/libSceSysmodule/Export.cpp index e9c5f23e1bfa..d03961037602 100644 --- a/core/libs/prx/libSceSysmodule/Export.cpp +++ b/core/libs/prx/libSceSysmodule/Export.cpp @@ -274,4 +274,19 @@ int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id) { return 0; } +int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id) { + if ((id & 0x7fffffffu) == 0) { + return SCE_SYSMODULE_ERROR_INVALID_VALUE; + } + if (!findModuleName(id)) { + throw std::runtime_error(std::string("sceSysmoduleIsLoadedInternal: unknown id ") + std::to_string(id)); + } + std::lock_guard lock(gMutex); + const auto it = gLoadCount.find(id); + if (it == gLoadCount.end() || it->second < 1) { + return SCE_SYSMODULE_ERROR_UNLOADED; + } + return 0; +} + } diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index 3f30628676fb..d09901bc2269 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -23,6 +23,7 @@ int APS5_VABI sceSysmoduleLoadModule(std::uint16_t id); int APS5_VABI sceSysmoduleUnloadModule(std::uint16_t id); int APS5_VABI sceSysmoduleLoadModuleInternal(std::uint32_t id); int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id); +int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id); int APS5_VABI sceSysmoduleLoadModuleInternalWithArg(std::uint32_t id, int argc, void* argv, std::uint64_t unk, int* ret); int APS5_VABI sceSysmoduleGetModuleInfoForUnwind(std::uint64_t addr, int flags, ModuleInfoForUnwind* info); int APS5_VABI sceSysmoduleGetModuleHandleInternal(std::uint32_t id, std::int32_t* handle); @@ -182,6 +183,7 @@ int main() { Require(sceSysmoduleLoadModule(0) == kInvalidValue); Require(sceSysmoduleUnloadModule(0) == kInvalidValue); for (std::uint32_t id : {0u, kInternalFlag}) { + Require(sceSysmoduleIsLoadedInternal(id) == kInvalidValue); Require(sceSysmoduleLoadModuleInternal(id) == kInvalidValue); Require(sceSysmoduleUnloadModuleInternal(id) == kInvalidValue); int result = 0x5a5a5a5a; @@ -189,8 +191,11 @@ int main() { Require(result == 0x5a5a5a5a); } + Require(ThrowsRuntimeError([] { sceSysmoduleIsLoadedInternal(0x00ffffffu); })); + Require(sceSysmoduleIsLoadedInternal(kFiberModuleId) == kModuleUnloaded); Require(sceSysmoduleIsLoaded(kFiberModuleId) == kModuleUnloaded); Require(sceSysmoduleLoadModuleInternal(kFiberModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kFiberModuleId) == 0); Require(sceSysmoduleIsLoaded(kFiberModuleId) == 0); Require(sceSysmoduleLoadModuleInternal(kFiberModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kFiberModuleId) == 0); @@ -282,11 +287,15 @@ int main() { Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == kModuleUnloaded); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleLoadModuleInternal(kAgcModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == 0); Require(sceSysmoduleLoadModuleInternal(kAgcModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == kModuleUnloaded); #if defined(__linux__) || defined(_WIN32) CheckUnwindInfo(); From 2200656dcc23892754e0c827571ec4e10d438819 Mon Sep 17 00:00:00 2001 From: hugodearaujo Date: Fri, 16 Oct 2026 09:21:34 -0300 Subject: [PATCH 133/176] fix(libkernel): report the read bit of every write bit in a mapping's protection A PS5 (firmware 13.60) reports CPU write 0x2 with CPU read 0x1, 0x80 with 0x40 and 0x200 with 0x100 after a map, sceKernelMprotect, sceKernelMtypeprotect or POSIX mprotect; GPU write 0x20 keeps GPU read 0x10 clear. sceKernelVirtualQuery and sceKernelQueryMemoryProtection now report the protection that way. (cherry picked from commit 514b6849ecab2d799949e82b71161c8c3569200a) --- core/libs/prx/libkernel/CMakeLists.txt | 10 +++ .../libkernel/DirectMemory/DirectMemory.cpp | 9 +- core/libs/tests/GuestProtectionReport.cpp | 84 +++++++++++++++++++ docs/dev/debt/libkernel.md | 4 +- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 core/libs/tests/GuestProtectionReport.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2eff9cfb6a8f..8229477efab0 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1449,6 +1449,16 @@ if(BUILD_TESTING) set_tests_properties(guest_memory guest_mmap_fixed_anonymous PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_protection_report_tests ${LIBS_INCLUDE_DIR}/tests/GuestProtectionReport.cpp) + target_include_directories(guest_protection_report_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_protection_report_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_protection_report_tests) + add_test(NAME guest_protection_report COMMAND guest_protection_report_tests) + set_tests_properties(guest_protection_report PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_protection_report PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_flexible_memory_budget_tests ${LIBS_INCLUDE_DIR}/tests/GuestFlexibleMemoryBudget.cpp) target_include_directories(guest_flexible_memory_budget_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_flexible_memory_budget_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 533e4a623199..317e6e04a622 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -909,11 +909,18 @@ void RecordReservation(const void* addr, size_t len) { g_reservations.emplace(start, start + len); } +int ReportedProtection(int prot) { + if ((prot & 0x2) != 0) prot |= 0x1; + if ((prot & 0x80) != 0) prot |= 0x40; + if ((prot & 0x200) != 0) prot |= 0x100; + return prot; +} + void RecordProtection(const void* addr, size_t len, int prot) { const auto start = reinterpret_cast(addr); std::lock_guard lock(g_protectionLock); EraseProtections(start, start + len); - if (prot >= 0) g_protections.emplace(start, ProtectedRange{start + len, prot}); + if (prot >= 0) g_protections.emplace(start, ProtectedRange{start + len, ReportedProtection(prot)}); } void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) { diff --git a/core/libs/tests/GuestProtectionReport.cpp b/core/libs/tests/GuestProtectionReport.cpp new file mode 100644 index 000000000000..87a3d29b527e --- /dev/null +++ b/core/libs/tests/GuestProtectionReport.cpp @@ -0,0 +1,84 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; +int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); +int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); +int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); +int APS5_VABI sceKernelMunmap(std::uint64_t, std::size_t); +int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); +int APS5_VABI sceKernelQueryMemoryProtection(void*, void**, void**, int*); +} + +namespace { + +constexpr std::size_t Page = 0x4000; + +void Require(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "%s\n", what); + std::abort(); +} + +void RequireReported(void* address, int expected, const char* what) { + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(address, 0, &info, sizeof(info)) == 0, "virtual query of a mapped page failed"); + void* start = nullptr; + void* end = nullptr; + int protection = -1; + Require(sceKernelQueryMemoryProtection(address, &start, &end, &protection) == 0, "protection query of a mapped page failed"); + if (info.protection == expected && protection == expected) return; + std::fprintf(stderr, "%s: reported 0x%x (virtual query) and 0x%x (protection query), expected 0x%x\n", what, info.protection, protection, expected); + std::abort(); +} + +void ProtectedReports() { + void* mapped = nullptr; + Require(sceKernelMapFlexibleMemory(&mapped, Page, 0x2, 0) == 0 && mapped != nullptr, "write-only flexible map failed"); + RequireReported(mapped, 0x3, "a write-only flexible mapping"); + struct Case { + int requested; + int reported; + }; + constexpr Case Cases[] = { + {0x1, 0x1}, {0x2, 0x3}, {0x3, 0x3}, {0x10, 0x10}, {0x12, 0x13}, {0x20, 0x20}, {0x22, 0x23}, {0x30, 0x30}, {0x40, 0x40}, {0x80, 0xc0}, {0x82, 0xc3}, {0x100, 0x100}, {0x200, 0x300}, + {0x202, 0x303}, {0x220, 0x320}, {0x2a2, 0x3e3}, {0xf2, 0xf3}, {0x3f2, 0x3f3}, {static_cast(0xfffffff2u), 0x3f3}, + }; + for (const auto& item : Cases) { + Require(sceKernelMprotect(mapped, Page, item.requested) == 0, "sceKernelMprotect failed"); + char what[64]; + std::snprintf(what, sizeof(what), "sceKernelMprotect 0x%x", static_cast(item.requested)); + RequireReported(mapped, item.reported, what); + } + Require(mprotect_nid_postfix(mapped, Page, 0x2) == 0, "write-only mprotect failed"); + RequireReported(mapped, 0x3, "mprotect with PROT_WRITE"); + Require(sceKernelMunmap(reinterpret_cast(mapped), Page) == 0, "unmap of the flexible mapping failed"); + + std::int64_t physical = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, Page, 0, 0, &physical) == 0, "direct memory allocation failed"); + void* direct = nullptr; + Require(sceKernelMapDirectMemory(&direct, Page, 0xf2, 0, physical, 0) == 0 && direct != nullptr, "direct map with 0xf2 failed"); + RequireReported(direct, 0xf3, "a direct mapping with 0xf2"); + Require(sceKernelMprotect(direct, Page, static_cast(0xfffffff2u)) == 0, "sceKernelMprotect of the direct mapping failed"); + RequireReported(direct, 0x3f3, "a direct mapping protected with 0xfffffff2"); + Require(sceKernelMunmap(reinterpret_cast(direct), Page) == 0, "unmap of the direct mapping failed"); +} + +} + +int main() { + try { + ProtectedReports(); + } catch (const std::exception& error) { + std::fprintf(stderr, "unexpected exception: %s\n", error.what()); + return 1; + } + std::puts("reported protections include the read bit of every write bit"); + return 0; +} diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 87b80d0d5b1f..12065718c0bd 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -114,8 +114,8 @@ - [Guest thread scheduling](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - the policies and priorities that `sched_get_priority_max`, `sched_get_priority_min`, `pthread_setschedparam` and `scePthreadSetschedparam` accept, and those a created thread takes, follow a PS5 (firmware 13.60), but they are only recorded and reported: they are not applied to host threads, so a title's priority order has no effect on host scheduling - [sceKernelMprotect, sceKernelMtypeprotect](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) over a range with a gap that is not registered in the allocation tracker protect the registered parts and skip the gap, and return 0, as FreeBSD 11.4's [`vm_map_protect`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/vm/vm_map.c) does (it has no check for holes); a range whose start is unmapped (also when later pages are mapped) returns `SCE_KERNEL_ERROR_EINVAL` (`mprotect`: `EINVAL`) because an existing test pins that for an unmapped start; FreeBSD answers 0 there too (`vm_map_protect` takes the next entry), so this errno is not taken from the source and was not changed. `sceKernelMprotect` threw for a gap before, which ended the title. Every part is checked for a GPU or image pin before the first one is protected; the parts are then protected one after the other, so a host protection failure in a later part leaves the earlier parts changed (FreeBSD's `vm_map_protect` cannot fail in its second pass). The behaviour on the console is not known. None of this was measured on a console. -- [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, as FreeBSD 11's [`sys_mprotect`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/vm/vm_mmap.c#L606) does with `VM_PROT_ALL`. Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`. Not measured on a console; the map calls and `sceKernelMtypeprotect` still refuse undefined bits -- [JIT shared memory](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - `sceKernelJitCreateSharedMemory`, `sceKernelJitCreateAliasOfSharedMemory` and `sceKernelJitMapSharedMemory` take the signatures of [Orbital](https://github.com/AlexAltea/orbital)'s `tools/ida/db_types.json` (name, length, maximum protection, descriptor out; descriptor, maximum protection, descriptor out; descriptor, protection, address in and out), which ps4sdk, ps5-jar-loader and the call sites in RetroArch PPSA99169, XPSemu PPSA97358 and VLC PPSA85300 match; SharpProspero declares the alias as returning the descriptor. The object is a host anonymous file shared by its descriptors (a `memfd` on Linux, a delete-on-close temporary file mapped as an executable section on Windows), the alias is a duplicate descriptor, and every map maps the whole object, so writes through one view show through the others; a non-null address is a hint, as in `mmap` without `MAP_FIXED`. Not measured on the console: a length that is zero or not a multiple of 16 KiB and protection bits other than CPU read, write and execute are `EINVAL`; an alias or a map asking for more than the descriptor's maximum protection (write and execute counting as read) is `EACCES`, as FreeBSD 11's `vm_map_protect` treats a protection past `max_protection`; a descriptor that is not a JIT object is `EBADF`. The name is ignored, the objects do not count against the flexible memory budget, `mprotect` of a view is not limited to the maximum protection, and `mmap` of the descriptors is unsupported, as for `shm_open`. On Linux the `memfd` is created without `MFD_EXEC`, which Linux 6.3 added and older kernels reject with `EINVAL`, and relies on the protection of the mapping: the flag only sets the file's execute permission bits, and the direct memory `memfd` is mapped executable the same way; a host with `vm.memfd_noexec` set to 2 refuses the creation and the call throws +- [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, as a PS5 (firmware 13.60) drops them: Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`, which the console reports as `0xf3` and then `0x3f3`. In a payload process the console also drops the CPU execute bit (`0x4`) of flexible, direct and JIT memory, and POSIX `mprotect` with write and execute reports write only (`0x2`). A title process may keep execute, so it is kept here. The map calls and `sceKernelMtypeprotect` refuse undefined bits on the console with `EINVAL` (`0x80020016`), and the direct map calls refuse execute; here both throw for undefined bits, and the direct map calls map execute +- [JIT shared memory](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - `sceKernelJitCreateSharedMemory`, `sceKernelJitCreateAliasOfSharedMemory` and `sceKernelJitMapSharedMemory` take the signatures of [Orbital](https://github.com/AlexAltea/orbital)'s `tools/ida/db_types.json` (name, length, maximum protection, descriptor out; descriptor, maximum protection, descriptor out; descriptor, protection, address in and out), which ps4sdk, ps5-jar-loader and the call sites in RetroArch PPSA99169, XPSemu PPSA97358 and VLC PPSA85300 match; SharpProspero declares the alias as returning the descriptor. The object is a host anonymous file shared by its descriptors (a `memfd` on Linux, a delete-on-close temporary file mapped as an executable section on Windows), the alias is a duplicate descriptor, and every map maps the whole object, so writes through one view show through the others; a non-null address is a hint, as in `mmap` without `MAP_FIXED`. Not measured on the console: a length that is zero or not a multiple of 16 KiB and protection bits other than CPU read, write and execute are `EINVAL`; an alias or a map asking for more than the descriptor's maximum protection (write and execute counting as read) is `EACCES`, as FreeBSD 11's `vm_map_protect` treats a protection past `max_protection`; a descriptor that is not a JIT object is `EBADF`. The name is ignored, the objects do not count against the flexible memory budget, `mprotect` of a view is not limited to the maximum protection, and `mmap` of the descriptors maps the object as a [regular file](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp), so its views and the JIT views see each other's writes. On Linux the `memfd` is created without `MFD_EXEC`, which Linux 6.3 added and older kernels reject with `EINVAL`, and relies on the protection of the mapping: the flag only sets the file's execute permission bits, and the direct memory `memfd` is mapped executable the same way; a host with `vm.memfd_noexec` set to 2 refuses the creation and the call throws - [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters and a read event on the host stream on descriptor 0, which never triggers, as `select` never reports descriptor 0 readable; PPSA34015 registers descriptor 0 for reading in a new kqueue at start-up and does not check the result; other filters, `EV_CLEAR` on a socket or a pipe, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event takes a mark of 1 only, where the host's writability stands in for one free byte, since the host does not report the send buffer's free space; other write marks and marks below 1, which make FreeBSD's read filter always fire, throw - [EVFILT_VNODE, sceKernelAddFileEvent and sceKernelDeleteFileEvent](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) watch the host file or directory behind the descriptor and turn host changes into FreeBSD 11 vnode hints, which [`filt_vfsvnode`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L5032) adds whole when they share a bit with the requested `fflags`: a write that grows a file is `NOTE_WRITE | NOTE_EXTEND`, another write `NOTE_WRITE`, a shrinking truncate or an attribute change `NOTE_ATTRIB`, a higher link count `NOTE_LINK`, a lower one or a pending delete `NOTE_DELETE`, a new path `NOTE_RENAME`; in a directory, an entry created, removed or renamed is `NOTE_WRITE`, with `NOTE_LINK` for a subdirectory, and on Linux an entry moved from or to another directory adds `NOTE_EXTEND`; inotify queues the two halves of a rename one after the other, so a watcher woken by the first can read it alone, and a half read without the other is classified only after a `getdents64` on the watched directory, which takes the directory lock that `rename` holds until both halves are queued ([`do_renameat2`](https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L4975) around `vfs_rename` and its `fsnotify_move`, [`iterate_dir`](https://github.com/torvalds/linux/blob/v6.8/fs/readdir.c#L103)), an ordering taken from the kernel source, not from inotify(7), which promises none; a directory registered with `NOTE_EXTEND` on Windows is accepted, as `vfs_kqfilter` does not check it, but never receives it, because the directory handle cannot tell a move between directories from a removal plus an addition. FreeBSD's hints depend on the filesystem and the console's filesystems are not known: a growing `ftruncate` reports `NOTE_WRITE | NOTE_EXTEND` where `vop_setattr_post` reports `NOTE_ATTRIB`, and on Windows `utimes` on a file reports `NOTE_WRITE`. The three Windows notifications are told apart in the error, which names the `parent`, `entries` or `subdirectories` watch, the path it was given and `GetLastError`, but a `FindFirstChangeNotification` that fails still throws rather than returning an error to the guest: the EVFILT_VNODE attach path is [`vfs_kqfilter`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L4936), which returns `EINVAL`, `ENOMEM` or 0 after `fget` has turned a descriptor that is not open into `EBADF`, and consults no directory permission, so no FreeBSD errno stands for a host that refuses to notify, and what the console does on such a host is not measured. Only the `parent` watch can fail this way in practice: `entries` and `subdirectories` need the `FILE_LIST_DIRECTORY` that opening the watched directory already took. The parent handle produces no hint of its own, as `RunHostWatch` only calls `FindNextChangeNotification` on it, so a later change may leave the watch on its 50 ms poll instead of throwing; that is not done here. Windows uses `FindFirstChangeNotification` on the parent directory (and on a watched directory itself) and compares `GetFileInformationByHandleEx` snapshots at least every 50 ms, because a write through an open handle is reported only when the handle is flushed or closed, so file hints can arrive 50 ms late, and a file deleted while watched, which the POSIX delete of Windows 10 1903 moves into `$Extend\$Deleted` until its last handle closes, reports `NOTE_DELETE` without `NOTE_RENAME` and keeps its handle without a parent directory watch, as that directory cannot be watched; Linux uses inotify on the file and is not run. `NOTE_REVOKE`, `NOTE_OPEN`, `NOTE_CLOSE`, `NOTE_CLOSE_WRITE`, `NOTE_READ`, unknown bits and descriptors other than files, directories, pipes and sockets throw. `sceKernelAddFileEvent(eq, fd, watch, udata)` and `sceKernelDeleteFileEvent(eq, fd)` take the [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9d/include/orbis/libkernel.h#L35) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0/src/SharpProspero/Interop/Kernel/KernelEqueue.cs#L164) signatures; `watch` is taken as the `fflags` and the event is added with `EV_CLEAR`, neither confirmed on a title (feat(libkernel): implement kevent EV_DISABLE, EV_DISPATCH and EV_FORCEONESHOT) (fix(libkernel): follow kqueue_register for EV_FORCEONESHOT and modify flags) - [sem_timedwait](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) consumes an available count before checking the timeout and waits indefinitely for a null timeout, following FreeBSD 11's `sem_timedwait`. Console behavior for null and invalid timeouts when a count is available is unverified. From c48b9f98f2b23f4d696280e17a18f2ce4d9d1b3d Mon Sep 17 00:00:00 2001 From: al3w0f205 Date: Fri, 16 Oct 2026 09:08:06 -0500 Subject: [PATCH 134/176] feat(libSceHmd2): export missing firmware functions from database (cherry picked from commit 05bdd6aeb08ee27bba0927614916afa2afd986e3) --- core/libs/prx/libSceHmd2/Export.cpp | 160 ++++++++++++++++++++++++++++ core/libs/tests/GuestHmd2.cpp | 26 +++++ docs/dev/debt/libSceHmd2.md | 1 + 3 files changed, 187 insertions(+) diff --git a/core/libs/prx/libSceHmd2/Export.cpp b/core/libs/prx/libSceHmd2/Export.cpp index 9f1c4cb8f9cb..e95cae3110f2 100644 --- a/core/libs/prx/libSceHmd2/Export.cpp +++ b/core/libs/prx/libSceHmd2/Export.cpp @@ -161,4 +161,164 @@ int APS5_VABI sceHmd2SetVibration() { return 0; } +int APS5_VABI sceHmd2GazeGetCalibrationData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeGetCalibrationDataMaxSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionClearUserEventEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionClearUserEventStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionGetCurrentOutput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetUserEventEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetUserEventStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeActivateGazeTracking() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeGetDeviceStatus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeGetUserCalibrationStatus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeInsertRawDataWithHandle() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeSetEnabledEye() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeSetUserCalibrationData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeSetUserCalibrationPoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeStartUserCalibration() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeStopUserCalibration() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSeeThroughDraw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSeeThroughIC() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionVideoOutEq() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionWaitShader() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2SeeThroughCallbackGetPoseDataFunc() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionGetSelectedObject() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionQueryMemory() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionClear() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSet() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetEventGpuEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetEventGpuStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2RpeorjectionSetParam() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2SetFieldOfView() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2SetIpdOffset() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/tests/GuestHmd2.cpp b/core/libs/tests/GuestHmd2.cpp index 85af8b0350a7..4276c24a61a8 100644 --- a/core/libs/tests/GuestHmd2.cpp +++ b/core/libs/tests/GuestHmd2.cpp @@ -10,6 +10,11 @@ std::int32_t APS5_VABI sceHmd2Initialize(const void* param); int APS5_VABI sceHmd2Open(); } +extern "C" { +int APS5_VABI sceHmd2GazeActivateGazeTracking(); +int APS5_VABI sceHmd2ReprojectionGetCurrentOutput(); +int APS5_VABI sceHmd2SetFieldOfView(); +} namespace { void Require(bool condition, const char* message) { @@ -32,6 +37,27 @@ int main() { threw = true; } Require(threw, "open after failed initialization did not throw"); + threw = false; + try { + sceHmd2GazeActivateGazeTracking(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "gaze activation did not throw"); + threw = false; + try { + sceHmd2ReprojectionGetCurrentOutput(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "reprojection get current output did not throw"); + threw = false; + try { + sceHmd2SetFieldOfView(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "set field of view did not throw"); std::puts("HMD2 tests passed"); return 0; } diff --git a/docs/dev/debt/libSceHmd2.md b/docs/dev/debt/libSceHmd2.md index 9b27109ad467..04db13ece297 100644 --- a/docs/dev/debt/libSceHmd2.md +++ b/docs/dev/debt/libSceHmd2.md @@ -11,6 +11,7 @@ - [sceHmd2Initialize](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the error the console returns when the PS VR2 runtime is unavailable is unknown; it returns libSceHmd's `UNSUPPORTED_FEATURE` (0x81110016). PPSA01317 passes a pointer to 16 zeroed bytes and only tests the result for non-zero - PPSA01317 imports declared without parameters, signatures unknown: the remaining [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) and [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) exports. Names from the aerolib NID list - PPSA15878 imports declared without parameters, signatures unknown: the [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reprojection and gaze exports, `sceVrTracker2Finalize`, `sceVrTracker2GetPlayAreaOrientedBoundingBox` and `sceVrTracker2LocateCoordinateSystem` in [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp), and the `scePadVrController` trigger effect exports in [libScePad](../../../core/libs/prx/libScePad/Export.cpp). Names from the aerolib NID list +- [libSceHmd2 gaze and reprojection exports](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - `sceHmd2GazeGetCalibrationData`, `sceHmd2GazeGetCalibrationDataMaxSize`, `sceHmd2ReprojectionClearUserEventEnd`, `sceHmd2ReprojectionClearUserEventStart`, `sceHmd2ReprojectionGetCurrentOutput`, `sceHmd2ReprojectionSetUserEventEnd`, `sceHmd2ReprojectionSetUserEventStart`, `sceHmd2GazeActivateGazeTracking`, `sceHmd2GazeGetDeviceStatus`, `sceHmd2GazeGetUserCalibrationStatus`, `sceHmd2GazeInsertRawDataWithHandle`, `sceHmd2GazeSetEnabledEye`, `sceHmd2GazeSetUserCalibrationData`, `sceHmd2GazeSetUserCalibrationPoint`, `sceHmd2GazeStartUserCalibration`, `sceHmd2GazeStopUserCalibration`, `sceHmd2ReprojectionSeeThroughDraw`, `sceHmd2ReprojectionSeeThroughIC`, `sceHmd2ReprojectionVideoOutEq`, `sceHmd2ReprojectionWaitShader`, `sceHmd2SeeThroughCallbackGetPoseDataFunc`, `sceHmd2GazeObjectSelectionGetSelectedObject`, `sceHmd2GazeObjectSelectionInitialize`, `sceHmd2GazeObjectSelectionQueryMemory`, `sceHmd2GazeObjectSelectionTerminate`, `sceHmd2ReprojectionClear`, `sceHmd2ReprojectionSet`, `sceHmd2ReprojectionSetEventGpuEnd`, `sceHmd2ReprojectionSetEventGpuStart`, `sceHmd2RpeorjectionSetParam`, `sceHmd2SetFieldOfView`, `sceHmd2SetIpdOffset` are declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware and every call throws ### Functional From 6207305d05e02bc9ceca66f7becfe6a489a5630d Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 18:15:59 +0400 Subject: [PATCH 135/176] fix(libc): keep errno when strtol converts nothing on macOS macOS's strtoll and strtoull set errno to EINVAL when the subject has no digits, as in "", "words" or " +". The guest's integer conversions pass that to the title, while guest_atoi, guest_wide_conversion and guest_math expect errno unchanged, as glibc leaves it. So atoi("") returned 0 with errno 22 on macOS, and all three tests failed there. A ParseInteger helper in SonyCrt.hpp calls the host's conversion and, on macOS, restores the caller's errno when nothing was converted and the base is valid. An overflow still sets ERANGE, and an invalid base still sets EINVAL. strtol, strtoul, strtoll, strtoull, atoi, atol, atoll, _Stoull and the wcsto* integer conversions in Strings.cpp use it. On other hosts the helper calls the conversion unchanged. (cherry picked from commit 717f318e2ec721d970e16e83259e9c9b3e168575) --- core/libs/prx/libc/include/SonyCrt.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index dee6a42018b9..4cccc3a1609e 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -59,6 +59,20 @@ inline SonyConstraintHandler& SonyConstraintHandlerSlot() { namespace { +template +inline TInteger ParseInteger(const char* text, char** end, int base, TInteger (*parse)(const char*, char**, int)) { +#ifdef __APPLE__ + const int saved = errno; + char* parsedEnd = nullptr; + const TInteger value = parse(text, &parsedEnd, base); + if (parsedEnd == text && errno == EINVAL && (base == 0 || (base >= 2 && base <= 36))) errno = saved; + if (end != nullptr) *end = parsedEnd; + return value; +#else + return parse(text, end, base); +#endif +} + inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int error) { SonyConstraintHandler handler = nullptr; { From 07ca6c6a3c76f5231eee62e1361607ef6252eefc Mon Sep 17 00:00:00 2001 From: Nithishvelan M Date: Fri, 16 Oct 2026 18:53:07 +0530 Subject: [PATCH 136/176] feat(libSceRtc): implement component getters and setters with range validation (cherry picked from commit 160941f81271c98c0bfbdfd49a423a6b567683d1) --- docs/dev/debt/libSceRtc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index c9eda0419c0d..4e838eb494ae 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -10,3 +10,4 @@ - [sceRtcGetCurrentRawNetworkTick, sceRtcGetCurrentAdNetworkTick, sceRtcGetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clocks 0x13, 0x12 and 0x11 (the ad and debug clocks fall back to the network clock 0x10) and returns the clock time plus the Unix epoch in ticks, or `0x80B50001` when the clock is unsupported (errno 5). Those console clocks are not modelled; all three return the system clock like `sceRtcGetCurrentNetworkTick`. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console - [sceRtcSetCurrentTick, sceRtcSetCurrentNetworkTick, sceRtcSetCurrentAdNetworkTick, sceRtcSetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console rejects a null pointer with `0x80B50002` and a tick before the Unix epoch with `0x80B50003`, then sets the system clock (clock 0, 0x10, 0x12 or 0x11) from the tick. A valid tick throws: the host clock is not set. sceRtcSetConf passes its third and fourth arguments to the same clock call with clock 0 and throws; what the pair selects is unknown - [sceRtcFormatRFC3339Precise, sceRtcFormatRFC3339PreciseLocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - read from the disassembly of libSceRtc.sprx on firmware 13.60: a null tick means the current tick, a precision above 6 or a zone outside -1439 to 1439 minutes returns `0x80B50003`, and the fraction is the microseconds truncated to the requested number of digits. Not compared with console output. The output buffer size is unknown; at most 33 bytes are written +- [sceRtcGetYear, sceRtcGetMonth, sceRtcGetDay, sceRtcGetHour, sceRtcGetMinute, sceRtcGetSecond, sceRtcGetMicrosecond, sceRtcSetYear, sceRtcSetMonth, sceRtcSetDay, sceRtcSetHour, sceRtcSetMinute, sceRtcSetSecond, sceRtcSetMicrosecond](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - inferred component behavior without confirmed hardware trace. Component setters validate individual boundaries (e.g. day 1..31) independently without cross-field calendar validation; getters return `0x80B50002` (`SCE_RTC_ERROR_INVALID_POINTER`) for a null pointer From 1f94d0820a7a60baf52a8fe495a256ce32dab23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 19 Oct 2026 12:33:33 +0200 Subject: [PATCH 137/176] fix(libkernel): pass the descriptor to Load in directory SEEK_END (cherry picked from commit 7d57845016ad63b59f91388320fb3d824a73e24a) --- core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp index 4979c63c067f..b1a9ec0b2a4c 100644 --- a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp +++ b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp @@ -114,7 +114,7 @@ std::optional SeekDirectoryDescriptor(int fd, std::int64_t offset, errno = EINVAL; return std::int64_t{-1}; } - if (whence == SeekEnd) Load(state); + if (whence == SeekEnd) Load(state, fd); const auto cursor = static_cast(whence == SeekEnd ? state.entries.size() : state.cursor); if (whence == SeekCurrent && offset == 0) return cursor; if ((whence == SeekCurrent || whence == SeekEnd) && offset > std::numeric_limits::max() - cursor) { From ea3f0b8c7c25c83f25c89aa09501067e6087ce9e Mon Sep 17 00:00:00 2001 From: Hussein Kadhim Date: Tue, 13 Oct 2026 14:32:43 +0300 Subject: [PATCH 138/176] fix(libc): apply the guest TZ to tzset, localtime, mktime and strftime Guest setenv, putenv and unsetenv only change the guest environment, while the time conversions read the host TZ, so a guest that set TZ and called tzset still got host local time. Copy a changed guest TZ to the host before each local conversion and call tzset again. (cherry picked from commit 7d1140d962e2a0cef53b1afc2e7bf6dd84334d00) --- core/libs/prx/libc/src/Time.cpp | 50 +++++++++++++++++++++++++ core/libs/tests/GuestMktime.cpp | 7 +--- core/libs/tests/GuestTimeConversion.cpp | 41 ++++++++++++++------ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 76a6d1ba79a3..808152ece720 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,52 @@ const bool timeZoneFixed = [] { #endif extern "C" int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr); +extern "C" char* APS5_VABI getenv_nid_postfix(const char* name); + +namespace { + +bool setHostZone(const char* zone) { +#ifdef _WIN32 + return _putenv_s("TZ", zone == nullptr ? "" : zone) == 0; +#else + return zone == nullptr ? ::unsetenv("TZ") == 0 : ::setenv("TZ", zone, 1) == 0; +#endif +} + +const char* hostZoneFor(const char* guest) { +#ifdef _WIN32 + return guest != nullptr && guest[0] == 0 ? "UTC0" : guest; +#else + return guest == nullptr ? ":/etc/localtime" : guest; +#endif +} + +void applyGuestZone() { + static std::mutex mutex; + static bool initialized = false; + static bool present = false; + static std::string current; + const std::lock_guard lock(mutex); + if (!initialized) { + const char* host = std::getenv("TZ"); + present = host != nullptr; + if (present) current = host; + initialized = true; + } + const char* guest = getenv_nid_postfix("TZ"); + if ((guest != nullptr) == present && (guest == nullptr || current == guest)) return; + if (!setHostZone(hostZoneFor(guest))) throw std::runtime_error("tzset: could not apply the guest TZ"); + present = guest != nullptr; + if (present) current = guest; + else current.clear(); +#ifdef _WIN32 + _tzset(); +#else + ::tzset(); +#endif +} + +} // namespace namespace { @@ -408,6 +455,7 @@ std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const ch int APS5_VABI ___mb_cur_max_nid_postfix(); void APS5_VABI tzset_nid_postfix(void) { + applyGuestZone(); char** names = HostZoneNames(); tzname_nid_postfix[0] = names[0]; tzname_nid_postfix[1] = names[1]; @@ -475,6 +523,7 @@ GuestTm* APS5_VABI localtime_nid_postfix(const int64_t* timer) { GuestTm* APS5_VABI localtime_s_nid_postfix(const int64_t* timer, GuestTm* result) { if (timer == nullptr || result == nullptr) return nullptr; + applyGuestZone(); const std::time_t t = static_cast(*timer); std::tm host{}; const int callerError = errno; @@ -516,6 +565,7 @@ GuestTm* APS5_VABI gmtime_nid_postfix(const int64_t* timer) { } int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr) { + applyGuestZone(); std::tm host = toHostTm(*timeptr); host.tm_wday = -1; const std::time_t t = std::mktime(&host); diff --git a/core/libs/tests/GuestMktime.cpp b/core/libs/tests/GuestMktime.cpp index 0a81573f9eab..06dbfcac6f6f 100644 --- a/core/libs/tests/GuestMktime.cpp +++ b/core/libs/tests/GuestMktime.cpp @@ -13,6 +13,7 @@ std::int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm*); std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*); GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); void APS5_VABI tzset_nid_postfix(); +int APS5_VABI setenv_nid_postfix(const char*, const char*, int); } using Converter = std::int64_t (APS5_VABI *)(GuestTm*); @@ -77,11 +78,7 @@ static void CheckFailure(Converter convert) { } static void CheckZone(const char* zone) { -#ifdef _WIN32 - Require(_putenv_s("TZ", zone) == 0, "Could not set the time zone"); -#else - Require(setenv("TZ", zone, 1) == 0, "Could not set the time zone"); -#endif + Require(setenv_nid_postfix("TZ", zone, 1) == 0, "Could not set the time zone"); tzset_nid_postfix(); constexpr std::array converters{libc_mktime_nid_postfix, mktime_nid_postfix}; constexpr std::array timers{-86401, -2, -1, 0, 1, 59, 60, 86400}; diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index aff8d5917604..f4570fb37b28 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -26,6 +26,7 @@ void APS5_VABI tzset_nid_postfix(void); extern char* tzname_nid_postfix[2]; char* APS5_VABI ctime_nid_postfix(const std::int64_t*); int* APS5_VABI __error_nid_postfix(); +int APS5_VABI setenv_nid_postfix(const char*, const char*, int); double APS5_VABI libc_difftime_nid_postfix(std::int64_t, std::int64_t); double APS5_VABI difftime_nid_postfix(std::int64_t, std::int64_t); } @@ -206,14 +207,33 @@ static void CheckFreeBsdConversions() { Require(strftime_nid_postfix(tooSmall, sizeof(tooSmall), "%k:%l", &guest) == 0, "strftime accepted an undersized buffer"); } +static void CheckGuestZone() { + const std::int64_t moment = 172800; + GuestTm result{}; + Require(setenv_nid_postfix("TZ", "UTC-5", 1) == 0, "Could not set the guest time zone"); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 5 && result.tm_gmtoff == 18000, + "localtime ignored a guest TZ change made without tzset"); + GuestTm calendar{}; + calendar.tm_year = 70; + calendar.tm_mday = 3; + calendar.tm_hour = 5; + calendar.tm_isdst = -1; + Require(mktime_nid_postfix(&calendar) == moment, "mktime ignored the guest TZ"); + Require(setenv_nid_postfix("TZ", "UTC+3", 1) == 0, "Could not change the guest time zone"); + tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 21 && result.tm_mday == 2 && result.tm_gmtoff == -10800, + "tzset ignored the guest TZ"); + Require(setenv_nid_postfix("TZ", "", 1) == 0, "Could not empty the guest time zone"); + tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 0 && result.tm_mday == 3 && result.tm_gmtoff == 0, + "An empty guest TZ was not treated as UTC"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); +} + int main() { -#ifdef _WIN32 - _putenv_s("TZ", "UTC-2"); - _tzset(); -#else - setenv("TZ", "UTC-2", 1); - tzset(); -#endif + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not set the guest time zone"); + tzset_nid_postfix(); constexpr std::array utcCases{{ {0, 70, 0, 1, 0, 0, 0, 4, 0}, {-1, 69, 11, 31, 23, 59, 59, 3, 364}, @@ -287,11 +307,8 @@ int main() { CheckConcurrent(gmtime_nid_postfix, gmtime_s_nid_postfix, "Concurrent gmtime returned another thread's date"); CheckConcurrent(libc_localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent libc_localtime returned another thread's date"); CheckConcurrent(localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent localtime returned another thread's date"); -#ifdef _WIN32 - _putenv_s("TZ", "UTC-5"); -#else - setenv("TZ", "UTC-5", 1); -#endif + CheckGuestZone(); + Require(setenv_nid_postfix("TZ", "UTC-5", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); GuestTm shifted{}; Require(localtime_s_nid_postfix(&epoch, &shifted) == &shifted && shifted.tm_hour == 5 && shifted.tm_gmtoff == 18000, diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index bf7da58a3387..ee0ac97d6f1e 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -801,3 +801,4 @@ Throughout the project, every function at every stage either **does exactly what - [NGS2 render buffer capacity](../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors. +- Guest `TZ` changes reach libc `tzset`, `localtime`, `localtime_s`, `ctime`, `mktime` and `strftime` by rewriting the host process `TZ`, so they also shift every other host consumer of `TZ` (for example libSceRtc local time) once a guest changes it. The zone grammar is the host C runtime's, not FreeBSD's: Windows accepts only the `std offset [dst]` form and treats an empty guest `TZ` as `UTC0`, and zone file names such as `:Europe/Berlin` are honoured on Linux only. From ed3c49d85a39fb99518abc271451d63dcf01351d Mon Sep 17 00:00:00 2001 From: Hussein Kadhim Date: Tue, 13 Oct 2026 18:00:03 +0300 Subject: [PATCH 139/176] fix(libc): serialise guest TZ changes with time conversions Copy the guest TZ under the guest environment lock so a concurrent setenv cannot free it while it is read, and hold one lock across the zone switch and the conversion so the fields of two zones are never mixed. Document the host rejection, Windows zone name and stale tm_zone limits. (cherry picked from commit 6b03cf445f8ba129b9543d927c3ac6c98ac7b67a) --- core/libs/prx/libc/include/General.hpp | 3 ++ core/libs/prx/libc/src/Environment.cpp | 12 ++++++ core/libs/prx/libc/src/Time.cpp | 35 ++++++++++------ core/libs/tests/GuestTimeConversion.cpp | 56 ++++++++++++++++++++++++- docs/dev/TechnicalDebt.md | 4 +- 5 files changed, 94 insertions(+), 16 deletions(-) diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 9e8f1124a8e7..b6b5e3854c6a 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBC_INCLUDE_GENERAL_HPP #include +#include #include #include @@ -17,6 +18,8 @@ extern "C" int KeepHostStandardError_nid_no_patch(); extern "C" int HostStandardError_nid_no_patch(); #endif +extern "C" bool CopyGuestEnvironment_nid_no_patch(const char* name, std::string& value); + extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); extern "C" std::filesystem::path ResolvePathOrRelinkedModule_nid_no_patch(const char* path); extern "C" std::filesystem::path RelinkedModulePath_nid_no_patch(const std::filesystem::path& path); diff --git a/core/libs/prx/libc/src/Environment.cpp b/core/libs/prx/libc/src/Environment.cpp index 03996644bf0e..31abdc857bad 100644 --- a/core/libs/prx/libc/src/Environment.cpp +++ b/core/libs/prx/libc/src/Environment.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #ifndef _WIN32 @@ -49,6 +50,17 @@ int EnvironmentError(int error) { errno = error; return -1; } } extern "C" { +bool CopyGuestEnvironment_nid_no_patch(const char* name, std::string& value) { + if (!ValidEnvironmentName(name)) return false; + std::lock_guard lock(environmentMutex); + for (auto& entry : Environment()) + if (entry.Matches(name)) { + value = entry.Data() + std::strlen(name) + 1; + return true; + } + return false; +} + char* APS5_VABI getenv_nid_postfix(const char* name) { if (!ValidEnvironmentName(name)) { errno = 22; return nullptr; } try { diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 808152ece720..02313df33470 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -113,7 +113,6 @@ const bool timeZoneFixed = [] { #endif extern "C" int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr); -extern "C" char* APS5_VABI getenv_nid_postfix(const char* name); namespace { @@ -133,24 +132,27 @@ const char* hostZoneFor(const char* guest) { #endif } +std::recursive_mutex& zoneMutex() { + static std::recursive_mutex mutex; + return mutex; +} + void applyGuestZone() { - static std::mutex mutex; static bool initialized = false; static bool present = false; static std::string current; - const std::lock_guard lock(mutex); if (!initialized) { const char* host = std::getenv("TZ"); present = host != nullptr; if (present) current = host; initialized = true; } - const char* guest = getenv_nid_postfix("TZ"); - if ((guest != nullptr) == present && (guest == nullptr || current == guest)) return; - if (!setHostZone(hostZoneFor(guest))) throw std::runtime_error("tzset: could not apply the guest TZ"); - present = guest != nullptr; - if (present) current = guest; - else current.clear(); + std::string guest; + const bool guestPresent = CopyGuestEnvironment_nid_no_patch("TZ", guest); + if (guestPresent == present && (!guestPresent || current == guest)) return; + if (!setHostZone(hostZoneFor(guestPresent ? guest.c_str() : nullptr))) throw std::runtime_error("tzset: the host rejected the guest TZ"); + present = guestPresent; + current = guest; #ifdef _WIN32 _tzset(); #else @@ -158,6 +160,14 @@ void applyGuestZone() { #endif } +class ZoneScope { +public: + ZoneScope() : lock(zoneMutex()) { applyGuestZone(); } + +private: + std::lock_guard lock; +}; + } // namespace namespace { @@ -455,7 +465,7 @@ std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const ch int APS5_VABI ___mb_cur_max_nid_postfix(); void APS5_VABI tzset_nid_postfix(void) { - applyGuestZone(); + const ZoneScope zone; char** names = HostZoneNames(); tzname_nid_postfix[0] = names[0]; tzname_nid_postfix[1] = names[1]; @@ -523,7 +533,7 @@ GuestTm* APS5_VABI localtime_nid_postfix(const int64_t* timer) { GuestTm* APS5_VABI localtime_s_nid_postfix(const int64_t* timer, GuestTm* result) { if (timer == nullptr || result == nullptr) return nullptr; - applyGuestZone(); + const ZoneScope zone; const std::time_t t = static_cast(*timer); std::tm host{}; const int callerError = errno; @@ -565,7 +575,7 @@ GuestTm* APS5_VABI gmtime_nid_postfix(const int64_t* timer) { } int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr) { - applyGuestZone(); + const ZoneScope zone; std::tm host = toHostTm(*timeptr); host.tm_wday = -1; const std::time_t t = std::mktime(&host); @@ -580,6 +590,7 @@ int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr) { } size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr) { + const ZoneScope zone; tzset_nid_postfix(); return formatGuestTime(str, count, format, *timeptr); } diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index f4570fb37b28..5e33eae3a668 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include extern "C" { @@ -58,7 +60,7 @@ static void CheckConcurrent(Converter convert, BufferedConverter buffered, const for (std::size_t index = 0; index < workers.size(); ++index) { workers[index] = std::thread([&, index] { start.arrive_and_wait(); - for (int iteration = 0; iteration < 50000; ++iteration) { + for (int iteration = 0; iteration < 10000; ++iteration) { const auto* result = convert(&timers[index]); if (result == nullptr || !Equal(*result, expected[index])) { matches = false; @@ -231,6 +233,54 @@ static void CheckGuestZone() { tzset_nid_postfix(); } +static void CheckZoneRace() { + const std::int64_t moment = 172800; + std::atomic stop{false}; + std::atomic torn{0}; + std::thread flipper([&] { + for (bool west = false; !stop.load(); west = !west) { + setenv_nid_postfix("TZ", west ? "UTC-5" : "UTC-2", 1); + std::this_thread::yield(); + } + }); + std::array readers; + for (auto& reader : readers) { + reader = std::thread([&] { + for (int iteration = 0; iteration < 10000; ++iteration) { + GuestTm result{}; + if (localtime_s_nid_postfix(&moment, &result) != &result || result.tm_mday != 3 || result.tm_hour * 3600 != result.tm_gmtoff) { + ++torn; + } + } + }); + } + for (auto& reader : readers) reader.join(); + stop = true; + flipper.join(); + Require(torn == 0, "localtime mixed the fields of two time zones while TZ changed"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); +} + +#ifdef _WIN32 +static void CheckRejectedZone() { + const std::int64_t moment = 172800; + const std::string oversized = "UTC-5" + std::string(40000, 'x'); + Require(setenv_nid_postfix("TZ", oversized.c_str(), 1) == 0, "The guest environment rejected a long TZ"); + GuestTm result{}; + bool thrown = false; + try { + localtime_s_nid_postfix(&moment, &result); + } catch (const std::runtime_error&) { + thrown = true; + } + Require(thrown, "localtime accepted a TZ the host cannot hold"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_gmtoff == 7200, "localtime did not recover after a rejected TZ"); +} +#endif + int main() { Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); @@ -308,6 +358,10 @@ int main() { CheckConcurrent(libc_localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent libc_localtime returned another thread's date"); CheckConcurrent(localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent localtime returned another thread's date"); CheckGuestZone(); + CheckZoneRace(); +#ifdef _WIN32 + CheckRejectedZone(); +#endif Require(setenv_nid_postfix("TZ", "UTC-5", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); GuestTm shifted{}; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index ee0ac97d6f1e..d73506d651b6 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -799,6 +799,4 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelVirtualQuery](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) returns [VirtualQueryInfo](../../core/libs/SceTypes.hpp) with a one-byte flag allocation unit so Windows and SysV hosts return the same 72-byte representation, with the name at byte 33 and GPU mask at byte 65. This preserves the existing SysV representation; the console offsets beyond the address, protection and size fields observed in Astro Bot's AGC calls are not hardware-measured. Size and offset assertions plus a raw-byte named-mapping regression guard the host ABI. - [kqueue and kevent](../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus the timer, user and [vnode](../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters; other filters, `NOTE_LOWAT`, `EV_CLEAR` on a socket, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop - [NGS2 render buffer capacity](../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors. - - -- Guest `TZ` changes reach libc `tzset`, `localtime`, `localtime_s`, `ctime`, `mktime` and `strftime` by rewriting the host process `TZ`, so they also shift every other host consumer of `TZ` (for example libSceRtc local time) once a guest changes it. The zone grammar is the host C runtime's, not FreeBSD's: Windows accepts only the `std offset [dst]` form and treats an empty guest `TZ` as `UTC0`, and zone file names such as `:Europe/Berlin` are honoured on Linux only. +- Guest `TZ` changes reach libc `tzset`, `localtime`, `localtime_s`, `ctime`, `mktime` and `strftime` by rewriting the host process `TZ`, so they also shift every other host consumer of `TZ` (for example libSceRtc local time) once a guest changes it. The zone switch and the conversion run under one lock, but host code outside libc does not take it. The zone grammar is the host C runtime's, not FreeBSD's: Windows accepts only the `std offset [dst]` form and treats an empty guest `TZ` as `UTC0`, a `:Europe/Berlin` style name is read as a zone abbreviation there and yields a wrong zone name (for example `rop`), and zone file names are honoured on Linux only. A guest `TZ` that the guest environment accepts but the host rejects (on Windows, longer than the host environment limit) makes the time functions throw `std::runtime_error`. `tm_zone` of a `tm` computed before a `TZ` change can point at storage that now holds the new zone name, so its `%Z` output is stale or changed. From 909913b7d1a24e264aabd65dbeb2edf3e660ad65 Mon Sep 17 00:00:00 2001 From: Hussein Kadhim Date: Tue, 13 Oct 2026 18:49:14 +0300 Subject: [PATCH 140/176] fix(libc): read the guest TZ once per outermost time conversion Nested conversions such as the one closing mktime no longer re-read TZ, so the instant and the normalised fields come from one zone. The race test now covers mktime and strftime. (cherry picked from commit b49644c83d113b7d6445f5690724e9d3985d518f) --- core/libs/prx/libc/src/Time.cpp | 15 +++++++++++- core/libs/tests/GuestTimeConversion.cpp | 32 +++++++++++++++++-------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 02313df33470..a60b91ab9351 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -160,9 +160,22 @@ void applyGuestZone() { #endif } +int& zoneDepth() { + static thread_local int depth = 0; + return depth; +} + class ZoneScope { public: - ZoneScope() : lock(zoneMutex()) { applyGuestZone(); } + ZoneScope() : lock(zoneMutex()) { + if (zoneDepth() == 0) applyGuestZone(); + ++zoneDepth(); + } + + ~ZoneScope() { --zoneDepth(); } + + ZoneScope(const ZoneScope&) = delete; + ZoneScope& operator=(const ZoneScope&) = delete; private: std::lock_guard lock; diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index 5e33eae3a668..c54cacec418d 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -233,31 +233,43 @@ static void CheckGuestZone() { tzset_nid_postfix(); } +static bool ZoneFieldsConsistent(const GuestTm& time, std::int64_t instant, int hour) { + const std::int64_t localSeconds = ((instant + time.tm_gmtoff) % 86400 + 86400) % 86400; + if (time.tm_hour * 3600 + time.tm_min * 60 + time.tm_sec != localSeconds || time.tm_hour != hour) return false; + char expected[16]; + const std::int64_t offset = time.tm_gmtoff < 0 ? -time.tm_gmtoff : time.tm_gmtoff; + std::snprintf(expected, sizeof(expected), "%02d %c%04d", hour, time.tm_gmtoff < 0 ? '-' : '+', static_cast(offset / 3600 * 100)); + char formatted[16]{}; + return strftime_nid_postfix(formatted, sizeof(formatted), "%H %z", &time) == 8 && std::strcmp(formatted, expected) == 0; +} + static void CheckZoneRace() { const std::int64_t moment = 172800; std::atomic stop{false}; std::atomic torn{0}; std::thread flipper([&] { - for (bool west = false; !stop.load(); west = !west) { - setenv_nid_postfix("TZ", west ? "UTC-5" : "UTC-2", 1); - std::this_thread::yield(); - } + for (bool west = false; !stop.load(); west = !west) setenv_nid_postfix("TZ", west ? "UTC-5" : "UTC+3", 1); }); - std::array readers; + std::array readers; for (auto& reader : readers) { reader = std::thread([&] { - for (int iteration = 0; iteration < 10000; ++iteration) { + for (int iteration = 0; iteration < 2500; ++iteration) { GuestTm result{}; - if (localtime_s_nid_postfix(&moment, &result) != &result || result.tm_mday != 3 || result.tm_hour * 3600 != result.tm_gmtoff) { - ++torn; - } + if (localtime_s_nid_postfix(&moment, &result) != &result || !ZoneFieldsConsistent(result, moment, result.tm_hour)) ++torn; + GuestTm calendar{}; + calendar.tm_year = 70; + calendar.tm_mday = 3; + calendar.tm_hour = 12; + calendar.tm_isdst = -1; + const std::int64_t instant = mktime_nid_postfix(&calendar); + if (instant == -1 || calendar.tm_mday != 3 || !ZoneFieldsConsistent(calendar, instant, 12)) ++torn; } }); } for (auto& reader : readers) reader.join(); stop = true; flipper.join(); - Require(torn == 0, "localtime mixed the fields of two time zones while TZ changed"); + Require(torn == 0, "A time conversion mixed the fields of two time zones while TZ changed"); Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); } From 094e5bcb7556710061d9e574e501069193666f70 Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Fri, 16 Oct 2026 18:02:09 +0000 Subject: [PATCH 141/176] fix(libc): refresh tzname after implicit TZ changes (cherry picked from commit 783c70de5355f6b64777cf78fcfb80e9c04149c5) --- core/libs/prx/libc/src/Time.cpp | 7 +++++++ core/libs/tests/GuestTimeConversion.cpp | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index a60b91ab9351..aa6a1678c2b0 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -19,6 +19,8 @@ #include "prx/libc/include/GuestTime.hpp" #include "prx/libc/include/SonyCrt.hpp" +extern "C" char* tzname_nid_postfix[2]; + namespace { constexpr const char* WEEKDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; @@ -137,6 +139,8 @@ std::recursive_mutex& zoneMutex() { return mutex; } +char** HostZoneNames(); + void applyGuestZone() { static bool initialized = false; static bool present = false; @@ -158,6 +162,9 @@ void applyGuestZone() { #else ::tzset(); #endif + char** names = HostZoneNames(); + tzname_nid_postfix[0] = names[0]; + tzname_nid_postfix[1] = names[1]; } int& zoneDepth() { diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index c54cacec418d..400e21572a16 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -231,6 +231,12 @@ static void CheckGuestZone() { "An empty guest TZ was not treated as UTC"); Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); tzset_nid_postfix(); + Require(setenv_nid_postfix("TZ", "EST5", 1) == 0, "Could not set the guest time zone without tzset"); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 19 && result.tm_gmtoff == -18000, + "localtime ignored a guest TZ change made without tzset"); + Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0, "Implicit guest TZ update did not refresh tzname"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); } static bool ZoneFieldsConsistent(const GuestTm& time, std::int64_t instant, int hour) { From 5e4b2ac9b92d374599d51e44af24ba4c9d27548c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 19 Oct 2026 13:04:13 +0200 Subject: [PATCH 142/176] test(libc): set the zone through the guest environment in tzname and region-zone checks (cherry picked from commit bd7453d97a84c5f706fa05327abf9a1368fe6dd4) --- core/libs/tests/GuestTimeConversion.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index 400e21572a16..b05b603ae2ba 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -387,11 +387,7 @@ int main() { "tzset did not pick up the new TZ"); Require(tzname_nid_postfix[0] != nullptr && std::strcmp(tzname_nid_postfix[0], "UTC") == 0 && tzname_nid_postfix[1] != nullptr, "tzname does not name the zone tzset set"); -#ifdef _WIN32 - _putenv_s("TZ", "EST5EDT"); -#else - setenv("TZ", "EST5EDT", 1); -#endif + Require(setenv_nid_postfix("TZ", "EST5EDT", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0 && std::strcmp(tzname_nid_postfix[1], "EDT") == 0, "tzname does not name the standard and daylight zones"); From 7075c6e8e888f52b6221fb23782b9b922b0f6520 Mon Sep 17 00:00:00 2001 From: akkadbakkad89 Date: Mon, 19 Oct 2026 11:16:04 +0000 Subject: [PATCH 143/176] fix(prx): correct the offline NP WebAPI error code and list silent stubs (cherry picked from commit 8f3849e7919c3fc28d6c3d45b7e5dcc3e69d0232) --- core/libs/prx/libSceNpCppWebApi/Export.cpp | 91 +++++++++++----------- docs/dev/debt/libSceNpCppWebApi.md | 10 ++- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index d65be3d1b5de..8bf9b62653e4 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -6,15 +6,16 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -static constexpr int SCE_NP_WEBAPI_ERROR_UNAVAILABLE = static_cast(0x80552901); +static constexpr int SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN = static_cast(0x80552907); // Offline PSN: request entry points (API calls, transaction start/readData, factories) fail with -// SCE_NP_WEBAPI_ERROR_UNAVAILABLE; parameter bookkeeping succeeds; response accessors stay unimplemented -// because a failed request never produces a response. +// SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN, the constant shadPS4/fpPS4 document for this code; parameter +// bookkeeping succeeds; response accessors stay unimplemented because a failed request never +// produces a response. extern "C" { int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody18setStartSerialRankERKi() { @@ -63,11 +64,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody7setP } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V128GetRankingRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_21GetRankingRequestBodyEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V129RecordScoreRequestBodyFactory6createEPNS1_6Common10LibContextElPNS5_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User12setAccountIdERKm() { @@ -79,7 +80,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User7setPcIdERKi() { } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi10getRankingEiRKNS4_21ParameterToGetRankingERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22GetRankingResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking10initializeEPNS1_6Common10LibContextEi() { @@ -105,7 +106,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanki } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi22getLargeDataByObjectIdEiRKNS4_33ParameterToGetLargeDataByObjectIdERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_37GetLargeDataByObjectIdResponseHeadersEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId10initializeEPNS1_6Common10LibContextEPKc() { @@ -127,7 +128,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLarge } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi18getBoardDefinitionEiRKNS4_29ParameterToGetBoardDefinitionERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30GetBoardDefinitionResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition10initializeEPNS1_6Common10LibContextEi() { @@ -149,11 +150,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoa } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi11recordScoreEiRKNS4_22ParameterToRecordScoreERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23RecordScoreResponseBodyEEENSA_INS4_26RecordScoreResponseHeadersEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi15recordLargeDataEiRKNS4_26ParameterToRecordLargeDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_27RecordLargeDataResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore10initializeEPNS1_6Common10LibContextEiNS6_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE() { @@ -197,7 +198,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecord } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_27DownloadDataResponseHeadersEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData10initializeEPNS1_6Common10LibContextEPKci() { @@ -372,7 +373,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_ } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { @@ -380,7 +381,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE6finishEv() { @@ -392,7 +393,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { @@ -400,11 +401,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS } int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextEm() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE8sendDataEPKvm() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { @@ -418,7 +419,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePt } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE6finishEv() { @@ -426,7 +427,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12Intrusive } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE8readDataEPcm() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEEC1Ev(void* self) { @@ -440,7 +441,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12Intrusive } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE6finishEv() { @@ -448,7 +449,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12Intrusive } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE8readDataEPcm() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEEC1Ev(void* self) { @@ -678,15 +679,15 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V19RecordApi26RecordScoreRespo } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE11getResponseERS8_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr* self) { @@ -730,7 +731,7 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_ } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv() { @@ -868,7 +869,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession16set } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestPlayerSessionMemberPlayerFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_32RequestCreatePlayerSessionPlayerEEEEEPNS9_INS3_32RequestPlayerSessionMemberPlayerEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE3endEv() { @@ -894,7 +895,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEptEv(const IntrusivePtr* self) { @@ -910,7 +911,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29Parame } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V122LocalizedStringFactory6createEPNS1_6Common10LibContextEPKcNS_4Json6ObjectEPNS5_12IntrusivePtrINS3_15LocalizedStringEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEED1Ev(IntrusivePtr* self) { @@ -1002,7 +1003,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25joinPlayerSessionAsPlayerEiRKNS4_36ParameterToJoinPlayerSessionAsPlayerERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_52PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(CommonString* self, const char* text) { @@ -1090,7 +1091,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsRespons } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi18leavePlayerSessionEiRKNS4_29ParameterToLeavePlayerSessionERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { @@ -1098,7 +1099,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sessio } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev() { @@ -1106,7 +1107,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31Parame } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi27getPlayerSessionInvitationsEiRKNS4_38ParameterToGetPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_54GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1Ev(IntrusivePtr* self) { @@ -1160,7 +1161,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession19set } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev() { @@ -1190,7 +1191,7 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvit } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev() { @@ -1250,11 +1251,11 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sessi } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133RequestPlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi20createPlayerSessionsEiRKNS4_31ParameterToCreatePlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30PostPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev() { @@ -1271,11 +1272,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V126RequestPlayerSessionPlaye } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V127RequestPlayerSessionFactory6createEPNS1_6Common10LibContextEiNS5_12IntrusivePtrINS3_32RequestPlayerSessionMemberPlayerEEERKNS5_6VectorINS5_6StringEEENS8_INS3_15LocalizedStringEEEPNS8_INS3_20RequestPlayerSessionEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { @@ -1289,7 +1290,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V131PlayerSessionPushContextFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEED1Ev(IntrusivePtr* self) { @@ -1339,7 +1340,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE6finishEv() { @@ -1365,7 +1366,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11getResponseERS4_() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { @@ -1374,7 +1375,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14 } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestCreatePlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_RKNS5_6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEEPNSB_INS3_32RequestCreatePlayerSessionPlayerEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { @@ -1406,15 +1407,15 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession12getSessio } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V158PostPlayerSessionsSessionIdMemberPlayersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEEEEPNS9_INS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V136PostPlayerSessionsRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_20RequestPlayerSessionEEEEEPNS9_INS3_29PostPlayerSessionsRequestBodyEEE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { - return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE() { diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index bdcbf5fe4ac3..4ea6a15b4ea2 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -1,8 +1,16 @@ # libSceNpCppWebApi technical debt +### Silent stubs + +- [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `TransactionBase<...>` (8 instantiations), `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish +- [sce::Np::CppWebApi::Leaderboards::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - request-body and parameter bookkeeping succeeds without storing state: `GetRankingRequestBody::set{StartSerialRank,Group,Limit,Users,Offset}`, `RecordScoreRequestBody::set{Comment,SmallData,NeedsTmpRank,ComparedDateTime,PcId}`, `User::{setAccountId,setPcId}`, `BoardsApi::ParameterToGetBoardDefinition::{initialize,terminate}`, `RecordApi::ParameterToRecordLargeData::{initialize,terminate}`, `RecordApi::ParameterToRecordScore::{initialize,setxPsnAtomicOperation,terminate}`, `ViewApi::ParameterToGetLargeDataByObjectId::{initialize,terminate}`, `ViewApi::ParameterToGetRanking::{initialize,setgetRankingRequestBody,terminate}` +- [sce::Np::CppWebApi::SessionManager::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - parameter bookkeeping succeeds without storing state: `PlayerSessionsApi::ParameterToCreatePlayerSessions::{ctor,dtor,initialize}`, `PlayerSessionsApi::ParameterToGetPlayerSessionInvitations::{ctor,dtor,initialize,setfields,setinvitationInvalidFilter}`, `PlayerSessionsApi::ParameterToJoinPlayerSessionAsPlayer::{ctor,dtor,initialize,terminate}`, `PlayerSessionsApi::ParameterToLeavePlayerSession::{ctor,dtor,initialize}`, `RequestPlayerSession::set{MaxSpectators,InvitableUserType,JoinableUserType,ExclusiveLeaderPrivileges}`, `RequestPlayerSessionPlayer::setPushContexts` +- [sce::Np::CppWebApi::TitleCloudStorage::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData::{initialize,terminate}` succeed without storing state + ### Unknown function info +- [sce::Np::CppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - every request entry point (API calls, transaction `start`/`getResponse`/`readData`, the `*Factory::create`s) returns `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN` offline. The constant's name and value follow the reference tables in shadPS4 `np_error.h` and fpPS4 `np_error.pas`, where `0x80552907` is `ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN` and `0x80552901` is `OUT_OF_MEMORY` (no `UNAVAILABLE` exists in the `0x80552901`-`0x8055291F` range); it is the code a signed-out console returns from these entry points. The earlier `0x80552901` value came from the module the previous implementation shipped, which used the same incorrect constant - [sce::Np::CppWebApi::Common::IntrusivePtr](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sce::Np::CppWebApi::Common::String](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. `append` grows the buffer with the host allocator and the destructor frees it, instead of going through the `LibContext` allocator. `pushBack` into `Vector` stores a deep copy, so a destroyed temporary cannot invalidate the stored element; a title that byte-copies a `String` with a live buffer would still double-free, as nothing increments the reference count - [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `operator[]` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one -- [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_UNAVAILABLE`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object +- [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object From 87a0e0b4a98b5a2622af36a5d5b5deb206aa4c65 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 17:47:12 +0400 Subject: [PATCH 144/176] test(libkernel): check the macOS open file limit against EMFILE guest_getrlimit failed on macOS because the host's soft and hard RLIMIT_NOFILE differed. On macOS it now also checks that the limit does not exceed kern.maxfilesperproc, and, as on Windows, that guest pipes fail with EMFILE within 64 descriptors of the reported limit. The pipe loop moves into a helper that both use. (cherry picked from commit 6eb29b8fe7bb05131229e3c90dee5402eda62db7) --- core/libs/tests/GuestGetrlimit.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/libs/tests/GuestGetrlimit.cpp b/core/libs/tests/GuestGetrlimit.cpp index 15e183c9e0b0..d9dc18406f83 100644 --- a/core/libs/tests/GuestGetrlimit.cpp +++ b/core/libs/tests/GuestGetrlimit.cpp @@ -11,6 +11,9 @@ #ifdef __APPLE__ #include #endif +#ifdef __APPLE__ +#include +#endif struct ResourceLimit { std::int64_t rlim_cur; From ed1389754fdc36512b45fe8c7683c0df0c89ae48 Mon Sep 17 00:00:00 2001 From: hypermist Date: Mon, 19 Oct 2026 18:57:31 +1300 Subject: [PATCH 145/176] test(agc): exceed the device's own indirect dispatch limit The over-limit row was a fixed 0xffffffff in X, which is a valid count on devices that report that X limit (RX 9070 XT). Take the first axis whose limit is below 0xffffffff and use limit + 1 there; skip when no count can exceed the limits. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 6b176d98e31f951c3fa6ab681d4002258e6c936a) --- .../tests/execution/IndirectDispatchLimit.cpp | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp index 5b4420522edb..54aba72adc0f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,16 @@ std::array BufferDescriptor(const void* data, std::uint32_t co return {static_cast(address), static_cast((address >> 32u) & 0xffffu), count * 4u, 0x31016facu}; } +std::optional> OverLimit(const std::uint32_t* limit) { + for (std::size_t axis = 0; axis < 3; ++axis) { + if (limit[axis] == 0xffffffffu) continue; + std::array groups{1u, 1u, 1u}; + groups[axis] = limit[axis] + 1u; + return groups; + } + return std::nullopt; +} + int Run(AgcDriver::VulkanDevice& device, std::uint32_t* block, std::array groups) { Output.fill(0xdeadbeefu); std::memcpy(block, groups.data(), sizeof(groups)); @@ -62,11 +73,15 @@ int main() { GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); int failures = 0; try { - const int invalidPath = Run(*device, block, {0xffffffffu, 1u, 1u}); - if (invalidPath != 0) { + const auto over = OverLimit(device->WorkGroupCountLimit()); + const int invalidPath = over ? Run(*device, block, *over) : 0; + const auto overText = over ? std::to_string((*over)[0]) + "x" + std::to_string((*over)[1]) + "x" + std::to_string((*over)[2]) : std::string(); + if (!over) { + std::puts("skipped, every group count is within the device limits"); + } else if (invalidPath != 0) { std::printf("skipped, the group counts were read on the CPU (reason %d): the device cannot import the block\n", invalidPath); } else { - for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 0xdeadbeefu, "indirect dispatch limit: thread " + std::to_string(tid) + " ran with 0xffffffff groups"); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 0xdeadbeefu, "indirect dispatch limit: thread " + std::to_string(tid) + " ran with " + overText + " groups"); Require(Run(*device, block, {1u, 1u, 1u}) == 0, "indirect dispatch limit: valid counts left the GPU path"); for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 7u, "indirect dispatch limit: thread " + std::to_string(tid) + " stored " + std::to_string(Output[tid]) + " with one group, expected 7"); } From 7c343abf4227628f901017bb626ed962d2ff3b79 Mon Sep 17 00:00:00 2001 From: fogydes Date: Thu, 15 Oct 2026 21:27:22 +0530 Subject: [PATCH 146/176] feat(libSceAgcDriver): implement command buffer submission (cherry picked from commit c3775ea000b0a6c9afbd3471b3c1a6140498825b) --- .../Submit/include/CommandBuffer.hpp | 12 ++++++ .../Submit/src/CommandBuffer.cpp | 29 ++++++++----- .../libSceAgcDriver/tests/WaitShutdown.cpp | 42 +++++++++++++++++++ docs/dev/debt/libSceAgcDriver.md | 1 + 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp b/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp index ef4b9e689444..52fb4cb31d91 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp +++ b/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_SUBMIT_INCLUDE_COMMANDBUFFER_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_SUBMIT_INCLUDE_COMMANDBUFFER_HPP +#include "SceTypes.hpp" +#include + +struct Packet; + +extern "C" { + +int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet); +int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp index 646827ca1138..7b326f7f9d92 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp @@ -1,26 +1,35 @@ #include "prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include #include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet) { - (void)queue_context; - (void)packet; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!queue_context || !packet) return EINVAL; + const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); + if (queue != 0 && (queue < 0x20 || queue >= 0x58)) { + throw std::runtime_error(std::string(__func__) + ": unsupported queue"); + } + AgcDriver::Submit(packet, queue); + return 0; } int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count) { - (void)queue_context; - (void)command_buffers; - (void)sizes_in_dwords; - (void)count; - NotImplemented_nid_no_patch(__func__); - return 0; + if (count == 0) return 0; + if (!queue_context || !command_buffers || !sizes_in_dwords) return EINVAL; + const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); + if (queue != 0 && (queue < 0x20 || queue >= 0x58)) { + throw std::runtime_error(std::string(__func__) + ": unsupported queue"); + } + return AgcDriver::SubmitMany(command_buffers, sizes_in_dwords, count, queue); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp b/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp index 72c88cbc478b..ee2a3fb2212a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -50,6 +51,46 @@ void Submit(std::uint32_t queue, const std::vector& words) { Require(result == 0, "shutdown wait submission failed"); } +void CheckCommandBufferSubmission() { + struct MockQueueContext { + std::uint32_t unused = 0; + std::uint32_t queue_id = 0; + }; + MockQueueContext graphicsCtx{0, 0}; + MockQueueContext computeCtx{0, 0x20}; + MockQueueContext invalidCtx{0, 1}; + + Packet emptyPacket{nullptr, 0, 0, {}}; + Require(sceAgcDriverSubmitCommandBuffer(nullptr, &emptyPacket) == 22, "null context submit must return EINVAL"); + Require(sceAgcDriverSubmitCommandBuffer(&graphicsCtx, nullptr) == 22, "null packet submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(nullptr, nullptr, nullptr, 0) == 0, "count 0 multi submit must return 0"); + + std::uint32_t* cmdBufs[] = {nullptr}; + const std::uint32_t cmdSizes[] = {0}; + Require(sceAgcDriverSubmitMultiCommandBuffers(nullptr, cmdBufs, cmdSizes, 1) == 22, "null context multi submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, nullptr, cmdSizes, 1) == 22, "null cmdBufs multi submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, nullptr, 1) == 22, "null cmdSizes multi submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, cmdSizes, 0) == 0, "empty multi submission failed"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, cmdSizes, 1) == 0, "graphics context multi submit failed"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&computeCtx, cmdBufs, cmdSizes, 1) == 0, "compute context multi submit failed"); + + bool submitRejected = false; + try { + sceAgcDriverSubmitCommandBuffer(&invalidCtx, &emptyPacket); + } catch (const std::runtime_error&) { + submitRejected = true; + } + Require(submitRejected, "unsupported queue context was not rejected"); + + bool multiRejected = false; + try { + sceAgcDriverSubmitMultiCommandBuffers(&invalidCtx, cmdBufs, cmdSizes, 1); + } catch (const std::runtime_error&) { + multiRejected = true; + } + Require(multiRejected, "unsupported queue context for multi submit was not rejected"); +} + void CheckShutdownCancellation() { static std::array states{}; constexpr std::array queues{0, 0x20, 0x28}; @@ -92,6 +133,7 @@ int main(int argc, char** argv) { return 1; } try { + CheckCommandBufferSubmission(); CheckShutdownCancellation(); std::puts("unsatisfied label waits cancelled cleanly at shutdown"); return 0; diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 110440d9b077..dcc451defe93 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -7,6 +7,7 @@ ### Unknown function info - [Initial point context](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - new queues and CLEAR_STATE seed PA_SU_POINT_SIZE 0x00080008 and PA_SU_POINT_MINMAX 0xffff0000, matching every public context table in libSceAgc/Command/include/RegisterDefaultsData.hpp. Cocoon (PPSA08766) reaches geometry point output without writing these registers in the observed startup. Applying the SDK context defaults to initial/CLEAR_STATE driver context is inferred; GPU power-on reset state is not measured and is not the source of these values. Explicit writes and pushed/restored contexts retain their own values. +- [sceAgcDriverSubmitCommandBuffer, sceAgcDriverSubmitMultiCommandBuffers](../../../core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp) (libSceAgcDriver) - `queue_context` is treated as a pointer to context whose queue identifier is stored 4 bytes in, matching KytyPS5 (`src/libs/agc.cpp` `AgcDriverSubmitCommandBuffer` / `get_driver_queue`). A null context returns `EINVAL` (22). Whether the console validates other context fields before queue dispatch is unverified - [Optional BMP frame dumps](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) use checked host stdio. An open, header, pixel or close failure prints one diagnostic and disables subsequent BMP dumps for the process; queued frames drain without more file I/O. Pixel validation and allocation failures retain the existing fatal path. The CPU fixture checks identical BMP bytes and real pipe failures. VideoOut flips on an RTX 3060 continue after a first dump that cannot be opened; header, pixel and close failures during presentation are unmeasured. From 138fadca9ba494afed3bb59d7f68ca7ccdf2c4db Mon Sep 17 00:00:00 2001 From: JJenkx Date: Fri, 16 Oct 2026 09:16:01 -0400 Subject: [PATCH 147/176] fix(agc): initialize point size and limits from SDK defaults (cherry picked from commit c8d7e37f4c092bfccdf3c80d865d1dfd4141d7fd) --- docs/dev/debt/libSceAgcDriver.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index dcc451defe93..9fbca11589fe 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -9,6 +9,9 @@ - [Initial point context](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - new queues and CLEAR_STATE seed PA_SU_POINT_SIZE 0x00080008 and PA_SU_POINT_MINMAX 0xffff0000, matching every public context table in libSceAgc/Command/include/RegisterDefaultsData.hpp. Cocoon (PPSA08766) reaches geometry point output without writing these registers in the observed startup. Applying the SDK context defaults to initial/CLEAR_STATE driver context is inferred; GPU power-on reset state is not measured and is not the source of these values. Explicit writes and pushed/restored contexts retain their own values. - [sceAgcDriverSubmitCommandBuffer, sceAgcDriverSubmitMultiCommandBuffers](../../../core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp) (libSceAgcDriver) - `queue_context` is treated as a pointer to context whose queue identifier is stored 4 bytes in, matching KytyPS5 (`src/libs/agc.cpp` `AgcDriverSubmitCommandBuffer` / `get_driver_queue`). A null context returns `EINVAL` (22). Whether the console validates other context fields before queue dispatch is unverified +- [Initial point context](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - new queues and CLEAR_STATE seed PA_SU_POINT_SIZE 0x00080008 and PA_SU_POINT_MINMAX 0xffff0000, matching every public context table in libSceAgc/Command/include/RegisterDefaultsData.hpp. Cocoon (PPSA08766) reaches geometry point output without writing these registers in the observed startup. Applying the SDK context defaults to initial/CLEAR_STATE driver context is inferred; GPU power-on reset state is not measured and is not the source of these values. Explicit writes and pushed/restored contexts retain their own values. + + - [Optional BMP frame dumps](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) use checked host stdio. An open, header, pixel or close failure prints one diagnostic and disables subsequent BMP dumps for the process; queued frames drain without more file I/O. Pixel validation and allocation failures retain the existing fatal path. The CPU fixture checks identical BMP bytes and real pipe failures. VideoOut flips on an RTX 3060 continue after a first dump that cannot be opened; header, pixel and close failures during presentation are unmeasured. - [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw. From 0791e53a554cbac70fc307163a5cbbf9dd1cb25c Mon Sep 17 00:00:00 2001 From: ionut27001 <194206737+ionut27001@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:53:20 +0300 Subject: [PATCH 148/176] test(agc): give the flip store test the deadline its siblings use agc_driver_flip_stores_small_results waits 2 s for the flip and the stored texels. That window also holds the driver's start and the compute program's compilation at dispatch, so on a loaded host the flip arrives late and the test reports that it did not reach the GPU. Wait 10 s, as the other execution tests do. (cherry picked from commit 58d608c5569b6c4b47fed42be7a8e1c8f5d193e8) --- .../libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp index bbac636c998a..34f0951dc6f5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp @@ -131,7 +131,7 @@ int main() { auto words = Frame(texels); Packet packet{words.data(), static_cast(words.size()), 0, {}}; AgcDriver::Submit(&packet, 0); - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); while ((output->ready.load() == 0 || !stored()) && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); const bool flipped = output->ready.load() == 1; const bool seen = stored(); From b4171383b9060cae40ec2395c318d8086f8488f6 Mon Sep 17 00:00:00 2001 From: gguerra26 Date: Thu, 15 Oct 2026 20:18:43 +0100 Subject: [PATCH 149/176] fix(agc): normalize unused viewport depth ranges Use representable viewport endpoints only when the draw has no depth attachment and its pixel shader does not read PositionZ. Preserve XY coverage, clip-space mode, depth clamping, and validation of non-finite inputs. Balatro (PPSA21402) reaches rendering on Apple M1 with MoltenVK after this change instead of stopping at VK_EXT_depth_range_unrestricted. Depth-dependent draws retain their original behavior. (cherry picked from commit 538b63ef4ccf83ce6627f60fbabf4e55bac94f26) --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 + .../Execution/src/Driver/Draw/DrawDecode.cpp | 1 + .../Graphics/include/State.hpp | 1 + .../libSceAgcDriver/Graphics/src/State.cpp | 8 +++ .../prx/libSceAgcDriver/tests/Graphics.cpp | 57 +++++++++++++++++++ 5 files changed, 69 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index dbb85754c84a..b655873aa55f 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1293,6 +1293,8 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_undithered_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --undithered-alpha-to-coverage-state) + add_test(NAME agc_driver_unused_viewport_depth COMMAND agc_driver_graphics_tests --unused-viewport-depth) + set_tests_properties(agc_driver_unused_viewport_depth PROPERTIES TIMEOUT 30) set_tests_properties(agc_driver_undithered_alpha_to_coverage_state PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --alpha-to-coverage-state) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 7d95fc5730f6..0d8486331fd5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -34,6 +34,7 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su product->state = Graphics::DecodeState(queue, vertexWorkgroup); DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true); product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue)); + Graphics::NormalizeUnusedViewportDepth(product->state, product->pixel); product->pixel.targetExportPacking = Graphics::ExportPackings(product->state); product->pixel.dualSourceBlend = product->state.dualSourceBlend; product->pixel.reversedBlend = product->state.reversedBlend; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 4ecb7b1bc86e..a394542d52ef 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -162,6 +162,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup = false); // The rasterizer's sample positions in [0, 1) of a pixel, rasterSamples of them: the custom pattern, or // the standard 2x/4x/8x pattern (the D3D/Vulkan standard, which is also Metal's default). std::array SampleLocations(const State& state); +void NormalizeUnusedViewportDepth(State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); std::array ExportMappings(const State& state); std::array ExportPackings(const State& state); ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index ed7497cd4d69..5c2c8fbf7216 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -16,6 +16,14 @@ #include namespace AgcDriver::Graphics { + +void NormalizeUnusedViewportDepth(State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel) { + if (state.depth || pixel.posZ) return; + if (!std::isfinite(state.viewport.minDepth) || !std::isfinite(state.viewport.maxDepth)) return; + state.viewport.minDepth = std::clamp(state.viewport.minDepth, 0.0f, 1.0f); + state.viewport.maxDepth = std::clamp(state.viewport.maxDepth, 0.0f, 1.0f); +} + namespace { std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBank bank = RegisterBank::Context) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 97e28ae6a4b1..ab45fd4aae9e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2633,6 +2633,57 @@ void quadPixelMaskTests() { Require(AgcDriver::Graphics::DecodePixelStageInfo(queue.context, {}).quadPixelMask == 0xfu, "full sample masks did not cover the whole quad"); } +void UnusedViewportDepthTests() { + auto queue = makeState(); + queue.context[0x204] = 0; + queue.context[0x113] = std::bit_cast(1.0f); + queue.context[0x114] = 0; + const auto decoded = AgcDriver::Graphics::DecodeState(queue); + Require(!decoded.depth && decoded.viewport.minDepth == -1 && decoded.viewport.maxDepth == 1, "unused depth fixture changed"); + AgcDriver::Graphics::Context context{}; + context.limits.maxViewportDimensions[0] = context.limits.maxViewportDimensions[1] = 16384; + context.limits.viewportBoundsRange[0] = -32768; + context.limits.viewportBoundsRange[1] = 32767; + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.posX = pixel.posY = pixel.posW = true; + auto color = decoded; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(color, pixel); + AgcDriver::Graphics::ValidateViewport(context, color.viewport); + Require(color.viewport.minDepth == 0 && color.viewport.maxDepth == 1, "unused viewport depth is not representable"); + AgcDriver::Graphics::Context remapping{}; + remapping.depthClamp = true; + const auto normalized = color; + AgcDriver::Graphics::ApplyDepthRemap(remapping, color); + Require(color.depthRemap.mode == 0 && color.viewport.minDepth == 0 && color.viewport.maxDepth == 1 && color.negativeOneToOne == normalized.negativeOneToOne && color.depthClamp == normalized.depthClamp, "the depth remap acted on a normalized viewport"); + Require(color.viewport.x == decoded.viewport.x && color.viewport.y == decoded.viewport.y && color.viewport.width == decoded.viewport.width && color.viewport.height == decoded.viewport.height && color.negativeOneToOne == decoded.negativeOneToOne && color.depthClamp == decoded.depthClamp, "unused depth changed coverage or clipping"); + auto observed = decoded; + pixel.posZ = true; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(observed, pixel); + Require(observed.viewport.minDepth == -1, "fragment position depth was changed"); + expectFailure([&] { AgcDriver::Graphics::ValidateViewport(context, observed.viewport); }, "viewport depth outside"); + pixel.posZ = false; + auto attached = decoded; + attached.depth.emplace(); + AgcDriver::Graphics::NormalizeUnusedViewportDepth(attached, pixel); + Require(attached.viewport.minDepth == -1, "attached depth was changed"); + expectFailure([&] { AgcDriver::Graphics::ValidateViewport(context, attached.viewport); }, "viewport depth outside"); + auto reversed = decoded; + reversed.viewport.minDepth = 2; + reversed.viewport.maxDepth = -1; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(reversed, pixel); + AgcDriver::Graphics::ValidateViewport(context, reversed.viewport); + Require(reversed.viewport.minDepth == 1 && reversed.viewport.maxDepth == 0, "reversed unused depth is not representable"); + auto bounded = decoded; + bounded.viewport.minDepth = 0.25f; + bounded.viewport.maxDepth = 0.75f; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(bounded, pixel); + Require(bounded.viewport.minDepth == 0.25f && bounded.viewport.maxDepth == 0.75f, "an in-range viewport changed"); + auto invalid = decoded; + invalid.viewport.minDepth = std::numeric_limits::infinity(); + AgcDriver::Graphics::NormalizeUnusedViewportDepth(invalid, pixel); + expectFailure([&] { AgcDriver::Graphics::ValidateViewport(context, invalid.viewport); }, "non-finite viewport"); +} + void DepthClipTests() { auto queue = makeState(); const auto direct = AgcDriver::Graphics::DecodeState(queue); @@ -6725,6 +6776,11 @@ int main(int argc, char** argv) { setenv("APS5_HEAP_MIRROR_MIB", "4", 1); #endif try { + if (argc == 2 && std::string_view(argv[1]) == "--unused-viewport-depth") { + UnusedViewportDepthTests(); + DepthClipTests(); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--unused-stencil-reference") { stencilReferenceUnusedTests(); return 0; @@ -6804,6 +6860,7 @@ int main(int argc, char** argv) { hardwareScreenOffsetTests(); srgb8TargetTests(); DepthClipTests(); + UnusedViewportDepthTests(); DepthStencilTests(); OneDimensionalColorTests(); htileDepthClearTests(); From 954c4cb4f3544bdcb596792c2a79a8b4f972aabe Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:12:02 -0500 Subject: [PATCH 150/176] fix(agc): drop depth remap lines from the unused viewport depth test The fork has no depth range remap, so the test no longer calls ApplyDepthRemap. --- core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index ab45fd4aae9e..f3eb7802748c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2650,11 +2650,6 @@ void UnusedViewportDepthTests() { AgcDriver::Graphics::NormalizeUnusedViewportDepth(color, pixel); AgcDriver::Graphics::ValidateViewport(context, color.viewport); Require(color.viewport.minDepth == 0 && color.viewport.maxDepth == 1, "unused viewport depth is not representable"); - AgcDriver::Graphics::Context remapping{}; - remapping.depthClamp = true; - const auto normalized = color; - AgcDriver::Graphics::ApplyDepthRemap(remapping, color); - Require(color.depthRemap.mode == 0 && color.viewport.minDepth == 0 && color.viewport.maxDepth == 1 && color.negativeOneToOne == normalized.negativeOneToOne && color.depthClamp == normalized.depthClamp, "the depth remap acted on a normalized viewport"); Require(color.viewport.x == decoded.viewport.x && color.viewport.y == decoded.viewport.y && color.viewport.width == decoded.viewport.width && color.viewport.height == decoded.viewport.height && color.negativeOneToOne == decoded.negativeOneToOne && color.depthClamp == decoded.depthClamp, "unused depth changed coverage or clipping"); auto observed = decoded; pixel.posZ = true; From dfce915d7a4be0bdf7c7784e2e1f6dc5ffd99508 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 14:55:16 +0400 Subject: [PATCH 151/176] test(agc): check integer interpolation against the hardware rows agc_driver_interpolation_modes skipped on devices without Float64. It now runs its 32 rows measured on RDNA2, for the three 16-bit interpolation instructions in all eight float modes, through the integer path on every device, and through the f64 path as well where the device has Float64. The shader for one row set is built for either path, and reports which path a mismatch came from. (cherry picked from commit b8a5c1a91a032ec1b1d49d66769cbf6490fda84e) --- .../tests/execution/InterpolationModes.cpp | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp index cb363ba335c0..4d411463f65d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace { @@ -67,13 +68,14 @@ constexpr std::uint32_t Words = 12; constexpr std::uint32_t Sentinel = 0xdeadbeefu; alignas(256) std::array Data{}; -RecompileResult Compile(std::uint32_t mode) { +RecompileResult Compile(std::span modes, bool integer, bool raw, std::uint32_t words) { IrProgram program; const ShaderStageInputInfo inputs{}; SpirvEmitterState state(program, inputs); + state.integerInterpolation = integer; auto& module = state.module; module.EmitCapability(spv::CapabilityShader); - module.EmitCapability(spv::CapabilityFloat64); + module.EmitCapability(integer ? spv::CapabilityInt64 : spv::CapabilityFloat64); module.EmitCapability(spv::CapabilitySignedZeroInfNanPreserve); module.EmitExtension("SPV_KHR_float_controls"); module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); @@ -92,14 +94,15 @@ RecompileResult Compile(std::uint32_t mode) { const auto voidType = module.Type(spv::OpTypeVoid); module.EmitEntryPoint(spv::ExecutionModelGLCompute, main, "main", {invocation}); module.AddExecutionMode(main, spv::ExecutionModeLocalSize, static_cast(Rows.size()), 1u, 1u); - for (const auto width : {32u, 64u}) module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, width); + module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, 32u); + if (!integer) module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, 64u); module.AddFunction(spv::OpFunction, voidType, main, spv::FunctionControlMaskNone, module.Type(spv::OpTypeFunction, voidType)); module.AddFunction(spv::OpLabel, module.AllocateId()); const auto id = module.AllocateId(); module.AddFunction(spv::OpLoad, TypeU32Vector(state, 3u), id, invocation); const auto lane = module.AllocateId(); module.AddFunction(spv::OpCompositeExtract, u32, lane, id, 0u); - const auto rowBase = Binary(state, spv::OpIMul, u32, lane, ConstantU32(state, Words)); + const auto rowBase = Binary(state, spv::OpIMul, u32, lane, ConstantU32(state, words)); const auto address = [&](std::uint32_t offset) { const auto pointer = module.AllocateId(); module.AddFunction(spv::OpAccessChain, element, pointer, buffer, ConstantU32(state, 0u), Binary(state, spv::OpIAdd, u32, rowBase, ConstantU32(state, offset))); @@ -113,14 +116,18 @@ RecompileResult Compile(std::uint32_t mode) { IrProgram constants; IrBuilder builder(constants); builder.SetInsertionPoint(constants.CreateBlock()); - const auto* flags = &builder.Constant(mode); - for (std::uint32_t op = 0; op < 3u; ++op) { - const auto delta = load(op * 3u); - const auto coordinate = load(op * 3u + 1u); - const auto base = load(op * 3u + 2u); - const auto value = op == 1u ? EmitFPInterpolateF16(state, delta, coordinate, base, flags) : EmitFPInterpolateF32(state, delta, coordinate, base, flags); - const auto result = op == 1u ? EmitConvertF16F32(state, value) : Unary(state, spv::OpBitcast, u32, value); - module.AddFunction(spv::OpStore, address(9u + op), result); + std::array operands{}; + for (std::uint32_t index = 0; index < operands.size(); ++index) operands[index] = load(index); + for (std::uint32_t slot = 0; slot < modes.size(); ++slot) { + const auto* flags = &builder.Constant(modes[slot]); + for (std::uint32_t op = 0; op < 3u; ++op) { + const auto delta = operands[op * 3u]; + const auto coordinate = operands[op * 3u + 1u]; + const auto base = operands[op * 3u + 2u]; + const auto value = op == 1u ? EmitFPInterpolateF16(state, delta, coordinate, base, flags) : EmitFPInterpolateF32(state, delta, coordinate, base, flags); + const auto result = op == 1u && !raw ? EmitConvertF16F32(state, value) : Unary(state, spv::OpBitcast, u32, value); + module.AddFunction(spv::OpStore, address(9u + slot * 3u + op), result); + } } module.AddFunction(spv::OpReturn); module.AddFunction(spv::OpFunctionEnd); @@ -132,10 +139,11 @@ RecompileResult Compile(std::uint32_t mode) { return result; } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode, bool integer) { Data.fill(Sentinel); for (std::uint32_t row = 0; row < Rows.size(); ++row) std::copy(Rows[row].input.begin(), Rows[row].input.end(), Data.begin() + row * Words); - device.Dispatch(Compile(ModeFlags[mode]), 1u, 1u, 1u); + const std::array modes{ModeFlags[mode]}; + device.Dispatch(Compile(modes, integer, false, Words), 1u, 1u, 1u); device.WaitIdle(); for (std::uint32_t row = 0; row < Rows.size(); ++row) { for (std::uint32_t input = 0; input < 9u; ++input) Require(Data[row * Words + input] == Rows[row].input[input], "interpolation modes changed an input"); @@ -143,7 +151,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode) { const auto actual = Data[row * Words + 9u + op]; const auto expected = Rows[row].expected[mode][op]; char message[200]; - std::snprintf(message, sizeof(message), "%s, %s, row %u: got 0x%08x, expected 0x%08x", OpNames[op], ModeNames[mode], row, actual, expected); + std::snprintf(message, sizeof(message), "%s (%s), %s, row %u: got 0x%08x, expected 0x%08x", OpNames[op], integer ? "integer" : "Float64", ModeNames[mode], row, actual, expected); Require(actual == expected, message); } } @@ -157,12 +165,13 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const auto target = device->Target(); - if (std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityFloat64) == target.supportedCapabilities.end()) { - std::puts("skipped, interpolation modes require Float64"); - return VulkanTestSkipped; + const bool float64 = std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityFloat64) != target.supportedCapabilities.end(); + for (std::uint32_t mode = 0; mode < ModeFlags.size(); ++mode) { + Run(*device, mode, true); + if (float64) Run(*device, mode, false); } - for (std::uint32_t mode = 0; mode < ModeFlags.size(); ++mode) Run(*device, mode); - std::puts("interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes"); + std::puts(float64 ? "interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes, integer and Float64 paths" + : "interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes, integer path (the device has no Float64)"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; From 4344dd6de8b51feaa6abe30e33c2c5681c710dfd Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 14:55:16 +0400 Subject: [PATCH 152/176] test(agc): compare 16-bit interpolation with a CPU reference on random rows The hardware rows cover rounding to nearest even only. The test now also builds one shader per path that evaluates 2,048 random rows in all 32 modes: the eight float modes with each of the four f16 rounding modes, which v_fma_mixlo_f16 and v_fma_mixhi_f16 use. The rows draw f16 deltas, f32 coordinates and bases from random bits, random finite values and special values (zeros, subnormals, the largest finites, infinities, NaNs), and a third of the bases nearly or exactly cancel the product. Each result must match a CPU reference built on f64: the exact product, the sum rounded to odd, one rounding at the target precision in the mode, and the same overflow, zero and NaN rules. On devices with Float64 the f64 path is checked against the same reference. (cherry picked from commit e9af1dc0a6afb807c0efad29942014e494b14f75) --- .../tests/execution/InterpolationModes.cpp | 149 +++++++++++++++++- 1 file changed, 146 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp index 4d411463f65d..9c332e8aaaab 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp @@ -6,9 +6,12 @@ #include "VulkanTestDevice.hpp" #include #include +#include +#include #include #include #include +#include #include #include @@ -66,7 +69,10 @@ constexpr std::array Rows{{ constexpr std::uint32_t Words = 12; constexpr std::uint32_t Sentinel = 0xdeadbeefu; -alignas(256) std::array Data{}; +constexpr std::uint32_t RandomRows = 2048; +constexpr std::uint32_t RandomModes = 32; +constexpr std::uint32_t RandomWords = 9 + RandomModes * 3; +alignas(256) std::array Data{}; RecompileResult Compile(std::span modes, bool integer, bool raw, std::uint32_t words) { IrProgram program; @@ -160,6 +166,141 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode, bool integer) { } +std::uint32_t Flush(std::uint32_t bits, bool flush, std::uint32_t smallestNormal) { + const auto magnitude = bits & 0x7fffffffu; + return flush && magnitude != 0u && magnitude < smallestNormal ? bits & 0x80000000u : bits; +} + +bool NonFinite(std::uint32_t bits) { return (bits & 0x7f800000u) == 0x7f800000u; } +bool Nan(std::uint32_t bits) { return (bits & 0x7fffffffu) > 0x7f800000u; } +bool Infinite(std::uint32_t bits) { return (bits & 0x7fffffffu) == 0x7f800000u; } +bool Zero(std::uint32_t bits) { return (bits & 0x7fffffffu) == 0u; } + +std::uint32_t Reference(std::uint32_t delta, std::uint32_t coordinate, std::uint32_t base, bool half, std::uint32_t mode) { + const bool quiet = half || (mode & InterpolationQuiet) != 0u; + const bool flush32 = (mode & InterpolationFlush32) != 0u; + const bool flush16 = (mode & InterpolationFlush16) != 0u; + const std::uint32_t rounding = half ? (mode >> InterpolationRound16Shift) & 3u : 0u; + const auto d = Flush(delta, flush16, 0x38800000u); + const auto c = Flush(coordinate, flush32, 0x00800000u); + const auto b = half ? Flush(base, flush32, 0x00800000u) : Flush(base, flush16, 0x38800000u); + const double fd = std::bit_cast(d); + const double fc = std::bit_cast(c); + const double fb = std::bit_cast(b); + std::uint32_t result = 0; + if (NonFinite(d) || NonFinite(c) || NonFinite(b)) { + const double unbounded = fd * fc + fb; + result = std::isnan(unbounded) ? 0xffc00000u : (std::signbit(unbounded) ? 0xff800000u : 0x7f800000u); + const auto quieted = [&](std::uint32_t bits) { return quiet ? bits | 0x00400000u : bits; }; + const bool invalid = (Infinite(d) && Zero(c)) || (Zero(d) && Infinite(c)); + if (Nan(b) && !invalid) result = quieted(b); + if (Nan(c)) result = quieted(c); + if (Nan(d)) result = quieted(d); + } else { + const double product = fd * fc; + double sum = product + fb; + const double back = sum - product; + const double error = (product - (sum - back)) + (fb - back); + if (error != 0.0 && (std::bit_cast(sum) & 1u) == 0u) sum = std::nextafter(sum, error > 0.0 ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()); + if (sum == 0.0 && rounding == 2u && !(product == 0.0 && !std::signbit(product) && fb == 0.0 && !std::signbit(fb))) sum = -0.0; + double rounded = sum; + if (sum != 0.0) { + const int precision = half ? 11 : 24; + const int smallest = half ? -24 : -149; + const int step = std::max(std::ilogb(sum) - (precision - 1), smallest); + const double scaled = std::ldexp(sum, -step); + const double integral = rounding == 1u ? std::ceil(scaled) : rounding == 2u ? std::floor(scaled) : rounding == 3u ? std::trunc(scaled) : std::nearbyint(scaled); + rounded = std::ldexp(integral, step); + } + if (half) { + const double magnitude = std::fabs(rounded); + const bool negative = std::signbit(rounded); + if (rounding == 0u && magnitude > 65536.0) rounded = std::copysign(65536.0, rounded); + if (rounding != 0u && magnitude > 65504.0) { + const bool larger = (rounding == 1u && !negative) || (rounding == 2u && negative); + rounded = std::copysign(larger ? 65536.0 : 65504.0, rounded); + } + } + result = std::bit_cast(static_cast(rounded)); + } + return half ? Flush(result, flush16, 0x38800000u) : Flush(result, flush32, 0x00800000u); +} + +struct Generator { + std::uint64_t state; + std::uint64_t Next() { + state += 0x9e3779b97f4a7c15ull; + auto value = state; + value = (value ^ (value >> 30u)) * 0xbf58476d1ce4e5b9ull; + value = (value ^ (value >> 27u)) * 0x94d049bb133111ebull; + return value ^ (value >> 31u); + } + std::uint32_t Pick(std::uint32_t count) { return static_cast(Next() % count); } + std::uint32_t Half() { + constexpr std::array special{0x0000u, 0x8000u, 0x0001u, 0x8001u, 0x03ffu, 0x0400u, 0x3c00u, 0xbc00u, 0x7bffu, 0xfbffu, 0x7c00u, 0xfc00u}; + const auto bits = Pick(4u) == 0u ? special[Pick(static_cast(special.size()))] : static_cast(Next() & 0xffffu); + const auto sign = (bits & 0x8000u) << 16u; + const auto exponent = (bits >> 10u) & 0x1fu; + const auto fraction = bits & 0x3ffu; + if (exponent == 31u) return sign | 0x7f800000u | (fraction << 13u); + if (exponent != 0u) return sign | ((exponent + 112u) << 23u) | (fraction << 13u); + return sign | std::bit_cast(static_cast(fraction) * 0x1p-24f); + } + std::uint32_t Single() { + constexpr std::array special{0x00000000u, 0x80000000u, 0x00000001u, 0x807fffffu, 0x00800000u, 0x3f800000u, 0xbf800000u, 0x7f7fffffu, 0xff7fffffu, 0x7f800000u, 0xff800000u, 0x7fc00000u, 0xffa00001u, 0x33800000u}; + switch (Pick(4u)) { + case 0: return special[Pick(static_cast(special.size()))]; + case 1: return static_cast(Next()); + default: return (Pick(2u) << 31u) | ((Pick(254u) + 1u) << 23u) | static_cast(Next() & 0x7fffffu); + } + } + std::uint32_t Cancelling(std::uint32_t delta, std::uint32_t coordinate, bool half) { + const double product = static_cast(std::bit_cast(delta)) * static_cast(std::bit_cast(coordinate)); + if (!std::isfinite(product)) return half ? Single() : Half(); + auto bits = std::bit_cast(static_cast(-product)); + if (Pick(2u) == 0u) bits += Pick(5u) - 2u; + if (half) return bits; + const auto value = std::bit_cast(bits); + if (!std::isfinite(value) || std::fabs(value) > 65504.0f) return Half(); + const auto exponent = std::max(std::ilogb(value) - 10, -24); + return std::bit_cast(static_cast(std::ldexp(std::nearbyint(std::ldexp(value, -exponent)), exponent))); + } +}; + +void RunRandom(AgcDriver::VulkanDevice& device, bool integer) { + std::array modes{}; + for (std::uint32_t slot = 0; slot < RandomModes; ++slot) modes[slot] = ModeFlags[slot % ModeFlags.size()] | ((slot / ModeFlags.size()) << InterpolationRound16Shift); + Generator generator{0x6b5f00000000ull}; + Data.fill(Sentinel); + for (std::uint32_t row = 0; row < RandomRows; ++row) { + for (std::uint32_t op = 0; op < 3u; ++op) { + const bool half = op == 1u; + const auto delta = generator.Half(); + const auto coordinate = generator.Single(); + const auto base = generator.Pick(3u) == 0u ? generator.Cancelling(delta, coordinate, half) : half ? generator.Single() : generator.Half(); + Data[row * RandomWords + op * 3u] = delta; + Data[row * RandomWords + op * 3u + 1u] = coordinate; + Data[row * RandomWords + op * 3u + 2u] = base; + } + } + device.Dispatch(Compile(modes, integer, true, RandomWords), RandomRows / static_cast(Rows.size()), 1u, 1u); + device.WaitIdle(); + for (std::uint32_t row = 0; row < RandomRows; ++row) { + for (std::uint32_t slot = 0; slot < RandomModes; ++slot) { + for (std::uint32_t op = 0; op < 3u; ++op) { + const auto* input = &Data[row * RandomWords + op * 3u]; + const auto expected = Reference(input[0], input[1], input[2], op == 1u, modes[slot]); + const auto actual = Data[row * RandomWords + 9u + slot * 3u + op]; + char message[240]; + std::snprintf(message, sizeof(message), "%s (%s), mode 0x%x, random row %u: 0x%08x * 0x%08x + 0x%08x gave 0x%08x, reference 0x%08x", + OpNames[op], integer ? "integer" : "Float64", modes[slot], row, input[0], input[1], input[2], actual, expected); + Require(actual == expected, message); + } + } + } + for (std::size_t index = RandomRows * RandomWords; index < Data.size(); ++index) Require(Data[index] == Sentinel, "random interpolation rows wrote beyond the rows"); +} + int main() { try { const auto device = OpenVulkanTestDevice(); @@ -170,8 +311,10 @@ int main() { Run(*device, mode, true); if (float64) Run(*device, mode, false); } - std::puts(float64 ? "interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes, integer and Float64 paths" - : "interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes, integer path (the device has no Float64)"); + RunRandom(*device, true); + if (float64) RunRandom(*device, false); + std::puts(float64 ? "interpolation modes passed: 32 hardware rows and 2048 random rows against the reference in 32 float modes, 3 ops, integer and Float64 paths" + : "interpolation modes passed: 32 hardware rows and 2048 random rows against the reference in 32 float modes, 3 ops, integer path (the device has no Float64)"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; From 6996ed1c4f10e42e953b4d44ba430f50cd4d718b Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 14:55:16 +0400 Subject: [PATCH 153/176] test(agc): run the mix precision test without Float64 agc_driver_mix_precision skipped on devices without Float64 because v_fma_mixlo_f16 and v_fma_mixhi_f16 were computed in f64. They now compile on such devices too, so the test runs everywhere. (cherry picked from commit 8b7ca7986909913898670a0470edbfd24719fdf2) --- .../prx/libSceAgcDriver/tests/execution/MixPrecision.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp index d8c1506e367b..d5870380d6ae 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp @@ -17,7 +17,6 @@ namespace { using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; -constexpr std::uint32_t Float64Capability = 10; constexpr std::uint32_t Threads = 64; constexpr std::uint32_t Inputs = 4; constexpr std::uint32_t Results = 16; @@ -241,11 +240,6 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - const auto capabilities = device->Target().supportedCapabilities; - if (std::find(capabilities.begin(), capabilities.end(), Float64Capability) == capabilities.end()) { - std::puts("skipped, v_fma_mixlo_f16 and v_fma_mixhi_f16 are computed in f64 and the device has no shaderFloat64"); - return VulkanTestSkipped; - } FillInput(); Run(*device, MixCode); Check(); From 01da0dcfe8cc68d7ce9d545e3df69b0d4863988b Mon Sep 17 00:00:00 2001 From: al3w0f205 Date: Thu, 15 Oct 2026 21:14:50 -0500 Subject: [PATCH 154/176] fix(libSceAgcDriver): mask WAIT_REG_MEM addresses and elide non-overlapping pipeline drains (cherry picked from commit d59934a67c2781566c2ca485c234e5598bc3be7d) --- .../src/Driver/Packets/PacketExecution.cpp | 14 +++++++++-- .../Driver/Synchronization/LabelHistory.cpp | 2 +- .../src/Driver/Synchronization/WaitMemory.cpp | 16 +++++++------ .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 23 ++++++++++--------- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 1c1dc62fea90..f4efaaf0477e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -209,14 +209,24 @@ void Driver::execute(const Submission& submission) { continue; } if (pipelined && !predicated && (opcode == 0x3c || opcode == 0x93) && header != RenderingWaitPacketHeader && orderedWaits && DrawPipeline::Queue0().Busy()) { - const std::uint64_t awaited = packet[2] | (static_cast(packet[3]) << 32u); - if (const auto value = DrawPipeline::Queue0().PendingLabel(awaited, Pm4::WaitAwaitedBytes(packet)); value.has_value() && Pm4::WaitComparesValue(packet, *value)) { + const auto awaitedBytes = Pm4::WaitAwaitedBytes(packet); + const std::uint64_t awaited = (packet[2] & (awaitedBytes == 8 ? ~7u : ~3u)) | (static_cast(packet[3]) << 32u); + const auto value = DrawPipeline::Queue0().PendingLabel(awaited, awaitedBytes); + if (value.has_value() && Pm4::WaitComparesValue(packet, *value)) { ++waitOutcomes().fromRecorderSameQueue; gateOpened() = true; recent.Record(cursor); cursor = nextCursor; continue; } + if (!DrawPipeline::Queue0().Overlaps(awaited, awaitedBytes) && Pm4::WaitSatisfiedUnchecked(packet)) { + bumpEpoch(&EpochBumps::waits); + ++waitOutcomes().atEntry; + gateOpened() = true; + recent.Record(cursor); + cursor = nextCursor; + continue; + } } if (pipelined && DrawPipeline::Queue0().Busy() && !keepsDrawPipeline(packet, header, opcode)) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, header == FlipPacketHeader ? 0xffu : opcode); if (pipelined && predicated && DrawPipeline::Queue0().Busy() && DrawPipeline::Queue0().Overlaps(queue.predication.address, queue.predication.operation == 3 ? 8 : 4)) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, opcode); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp index 696612f79475..27963f67d944 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp @@ -50,7 +50,7 @@ void Driver::traceLabel(std::span packet, std::uint32_t que const char* kind = nullptr; if (opcode == 0x49 && packet.size() >= 7) { target = packet[3] | (static_cast(packet[4]) << 32u); kind = "RELEASE_MEM"; } else if (opcode == 0x37 && packet.size() >= 5) { target = packet[2] | (static_cast(packet[3]) << 32u); kind = "WRITE_DATA"; } - else if ((opcode == 0x3c || opcode == 0x93) && packet.size() >= 7) { target = packet[2] | (static_cast(packet[3]) << 32u); kind = "WAIT_REG_MEM"; } + else if ((opcode == 0x3c || opcode == 0x93) && packet.size() >= 7) { target = (packet[2] & (Pm4::WaitAwaitedBytes(packet) == 8 ? ~7u : ~3u)) | (static_cast(packet[3]) << 32u); kind = "WAIT_REG_MEM"; } else if (opcode == 0x40 && packet.size() >= 6) { target = packet[4] | (static_cast(packet[5]) << 32u); kind = "COPY_DATA"; } std::uint64_t length = 4; if (opcode == 0x50 && packet.size() >= 7) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp index 3dc99dfb55b3..da33d3a361ab 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -110,11 +110,13 @@ void Driver::waitMemory(std::span packet, std::uint32_t que GuestMemory::TagGpuLockThread(queue); auto& outcomes = waitOutcomes(); - const std::uint64_t awaited = packet[2] | (static_cast(packet[3]) << 32u); + const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; const std::size_t awaitedBytes = Pm4::WaitAwaitedBytes(packet); + const auto alignMask = awaitedBytes == 8 ? ~7ull : ~3ull; + const std::uint64_t awaited = (packet[2] & static_cast(alignMask)) | (static_cast(packet[3]) << 32u); static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; - if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x waits 0x%llx == 0x%x (now 0x%x)\n", TraceMs(), queue, static_cast(packet[2] | (static_cast(packet[3]) << 32u)), packet[4], - *reinterpret_cast(packet[2] | (static_cast(packet[3]) << 32u))); + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x waits 0x%llx == 0x%x (now 0x%x)\n", TraceMs(), queue, static_cast(awaited), packet[4], + *reinterpret_cast(awaited)); struct WaitTrace { bool enabled; std::uint32_t queue; std::uint64_t address; std::chrono::steady_clock::time_point begin; ~WaitTrace() { @@ -122,7 +124,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que const auto ms = std::chrono::duration(std::chrono::steady_clock::now() - begin).count(); if (ms >= 0.5) std::fprintf(stderr, "[gpu] %.1f queue 0x%x wait on 0x%llx done after %.1f ms\n", TraceMs(), queue, static_cast(address), ms); } - } waitTrace{traceGpu, queue, packet[2] | (static_cast(packet[3]) << 32u), std::chrono::steady_clock::now()}; + } waitTrace{traceGpu, queue, awaited, std::chrono::steady_clock::now()}; bool warned = false; struct EpochPoint { @@ -369,7 +371,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que static std::uint64_t timeouts = 0; const std::lock_guard lock(reportedMutex); if (++timeouts % 20 == 0) std::fprintf(stderr, "[gpu] %llu GPU waits have timed out\n", static_cast(timeouts)); - firstReport = reported.insert(packet[2] | (static_cast(packet[3]) << 32u)).second; + firstReport = reported.insert(awaited).second; } if (std::getenv("APS5_SKIP_FAILED_SHADERS") != nullptr) { if (firstReport) { @@ -382,10 +384,10 @@ void Driver::waitMemory(std::span packet, std::uint32_t que const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; const std::uint64_t reference = wide ? packet[4] | (static_cast(packet[5]) << 32u) : packet[4]; const std::uint64_t mask = wide ? packet[6] | (static_cast(packet[7]) << 32u) : packet[5]; - const std::uint64_t current = wide ? *reinterpret_cast(awaited) : *reinterpret_cast(awaited); + const std::uint64_t current = awaitedBytes == 8 ? *reinterpret_cast(awaited) : *reinterpret_cast(awaited); std::fprintf(stderr, "[gpu] queue 0x%x WAIT_REG_MEM%s%s at 0x%llx timed out after %dms (function %u ref 0x%llx mask 0x%llx value 0x%llx)\n", queue, wide ? "_64" : "", requireMemory ? " (landing)" : "", static_cast(awaited), waitTimeoutMs(), packet[1] & 7u, static_cast(reference), static_cast(mask), static_cast(current)); - const auto address = packet[2] | (static_cast(packet[3]) << 32u); + const auto address = awaited; for (const auto& record : writeHistory()) { if (record.length != 0 && record.target <= address && address < record.target + std::max(record.length, 4)) std::fprintf(stderr, "[gpu] earlier write by queue 0x%x opcode 0x%x at 0x%llx+0x%llx\n", record.queue, record.opcode, static_cast(record.target), static_cast(record.length)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 61cb01ec34bb..07371b74d014 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -456,7 +456,6 @@ void Validate(std::span packet, std::uint32_t queue) { size(opcode == 0x3c ? 7 : 9); require((packet[1] & 0x10u) != 0, "register-space WAIT_REG_MEM is not implemented"); require((packet[1] & 7u) <= 6u, "invalid WAIT_REG_MEM compare function"); - require((packet[2] & (opcode == 0x3c ? 3u : 7u)) == 0, "misaligned WAIT_REG_MEM address"); break; } case 0x45: { @@ -548,20 +547,27 @@ bool waitCompares(std::span packet, bool wide, std::uint64_ } +std::size_t WaitAwaitedBytes(std::span packet) { + const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; + return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; +} + bool WaitSatisfied(std::span packet) { const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; - const auto source = address(packet[2], packet[3]); + const auto awaitedBytes = WaitAwaitedBytes(packet); + const auto source = address(packet[2] & (awaitedBytes == 8 ? ~7u : ~3u), packet[3]); std::uint64_t value = 0; // Named for the [hooksync] attribution (the read goes through the flush hook). const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); - GuestMemory::Read(source, std::as_writable_bytes(std::span(&value, 1)).first(wide ? 8 : 4), wide ? 8 : 4); + GuestMemory::Read(source, std::as_writable_bytes(std::span(&value, 1)).first(awaitedBytes), awaitedBytes); return waitCompares(packet, wide, value); } bool WaitSatisfiedUnchecked(std::span packet) { const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; - const auto source = address(packet[2], packet[3]); - const std::uint64_t value = wide ? *reinterpret_cast(source) : *reinterpret_cast(source); + const auto awaitedBytes = WaitAwaitedBytes(packet); + const auto source = address(packet[2] & (awaitedBytes == 8 ? ~7u : ~3u), packet[3]); + const std::uint64_t value = awaitedBytes == 8 ? *reinterpret_cast(source) : *reinterpret_cast(source); return waitCompares(packet, wide, value); } @@ -570,11 +576,6 @@ bool WaitComparesValue(std::span packet, std::uint64_t valu return waitCompares(packet, wide, value); } -std::size_t WaitAwaitedBytes(std::span packet) { - const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; - return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; -} - std::size_t ConditionalWords(std::span packet) { require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); return packet[4] & ConditionalWordsMask; @@ -968,7 +969,7 @@ void Execute(std::span packet, QueueState& queue) { if (!warned && std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) { warned = true; std::fprintf(stderr, "[gpu] WAIT_REG_MEM at 0x%llx still waiting after 5s (function %u ref 0x%x mask 0x%x)\n", - static_cast(address(packet[2], packet[3])), packet[1] & 7u, packet[4], packet[5]); + static_cast(address(packet[2] & (WaitAwaitedBytes(packet) == 8 ? ~7u : ~3u), packet[3])), packet[1] & 7u, packet[4], packet[5]); } } return; From 2db8dbcb80701bc8f72b718370fe810bfb94e1fb Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:13:23 -0500 Subject: [PATCH 155/176] fix(agc): drop unused wide flag after the WAIT_REG_MEM address masking pick Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj --- .../Execution/src/Driver/Synchronization/WaitMemory.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp index da33d3a361ab..83ea44f742dc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -110,7 +110,6 @@ void Driver::waitMemory(std::span packet, std::uint32_t que GuestMemory::TagGpuLockThread(queue); auto& outcomes = waitOutcomes(); - const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; const std::size_t awaitedBytes = Pm4::WaitAwaitedBytes(packet); const auto alignMask = awaitedBytes == 8 ? ~7ull : ~3ull; const std::uint64_t awaited = (packet[2] & static_cast(alignMask)) | (static_cast(packet[3]) << 32u); From e094fb91e291caf61970b9f067d2c745e8de9b64 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:14:36 -0500 Subject: [PATCH 156/176] fix(metal): keep the position export layout when the vertex program tail does not decode Metal port of upstream b1984ce8d (fix(agc): preserve position export layout on undecodable tail), applied to the Metal copies of PositionExportLayout. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj --- .../Execution/Metal/MetalDrawAhead.mm | 14 ++++++++++---- .../Execution/Metal/MetalPacketExecution.mm | 13 +++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm index d44540fa483c..1c07178a3a9f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm +++ b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm @@ -10,6 +10,7 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include #include #include #include @@ -23,10 +24,15 @@ std::uint32_t PositionExportLayout(std::span code, std::uint32_t paClVsOutCntl) { const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits; if (bits == 0) return 0; - const auto decoded = RdnaInstructionDecoder{}.Decode(code); - const bool used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { - return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; - }); + bool used = true; + try { + const auto decoded = RdnaInstructionDecoder{}.Decode(code); + used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { + return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; + }); + } catch (const std::invalid_argument& error) { + if (std::strstr(error.what(), "opcode is not implemented") == nullptr) throw; + } return used ? bits : 0; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm index e9940bf1b864..4cacdf80ea23 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm +++ b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm @@ -228,10 +228,15 @@ bool reportSkipCount(std::uint64_t count) { std::uint32_t PositionExportLayout(std::span code, std::uint32_t paClVsOutCntl) { const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits; if (bits == 0) return 0; - const auto decoded = RdnaInstructionDecoder{}.Decode(code); - const bool used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { - return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; - }); + bool used = true; + try { + const auto decoded = RdnaInstructionDecoder{}.Decode(code); + used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { + return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; + }); + } catch (const std::invalid_argument& error) { + if (std::strstr(error.what(), "opcode is not implemented") == nullptr) throw; + } return used ? bits : 0; } From c725e7423a06360b8d3497aab19589971cf176b8 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:14:49 -0500 Subject: [PATCH 157/176] fix(agc): include optional and string in the shader registry Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj --- .../Execution/src/Driver/Shaders/ShaderRegistry.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 6f767c93912a..3978ec70f183 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -28,9 +28,11 @@ #include #include #include +#include #include #include #include +#include #ifdef __GLIBC__ #include #endif From f4bf2677bfe7a1ede40b77f176e9fc72a2f101a9 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:18:35 -0500 Subject: [PATCH 158/176] fix(sync): resolve Batch 42 cross-slice duplicates Drop the second ParseInteger copy (main already carries it) and list the sceRtcInit/sceRtcEnd silent stubs as upstream's debt doc does. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/SonyCrt.hpp | 14 -------------- docs/dev/debt/libSceRtc.md | 4 ++++ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index 4cccc3a1609e..dee6a42018b9 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -59,20 +59,6 @@ inline SonyConstraintHandler& SonyConstraintHandlerSlot() { namespace { -template -inline TInteger ParseInteger(const char* text, char** end, int base, TInteger (*parse)(const char*, char**, int)) { -#ifdef __APPLE__ - const int saved = errno; - char* parsedEnd = nullptr; - const TInteger value = parse(text, &parsedEnd, base); - if (parsedEnd == text && errno == EINVAL && (base == 0 || (base >= 2 && base <= 36))) errno = saved; - if (end != nullptr) *end = parsedEnd; - return value; -#else - return parse(text, end, base); -#endif -} - inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int error) { SonyConstraintHandler handler = nullptr; { diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index 4e838eb494ae..8f90cdda7fb2 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -1,5 +1,9 @@ # libSceRtc technical debt +### Silent stubs + +- [sceRtcInit, sceRtcEnd](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - return 0 and do nothing, as the console functions do: both are a bare `xor eax, eax; ret` in libSceRtc.sprx on firmware 13.60 + ### Unknown function info - [sceRtcParseDateTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console's zone name table is only known from the names measured on PS5 firmware 13.60 and 13.42; any other name is assumed to take the military letter offset of its first character, as every measured name outside the table does (`HST`, `MSK`, `XYZ`), so a name the console's table holds but nobody measured gets the wrong offset From 6ab37b1a4065e5731ceebadfe6c96b1a4af7ddb0 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:50:27 -0500 Subject: [PATCH 159/176] revert(libScePad): drop duplicate firmware pad interface stubs Reverts 3fc8b2b89: all 149 stubs it appends already exist in the fork's Export.cpp (redefinition errors). Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libScePad/Export.cpp | 745 ----------------------------- 1 file changed, 745 deletions(-) diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index b9249868ed52..667dfcff49cc 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -1071,749 +1071,4 @@ int APS5_VABI scePadControllerUpdateTerm(void) { return 0; } -int APS5_VABI scePadSetTriggerEffectExt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadLastLotusButtonData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadReadStateExt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetExtensionReport(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetExtensionUnitInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnableExtensionPort(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnableSpecificDeviceClass(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadOpenExt2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDeviceOpen(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetDataInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetUserColor(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableVibration(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetVibrationStrength(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLightBarBaseBrightness(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLightBarBlinking(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetIdleCount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetControllerIdleCount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetPowerSavingState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetAutoPowerOffCount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnableUsbConnection(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnableAutoDetect(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnablePlayPauseEmulation(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableTrackingLedBatterySaver(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLoginUserNumber(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetCapability(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadShareOutputData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadShareOutputDataWithFunctionType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetVibrationForce(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetVersionInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetControllerType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableDataConversion(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetButtonRemappingInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetProcessPrivilegeOfButtonRemapping(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetForceIntercepted(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsLightBarBaseBrightnessControllable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSwitchConnection(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableTriggerEffect(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetTriggerEffectStrength(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetTriggerEffectInternalState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetUserNumber(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableTransportButtons(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLotusIrCodeSetStart(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLotusIrCodeSetData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLotusIrCodeSetEnd(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetLotusIrCodeSetResult(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLotusEnableIrKey(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLotusDisableIrKey(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetUsbDetectionState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadUiReadState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadUiReadStateForTuning(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetStickTriggerTuningMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetStickTuningParam(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetTriggerTuningParam(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetProfileHunt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetProfileHunt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadResetHunt(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetProfileInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetActiveProfileInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableActiveProfileChange(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisableActiveProfileChangeByDeviceId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetActiveProfileSlot(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetSavedSettingInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetSavedSettingInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVrControllerGetPhysicalUsbConnectionStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadParseUniqueDataLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetControllerTypeLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetControllerTypeLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetProfileLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetProfileLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadResetLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetProfileEditMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetProfileEditMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetProfileEditModeLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetSelectButtonCustomizeMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetSelectButtonCustomizeMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetToggleAllReleaseLeonardo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnableControllerIndicator(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnableProfileIndicator(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetProfileIndicatorAnimation(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsCoPilot(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetIsCoPilot(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadRegisterCollaboInputMgrProcess(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetTelemetryInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetActiveTime(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetDeviceId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetInfoByPortType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadResetLightBarAll(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadResetLightBarAllByPortType(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetLightBarForTracker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadReadForTracker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadReadBlasterForTracker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadResetOrientationForTracker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetMotionTimerUnit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetVrTrackingMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetMotionSensorPosition(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetSphereRadius(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsValidHandle(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsDS4Connected(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsBlasterConnected(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsMoveConnected(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetBluetoothAddress(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadIsMoveReproductionModel(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadReadAstonForTracker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetAstonModel(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadMbusInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadMbusTerm(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadConnectPort(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisconnectPort(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadDisconnectDevice(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetDeviceInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetProcessFocus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVertualDeviceAddDevice(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceAddDevice(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceDeleteDevice(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceInsertData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceInsertTriggerInternalState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceGetRemoteSetting(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceSetTriggerEffectSetting(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadVirtualDeviceDisableButtonRemapping(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetShareButtonMaskForRemotePlay(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetPS4BcVibrationMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetVirtualDeviceComponentInformation(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadEnablePs4CompatibleMode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetLicenseControllerInformation(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadReadHistory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadStartRecording(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadStopRecording(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadRemoteControlInsertData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadRemoteControlInsertDataForAston(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadGetFeatureReport2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetFeatureReport2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadSetMuteLed(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateIsReady(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateStart(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateGetStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateSendData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateResetBond(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateGetReconnectStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadBondUpdateTerm(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateIsUpdateNeeded2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateIsReady(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateStart(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateGetStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateSendData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateResetController(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateGetReconnectStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI scePadControllerUpdateTerm(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } From ee1c2111134637a1c9d84954e450eb57f4776e3b Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:50:27 -0500 Subject: [PATCH 160/176] fix(libc): keep one __fpclassifyl export Upstream moved it to MathExact.cpp (raw x87 bits, pseudo-denormal aware); drop the older Math.cpp copy. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/Math.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index ab7442b936ec..e855820768f9 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -625,10 +625,6 @@ int APS5_VABI isnanf_nid_postfix(float x) { return __isnanf_nid_postfix(x); } int APS5_VABI isnan_nid_postfix(double x) { return __isnan_nid_postfix(x); } int APS5_VABI isinf_nid_postfix(double x) { return __isinf_nid_postfix(x); } int APS5_VABI __fpclassifyf_nid_postfix(float x) { return SonyFpclassify(x); } -int APS5_VABI __fpclassifyl_nid_postfix(long double x) { - static_assert(sizeof(long double) == 16, "guest long double requires x87 extended precision storage"); - return SonyFpclassify(x); -} int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } int APS5_VABI __signbitl_nid_postfix(long double x) { static_assert(sizeof(long double) == 16, "guest long double requires x87 extended precision storage"); From a8f49e73fbbc0813dcec1ff89f8a0a72ba681f9e Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:05:37 -0500 Subject: [PATCH 161/176] fix(libkernel_sys): define GuestEbadf once Two Batch 42 picks each added it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel_sys/Export.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 9719ebff0e73..c740a96ee513 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -60,7 +60,6 @@ constexpr int GuestEperm = 1; constexpr int GuestEio = 5; constexpr int GuestEbadf = 9; constexpr int GuestEfault = 14; -constexpr int GuestEbadf = 9; constexpr int GuestEnotdir = 20; constexpr int GuestEisdir = 21; constexpr int GuestEinval = 22; From ab86088c0b4bce9cad4a3838cc56e17f88cd4acd Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:09:46 -0500 Subject: [PATCH 162/176] revert(libSceAudioPropagation): drop duplicate firmware function stubs Reverts 264467d0a: all 20 stubs it appends already exist in the fork's Export.cpp (redefinition errors). Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAudioPropagation/Export.cpp | 100 ------------------ 1 file changed, 100 deletions(-) diff --git a/core/libs/prx/libSceAudioPropagation/Export.cpp b/core/libs/prx/libSceAudioPropagation/Export.cpp index 0d5ea8424bff..474117fe6fec 100644 --- a/core/libs/prx/libSceAudioPropagation/Export.cpp +++ b/core/libs/prx/libSceAudioPropagation/Export.cpp @@ -415,104 +415,4 @@ int APS5_VABI sceAudioPropagationSystemUnlock() { return 0; } -int APS5_VABI sceAudioPropagationPathGetNumPoints() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationReportApi() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationResetAttributes() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSourceQueryInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemLock() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemQueryInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationAudioPathInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationAudioPathPointInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationMaterialInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationPortalSettingsInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationRayInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSourceRenderInfoInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSourceSetAudioPathsParamInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystem() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemMemoryInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemOptionInit() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationReportRegisterHandler() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationReportUnregisterHandler() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemEnumHandles() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAudioPropagationSystemUnlock() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } From fd3402589cac81648cebc0de843764f077013069 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:10:18 -0500 Subject: [PATCH 163/176] revert(relinker): keep the fork's macOS image base source Reverts 96e373f8a: it calls WindowsLoadImage::GetImageBase, which the fork's load image does not have. Pure refactor, no behaviour lost. Co-Authored-By: Claude Opus 5.5 --- .../elfpatcher/src/macos/MacOsImage.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 8b0800faa95c..81fc38d4db65 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -20,6 +20,7 @@ namespace Elfpatcher::MacOs { namespace { +using Windows::ImageBase; using Windows::LoadRva; constexpr std::uint64_t PageSize = 0x1000; @@ -380,7 +381,6 @@ std::uint64_t GuestTlsBlockSize(const Domain::ProgramHeader& tls) { std::vector WriteMacOsImage(MacOsImageInput& input) { auto& image = *input.Image; const auto& headers = *input.Headers; - const auto imageBase = image.GetImageBase(); const auto tls = ScanTls(*input.Source, headers, image); const bool hasTls = tls.Tls != nullptr && tls.Tls->MemorySize != 0; if (!hasTls && !tls.Accesses.empty()) throw Domain::RelinkerException("Guest TLS access without a non-empty PT_TLS"); @@ -436,7 +436,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { Io::WriteU64(meta, 0, MetaVersion); const auto firstLoad = std::find_if(headers.begin(), headers.end(), [](const auto& header) { return header.Type == 1; }); if (firstLoad == headers.end()) throw Domain::RelinkerException("No PT_LOAD segments found"); - Io::WriteU64(meta, MetaLoadBias, imageBase + image.GetRva(firstLoad->MappedAddress) - firstLoad->MappedAddress); + Io::WriteU64(meta, MetaLoadBias, ImageBase + image.GetRva(firstLoad->MappedAddress) - firstLoad->MappedAddress); input.Rebases.push_back(metaRva + MetaLoadBias); Io::WriteU64(meta, MetaHeaderCount, headers.size()); for (std::size_t index = 0; index < headers.size(); ++index) { @@ -478,7 +478,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { if (!hasTls) throw Domain::RelinkerException("Guest TLS export has no TLS block: " + variable.Symbol); if (variable.Offset >= tls.Tls->MemorySize) throw Domain::RelinkerException("Guest TLS export is outside the TLS block: " + variable.Symbol); const auto at = tlsExports + index * 16; - Io::WriteU64(meta, at, imageBase + descriptor); + Io::WriteU64(meta, at, ImageBase + descriptor); Io::WriteU64(meta, at + 8, variable.Offset); input.Rebases.push_back(metaRva + static_cast(at)); input.Exports.push_back({variable.Symbol, metaRva + static_cast(at), variable.Flags}); @@ -498,7 +498,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { } Io::WriteU64(meta, MetaTlsDescriptor, 1); if (header.FileSize != 0) { - Io::WriteU64(meta, MetaTlsDescriptor + 8, imageBase + image.GetRva(header.MappedAddress, header.FileSize)); + Io::WriteU64(meta, MetaTlsDescriptor + 8, ImageBase + image.GetRva(header.MappedAddress, header.FileSize)); input.Rebases.push_back(descriptor + 8); } Io::WriteU64(meta, MetaTlsDescriptor + 0x10, header.FileSize); @@ -508,7 +508,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { input.Binds.push_back({"_Aps5GuestTlsRegister_nid_no_patch", metaRva + static_cast(MetaRegisterSlot)}); for (const auto slot : input.TlsModuleSlots) { auto& segment = SegmentAt(segments, slot, 8); - Io::WriteU64(segment.Data, slot - segment.Rva, imageBase + descriptor); + Io::WriteU64(segment.Data, slot - segment.Rva, ImageBase + descriptor); input.Rebases.push_back(slot); } @@ -623,7 +623,7 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { code.Rip({0xff, 0x15}, metaRva + static_cast(MetaCxaAtexitSlot)); } code.Emit({0x5d, 0xc3}); - Io::WriteU64(meta, initPointer, imageBase + initStub); + Io::WriteU64(meta, initPointer, ImageBase + initStub); input.Rebases.push_back(metaRva + static_cast(initPointer)); dataSections.push_back({"__mod_init_func", initPointer, 8, SectionModInitFuncPointers, 3}); } @@ -822,19 +822,19 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { Io::WriteU64(commands, at + 48, filesize); }; std::size_t index = 0; - if (input.Executable) writeSegment(segmentCommandOffsets[index++], 0, imageBase, 0, 0); - writeSegment(segmentCommandOffsets[index++], imageBase, LoadRva, 0, LoadRva); + if (input.Executable) writeSegment(segmentCommandOffsets[index++], 0, ImageBase, 0, 0); + writeSegment(segmentCommandOffsets[index++], ImageBase, LoadRva, 0, LoadRva); for (; index < segments.size(); ++index) { const auto& segment = segments[index]; - writeSegment(segmentCommandOffsets[index], imageBase + segment.Rva, AlignUp(segment.MemorySize, PageSize), segment.FileOffset, segment.Data.size()); + writeSegment(segmentCommandOffsets[index], ImageBase + segment.Rva, AlignUp(segment.MemorySize, PageSize), segment.FileOffset, segment.Data.size()); for (std::size_t sectionIndex = 0; sectionIndex < segment.Sections.size(); ++sectionIndex) { const auto& section = segment.Sections[sectionIndex]; const auto at = segmentCommandOffsets[index] + 72 + sectionIndex * 80; - Io::WriteU64(commands, at + 32, imageBase + segment.Rva + section.Offset); + Io::WriteU64(commands, at + 32, ImageBase + segment.Rva + section.Offset); Io::WriteU32(commands, at + 48, static_cast(segment.FileOffset + section.Offset)); } } - writeSegment(linkeditCommand, imageBase + imageEnd, AlignUp(linkeditSize, PageSize), linkeditOffset, linkeditSize); + writeSegment(linkeditCommand, ImageBase + imageEnd, AlignUp(linkeditSize, PageSize), linkeditOffset, linkeditSize); Io::WriteU32(commands, dyldInfoCommand + 8, static_cast(rebaseOffset)); Io::WriteU32(commands, dyldInfoCommand + 12, static_cast(rebase.size())); Io::WriteU32(commands, dyldInfoCommand + 16, static_cast(bindOffset)); From c19ae352ba99a7ac822decdc16a6d0c8432a9320 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:10:23 -0500 Subject: [PATCH 164/176] fix(agc): match fork PreparedGraphicsStage fields in deferred stage pushes The deferred-registration pushes used upstream's five-field initializer; the fork's PreparedGraphicsStage has only snapshot and entry. Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/Driver/Shaders/ShaderRegistry.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 3978ec70f183..d7dac242f48b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1061,7 +1061,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod const auto offset = pushOffset; const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes; if (deferUnprepared && DeferFailedRegistration(*program.snapshot)) { - prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}}); continue; } std::optional vertex; @@ -1070,7 +1070,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } catch (const std::exception& error) { if (!deferUnprepared) throw; DeferPipelineStage(transaction.Edit(*program.snapshot), *program.snapshot, error.what()); - prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}}); continue; } ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, offset, room}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; @@ -1090,7 +1090,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } catch (const std::exception& error) { if (!deferUnprepared) throw; DeferPipelineStage(transaction.Edit(*program.snapshot), *program.snapshot, error.what()); - prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}}); continue; } } From 448fb30e3b93e619b213c18b6fc5ab0e6045333a Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:10:47 -0500 Subject: [PATCH 165/176] fix(relinker): accept section-symbol RELATIVE relocations in the fork check Upstream's section-relative fixture uses a section symbol (Info 3, value 0) with a non-zero addend, which the fork's symbol==addend check rejected. Drop the now-dead info rewrite for symbol-less RELATIVE entries. Co-Authored-By: Claude Opus 5.5 --- core/relinker/relinker/src/guest/GuestImageReader.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index 48e4d6ebac88..1a018d30dd76 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -310,7 +310,8 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (relocation == 8) { if ((info >> 32) != 0) { const auto& symbol = image.Symbols[info >> 32]; - if (symbol.Section == 0 || addend > static_cast(std::numeric_limits::max()) || symbol.Value != addend) + const bool sectionSymbol = symbol.Info == 3 && symbol.Value == 0; + if (!sectionSymbol && (symbol.Section == 0 || addend > static_cast(std::numeric_limits::max()) || symbol.Value != addend)) fail("RELATIVE relocation has a symbol"); mapped(addend, 0, 0); Io::AppendU64(output, target); @@ -326,9 +327,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const auto& symbol = image.Symbols[info >> 32]; if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation"); } - const auto position = output.size(); output.insert(output.end(), bytes.begin() + offset + index, bytes.begin() + offset + index + 24); - if (relocation == 8) Io::WriteU64(output, position + 8, 8); } }; if (tags.contains(7) || tags.contains(0x6100002f)) { From b0709e1a21a23381f6f2d0c96526014f3a07f897 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:11:29 -0500 Subject: [PATCH 166/176] fix(agc): mask WAIT_REG_MEM address at every reader Pm4::Validate now accepts misaligned wait addresses, but the Metal pending label check, DispatchAhead and the draw-pipeline keep check still used the raw address. Add Pm4::WaitAddress and use it at all WAIT_REG_MEM readers. Co-Authored-By: Claude Opus 5.5 --- .../Execution/Metal/MetalPacketExecution.mm | 4 ++-- .../libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp | 2 ++ .../Execution/src/Driver/Dispatch/DispatchAhead.cpp | 2 +- .../Execution/src/Driver/Packets/PacketExecution.cpp | 4 ++-- .../src/Driver/Synchronization/LabelHistory.cpp | 2 +- .../src/Driver/Synchronization/WaitMemory.cpp | 3 +-- core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp | 10 +++++++--- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm index 4cacdf80ea23..3ac37aac1bd5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm +++ b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm @@ -949,7 +949,7 @@ bool reportSkipCount(std::uint64_t count) { } bool MetalDriver::Impl::PendingLabelSatisfies(std::span packet) const { - const auto address = static_cast(packet[2]) | (static_cast(packet[3]) << 32u); + const auto address = Pm4::WaitAddress(packet); const auto bytes = Pm4::WaitAwaitedBytes(packet); for (auto label = pendingLabels.rbegin(); label != pendingLabels.rend(); ++label) { if (address + bytes <= label->address || label->address + label->size <= address) continue; @@ -1169,7 +1169,7 @@ bool reportSkipCount(std::uint64_t count) { // ours the guest CPU waits on before it writes this one still reach it. A value our own // pending work may still write drains first, so the wait never passes on the bytes from // before that work. ANYPS5_METAL_GPU_WAITS=0 always drains first, as before. - const auto address = static_cast(packet[2]) | (static_cast(packet[3]) << 32u); + const auto address = Pm4::WaitAddress(packet); const auto awaitedEnd = address + Pm4::WaitAwaitedBytes(packet); const bool oursPending = timeline.MayHaveWritten(address, awaitedEnd) || std::any_of(pendingLabels.begin(), pendingLabels.end(), [&](const PendingLabel& label) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index c8522dc93866..980c4734b8df 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -107,6 +107,8 @@ bool WaitSatisfiedUnchecked(std::span packet); // holds will store), without reading memory. bool WaitComparesValue(std::span packet, std::uint64_t value); std::size_t WaitAwaitedBytes(std::span packet); +// The awaited address with the low bits the hardware ignores for the compare width masked off. +std::uint64_t WaitAddress(std::span packet); std::size_t ConditionalWords(std::span packet); std::uint32_t ReadCondition(std::span packet); // A label write (RELEASE_MEM with a data select, WRITE_DATA to memory): the destination and the diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp index f268e7dfaebb..0fba6c571bf9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp @@ -70,7 +70,7 @@ bool OverlapsRange(std::span regions, cons } bool WrittenByRun(std::span wait, const std::vector& labels) { - const std::uint64_t awaited = wait[2] | (static_cast(wait[3]) << 32u); + const std::uint64_t awaited = Pm4::WaitAddress(wait); const auto bytes = Pm4::WaitAwaitedBytes(wait); for (auto it = labels.rbegin(); it != labels.rend(); ++it) { if (it->address != awaited || it->size < bytes) continue; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index f4efaaf0477e..6c6d497a6cde 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -36,7 +36,7 @@ bool keepsDrawPipeline(std::span packet, std::uint32_t head return packet.size() >= 5 && !DrawPipeline::Queue0().Overlaps(packet[1] | (static_cast(packet[2]) << 32u), static_cast(packet[4]) * 8); case 0x3c: case 0x93: { if (packet.size() < 5) return false; - const std::uint64_t awaited = packet[2] | (static_cast(packet[3]) << 32u); + const std::uint64_t awaited = Pm4::WaitAddress(packet); const auto bytes = Pm4::WaitAwaitedBytes(packet); if (DrawPipeline::Queue0().Overlaps(awaited, bytes)) return false; GuestMemory::CheckRange(reinterpret_cast(awaited), bytes, bytes); @@ -210,7 +210,7 @@ void Driver::execute(const Submission& submission) { } if (pipelined && !predicated && (opcode == 0x3c || opcode == 0x93) && header != RenderingWaitPacketHeader && orderedWaits && DrawPipeline::Queue0().Busy()) { const auto awaitedBytes = Pm4::WaitAwaitedBytes(packet); - const std::uint64_t awaited = (packet[2] & (awaitedBytes == 8 ? ~7u : ~3u)) | (static_cast(packet[3]) << 32u); + const std::uint64_t awaited = Pm4::WaitAddress(packet); const auto value = DrawPipeline::Queue0().PendingLabel(awaited, awaitedBytes); if (value.has_value() && Pm4::WaitComparesValue(packet, *value)) { ++waitOutcomes().fromRecorderSameQueue; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp index 27963f67d944..9e3de640fb08 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp @@ -50,7 +50,7 @@ void Driver::traceLabel(std::span packet, std::uint32_t que const char* kind = nullptr; if (opcode == 0x49 && packet.size() >= 7) { target = packet[3] | (static_cast(packet[4]) << 32u); kind = "RELEASE_MEM"; } else if (opcode == 0x37 && packet.size() >= 5) { target = packet[2] | (static_cast(packet[3]) << 32u); kind = "WRITE_DATA"; } - else if ((opcode == 0x3c || opcode == 0x93) && packet.size() >= 7) { target = (packet[2] & (Pm4::WaitAwaitedBytes(packet) == 8 ? ~7u : ~3u)) | (static_cast(packet[3]) << 32u); kind = "WAIT_REG_MEM"; } + else if ((opcode == 0x3c || opcode == 0x93) && packet.size() >= 7) { target = Pm4::WaitAddress(packet); kind = "WAIT_REG_MEM"; } else if (opcode == 0x40 && packet.size() >= 6) { target = packet[4] | (static_cast(packet[5]) << 32u); kind = "COPY_DATA"; } std::uint64_t length = 4; if (opcode == 0x50 && packet.size() >= 7) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp index 83ea44f742dc..03330e057f4f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -111,8 +111,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que GuestMemory::TagGpuLockThread(queue); auto& outcomes = waitOutcomes(); const std::size_t awaitedBytes = Pm4::WaitAwaitedBytes(packet); - const auto alignMask = awaitedBytes == 8 ? ~7ull : ~3ull; - const std::uint64_t awaited = (packet[2] & static_cast(alignMask)) | (static_cast(packet[3]) << 32u); + const std::uint64_t awaited = Pm4::WaitAddress(packet); static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x waits 0x%llx == 0x%x (now 0x%x)\n", TraceMs(), queue, static_cast(awaited), packet[4], *reinterpret_cast(awaited)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 07371b74d014..beb60f630d22 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -552,10 +552,14 @@ std::size_t WaitAwaitedBytes(std::span packet) { return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; } +std::uint64_t WaitAddress(std::span packet) { + return address(packet[2] & (WaitAwaitedBytes(packet) == 8 ? ~7u : ~3u), packet[3]); +} + bool WaitSatisfied(std::span packet) { const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; const auto awaitedBytes = WaitAwaitedBytes(packet); - const auto source = address(packet[2] & (awaitedBytes == 8 ? ~7u : ~3u), packet[3]); + const auto source = WaitAddress(packet); std::uint64_t value = 0; // Named for the [hooksync] attribution (the read goes through the flush hook). const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); @@ -566,7 +570,7 @@ bool WaitSatisfied(std::span packet) { bool WaitSatisfiedUnchecked(std::span packet) { const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; const auto awaitedBytes = WaitAwaitedBytes(packet); - const auto source = address(packet[2] & (awaitedBytes == 8 ? ~7u : ~3u), packet[3]); + const auto source = WaitAddress(packet); const std::uint64_t value = awaitedBytes == 8 ? *reinterpret_cast(source) : *reinterpret_cast(source); return waitCompares(packet, wide, value); } @@ -969,7 +973,7 @@ void Execute(std::span packet, QueueState& queue) { if (!warned && std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) { warned = true; std::fprintf(stderr, "[gpu] WAIT_REG_MEM at 0x%llx still waiting after 5s (function %u ref 0x%x mask 0x%x)\n", - static_cast(address(packet[2] & (WaitAwaitedBytes(packet) == 8 ? ~7u : ~3u), packet[3])), packet[1] & 7u, packet[4], packet[5]); + static_cast(WaitAddress(packet)), packet[1] & 7u, packet[4], packet[5]); } } return; From ef0985b6c11c4adc8af19121590d10b8b87f499b Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:11:40 -0500 Subject: [PATCH 167/176] fix(agc): normalize unused viewport depth in the Metal draw decode DecodeDrawDispatch is the fork-only decode Metal uses; upstream's NormalizeUnusedViewportDepth was only added to the Vulkan DrawDecode path. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp index 6fb75d515aab..961734aaaf54 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp @@ -141,6 +141,7 @@ DrawDispatchState DecodeDrawDispatch(const QueueState& queue, const DriverDetail product.state = Graphics::DecodeState(queue, vertexWorkgroup); DecodeDrawPrograms(product.state, queue, shaders, nullPixelProgramAddress, false, true, product.programs, product.roles); product.pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product.state), Graphics::PixelProgramSkipped(queue)); + Graphics::NormalizeUnusedViewportDepth(product.state, product.pixel); // The kept pixel ancillary word carries the primitive class (upstream 18d9874a0); the driver path sets it in DrawDecode. if (product.pixel.ancillary) product.pixel.rasterPrimitive = Graphics::RasterPrimitiveOf(product.state); return product; From 16b9c6ad0fa9b30028512d37fa9bdc3cab0ac3c8 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:11:49 -0500 Subject: [PATCH 168/176] revert(agc): drop the FMASK image read reproduction test Reverts 57466a015 (upstream e8544ef03). Its fix, upstream caae92c12, does not apply to the fork (ShaderMemory.cpp, ResourceMaterializer.cpp, SpirvImageEmitter.cpp, SpirvDescriptors.cpp conflict), so the globbed test would fail. Re-add it together with a port of caae92c12. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/FmaskImageReads.cpp | 144 ------------------ 1 file changed, 144 deletions(-) delete mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp deleted file mode 100644 index 5416e5189004..000000000000 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FmaskImageReads.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" -#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" -#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" -#include "Recompiler.hpp" -#include "VulkanTestDevice.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -using namespace AgcDriver::Graphics; - -constexpr std::uint32_t Width = 128; -constexpr std::uint32_t Height = 64; -constexpr std::uint32_t Threads = 32; -constexpr std::uint32_t Sentinel = 0xdeadbeef; -alignas(4096) std::array TexelStorage{}; -const auto Texels = std::span(TexelStorage).subspan((0u - reinterpret_cast(TexelStorage.data())) & 0xffffu, 3 * 65536); -alignas(256) std::array Input{}; -alignas(256) std::array Output{}; -alignas(256) constexpr std::array Code{0x34020084u, 0xe0381000u, 0x80000201u, 0xbf8c3f70u, 0xf0001f08u, 0x00020602u, 0xbf8c3f70u, 0xe0781000u, 0x80010601u, 0xbf810000u}; - -std::array Buffer(const void* data, std::uint32_t bytes) { - const auto address = reinterpret_cast(data); - return {static_cast(address), static_cast(address >> 32u) & 0xffffu, bytes, 0x31016facu}; -} - -std::array Image(bool layered, std::uint32_t pipeXor, std::uint32_t swizzle = 4u) { - const auto address = reinterpret_cast(Texels.data()) + pipeXor; - return {static_cast(address >> 8u), static_cast(address >> 40u) | (166u << 20u) | (3u << 30u), 31u | ((Height - 1u) << 14u), swizzle | (24u << 20u) | ((layered ? 13u : 9u) << 28u), layered ? 2u | (1u << 16u) : 0u, 0u, 0u, 0u}; -} - -std::uint32_t Mapping(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t round) { - if (round == 0) return 0u; - if (round == 1) return 0x11111111u; - if (round == 2) return 0x11111100u; - std::uint32_t word = 0; - for (std::uint32_t sample = 0; sample < 8; ++sample) word |= ((x + 3u * y + 5u * layer + sample + round) & 7u) << (sample * 4u); - return word; -} - -std::uint64_t EquationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t layer) { - std::uint64_t offset = 0; - for (std::uint32_t bit = 0; bit < 16; ++bit) { - const auto mask = equation.bits[bit]; - const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12u) & (mask & 0xfff000u)) ^ ((layer << 24u) & (mask & 0xff000000u)); - offset |= static_cast(std::popcount(selected) & 1u) << bit; - } - return offset; -} - -void Fill(bool layered, std::uint32_t pipeXor, std::uint32_t round) { - auto words = Image(layered, pipeXor); - words[1] = (words[1] & ~(0x1ffu << 20u)) | (20u << 20u); - const auto resource = DecodeTextureResource(words); - const auto geometry = DescribeSurface(resource); - Require(geometry.guestBytes <= Texels.size(), "FMASK fixture exceeds its allocation"); - const auto& mip = geometry.mips.at(0); - const auto block = ThinBlockLayout(resource.tileMode, 4u); - const auto* equation = FindTextureSwizzleEquation(24u, 4u); - Require(equation != nullptr, "FMASK fixture has no Z swizzle equation"); - std::ranges::fill(Texels, std::byte{0}); - for (std::uint32_t layer = 0; layer < geometry.layers; ++layer) { - for (std::uint32_t y = 0; y < Height; ++y) { - for (std::uint32_t x = 0; x < Width; ++x) { - auto offset = geometry.GuestLayerOffset(layer) + mip.tiledOffset; - if (mip.tail) offset += EquationOffset(*equation, x + mip.tailX, y + mip.tailY, layer) ^ pipeXor; - else offset += (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + (EquationOffset(*equation, x, y, layer) ^ pipeXor); - const auto value = Mapping(x, y, layer, round); - Require(offset + sizeof(value) <= Texels.size(), "FMASK fixture addresses outside its allocation"); - std::memcpy(Texels.data() + offset, &value, sizeof(value)); - } - } - } -} - -ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code, std::span image, bool registration) { - std::vector userData(16u); - const auto input = Buffer(Input.data(), sizeof(Input)); - const auto output = Buffer(Output.data(), Threads * 16u); - std::copy(input.begin(), input.end(), userData.begin()); - std::copy(output.begin(), output.end(), userData.begin() + 4u); - std::copy(image.begin(), image.end(), userData.begin() + 8u); - const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1u, 1u}, 0u, {false, false, false}, false, 1u}; - auto target = device.Target(); - target.registrationModules = registration; - ShaderRecompiler::RecompileRequest request{{ShaderRecompiler::ShaderStage::Compute, reinterpret_cast(code.data()), code, 0u, {}}, {32u, 0u, userData, compute, std::nullopt, std::nullopt, memory}, target, {0u, 0u, 0u, 128u}}; - return ShaderRecompiler::Recompile(request); -} - -void Run(AgcDriver::VulkanDevice& device, bool layered, bool registration, std::uint32_t pipeXor) { - auto code = Code; - if (layered) code[4] = 0xf0001f28u; - ShaderRecompiler::CompiledShaderArtifact first; - for (std::uint32_t round = 0; round < 5; ++round) { - Fill(layered, pipeXor, round); - for (std::uint32_t lane = 0; lane < Threads; ++lane) { - Input[lane * 4u] = (lane * 37u + 11u) % Width; - Input[lane * 4u + 1u] = (lane * 23u + 5u) % Height; - Input[lane * 4u + 2u] = layered ? lane & 1u : 0u; - Input[lane * 4u + 3u] = 0u; - } - Output.fill(Sentinel); - const auto swizzle = round == 4u ? 4u | (1u << 3u) | (4u << 6u) : 4u; - const auto shader = Compile(device, code, Image(layered, pipeXor, swizzle), registration); - if (first.variantId == 0u) first = shader; - else Require(shader.cacheHit && shader.variantId == first.variantId, "FMASK data or channel selectors changed the compiled shader artifact"); - device.Dispatch(shader, 1u, 1u, 1u); - device.WaitIdle(); - for (std::uint32_t lane = 0; lane < Threads; ++lane) { - const auto value = Mapping(Input[lane * 4u], Input[lane * 4u + 1u], layered ? Input[lane * 4u + 2u] + 1u : 0u, round); - const std::array expected{value, round == 4u ? 1u : 0u, round == 4u ? value : 0u, 0u}; - for (std::uint32_t component = 0; component < 4u; ++component) Require(Output[lane * 4u + component] == expected[component], "FMASK load did not read its mapping: round " + std::to_string(round) + ", lane " + std::to_string(lane) + ", component " + std::to_string(component)); - } - Require(std::all_of(Output.begin() + Threads * 4u, Output.end(), [](auto value) { return value == Sentinel; }), "FMASK load wrote outside its output"); - } -} - -} - -int main() { - try { - const auto device = OpenVulkanTestDevice(); - if (!device) return VulkanTestSkipped; - for (const bool registration : {false, true}) { - for (const bool layered : {false, true}) { - for (const auto pipeXor : {0u, 0xf00u}) Run(*device, layered, registration, pipeXor); - } - } - std::cout << "FMASK image reads passed: 40 dispatches, 5120 component checks\n"; - return 0; - } catch (const std::exception& error) { - std::cerr << error.what() << '\n'; - return 1; - } -} From 76e18137be3b3ffdd6d0813468e7e261fd77fd23 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:11:58 -0500 Subject: [PATCH 169/176] fix(agc): remove the duplicate LiveCounter in the graphics tests tests/Graphics.cpp defined struct LiveCounter twice (also on main), so the graphics test target could not compile. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/Graphics.cpp | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 463bc65b26a9..0a197cfe1b13 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2842,33 +2842,6 @@ struct LiveCounter { } }; -struct LiveCounter { - std::atomic value{0}; - LiveCounter() = default; - LiveCounter(const LiveCounter& other) : value(other.value.load()) {} - LiveCounter& operator=(const LiveCounter& other) { - value.store(other.value.load()); - return *this; - } - operator std::int64_t() const { return value.load(); } - LiveCounter& operator++() { - ++value; - return *this; - } - LiveCounter& operator--() { - --value; - return *this; - } - LiveCounter& operator+=(std::int64_t count) { - value += count; - return *this; - } - LiveCounter& operator-=(std::int64_t count) { - value -= count; - return *this; - } -}; - struct MockVulkan { std::uint64_t next = 1; LiveCounter live; From dc2d641216dcdf9bd28f7285342e4d5f9729fdfb Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:18:24 -0500 Subject: [PATCH 170/176] test(relinker): add upstream's guest_import_fixtures helper test_guest_relative_section_symbols and test_guest_none_relocations import it, but the fork never took the module (upstream 723d02c6b), so both failed with ModuleNotFoundError. Co-Authored-By: Claude Opus 5.5 --- .../relinker/tests/guest_import_fixtures.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 core/relinker/relinker/tests/guest_import_fixtures.py diff --git a/core/relinker/relinker/tests/guest_import_fixtures.py b/core/relinker/relinker/tests/guest_import_fixtures.py new file mode 100644 index 000000000000..45c69cbfb420 --- /dev/null +++ b/core/relinker/relinker/tests/guest_import_fixtures.py @@ -0,0 +1,64 @@ +import contextlib +import shutil +import struct +import tempfile +import time + +from test_guest_intel_trampolines import main_fixture + + +@contextlib.contextmanager +def fixture_directory(prefix): + directory = tempfile.mkdtemp(prefix=prefix) + try: + yield directory + finally: + deadline = time.monotonic() + 10 + while True: + try: + shutil.rmtree(directory) + break + except PermissionError: + if time.monotonic() >= deadline: + raise + time.sleep(0.05) + + +def dynamic(image, offset, phoff, tags): + for index, tag in enumerate(tags + [(0, 0)]): + struct.pack_into(' Date: Fri, 23 Oct 2026 03:13:30 -0500 Subject: [PATCH 171/176] fix(agc): wait for address-based writers before reading indirect dispatch arguments Hand port of upstream f6c8e8474. CPU reads of DISPATCH_INDIRECT arguments now finish leased batches (Graphics::FinishLeaseWork) so an address-based producer has run; a read-ahead whose counts differ from the re-read is dropped. Conflicts kept the fork's re-decode after adopting a parked packet and its NVIDIA split message (now with the CPU-read note). The new test drops upstream's code-address PrepareDispatch argument, which the fork's VulkanDevice does not take. Co-Authored-By: Claude Opus 5.5 --- .../Execution/include/Driver/Driver.hpp | 12 +- .../src/Driver/Dispatch/Dispatch.cpp | 9 +- .../src/Driver/Dispatch/DispatchAhead.cpp | 4 +- .../src/Driver/Dispatch/IndirectDispatch.cpp | 71 +++- .../Execution/src/VulkanDevice.cpp | 17 +- .../Graphics/include/GuestBufferMemory.hpp | 1 + .../Graphics/src/GuestBufferMemory.cpp | 11 + .../IndirectArgumentsPendingProducer.cpp | 365 ++++++++++++++++++ docs/dev/debt/libSceAgcDriver.md | 1 + 9 files changed, 469 insertions(+), 22 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index f64410a1980d..83a5fbfd02fb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -169,6 +169,16 @@ class Driver { static void countIndirect(int path, double readMs); static int indirectArgumentsPath(std::span packet, std::uint64_t arguments); void dispatchIndirect(QueueState& queue, std::span packet, const Submission& submission); + enum class AheadArguments { None, Matched, Differed }; + struct IndirectSource { + std::uint64_t arguments = 0; + int path = IndirectGpu; + std::uint32_t queue = 0; + AheadArguments ahead = AheadArguments::None; + }; + static IndirectSource& indirectSource(); + static std::string describeIndirectSource(); + std::array readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator); static bool& sampledRead(); static bool writeEvidenceEnabled(); static bool writerKeyedEvidence(); @@ -243,7 +253,7 @@ class Driver { std::chrono::steady_clock::time_point resolvedAt{}; }; struct GroupCaptureStats { - std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0; + std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0; double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0; std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now(); }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp index d47e7d5b6090..7bd917ba118b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp @@ -63,11 +63,10 @@ void Driver::dispatch(QueueState& queue, std::span packet, // The parked packet may differ from the entry packet (e.g. indirect arguments resolved ahead). decoded = DecodeComputeDispatch(queue, packet, snapshot.header, noteShaderRead); compute = decoded.compute; - } else if (indirectArguments != 0 && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) { - - recordQueuedLabelsBeforeRead(submission.queue); + } else if (indirectArguments != 0 && !resolving && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) { + indirectSource().path = IndirectFillKernel; const auto readStart = std::chrono::steady_clock::now(); - resolved = Pm4::ReadDispatchArguments(indirectArguments, packet[4]); + resolved = readDispatchArguments(submission.queue, indirectArguments, packet[4]); countIndirect(IndirectFillKernel, std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); packet = resolved; indirectArguments = 0; @@ -347,7 +346,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, } char where[64]; std::snprintf(where, sizeof(where), "compute shader 0x%llx: ", static_cast(address)); - throw std::runtime_error(where + std::string(error.what())); + throw std::runtime_error(where + std::string(error.what()) + describeIndirectSource()); }; phaseTiming.Phase(PhaseSnapshots); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp index 0fba6c571bf9..f1d0091178b6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp @@ -209,10 +209,10 @@ void Driver::reportGroupCapture(std::uint32_t queue) { auto& stats = groupCaptureStats(); const auto now = std::chrono::steady_clock::now(); if (now - stats.reported < std::chrono::seconds(10)) return; - AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device\n", + AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n", queue, static_cast(stats.runs), static_cast(stats.resolved), static_cast(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs, static_cast(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast(stats.adopted) : 0.0, stats.ageMaxMs, static_cast(stats.addressMismatches), static_cast(stats.keyMismatches), - static_cast(stats.failures), static_cast(stats.stale), static_cast(stats.labelOverlaps), static_cast(stats.writtenBefore), static_cast(stats.deviceRejects)); + static_cast(stats.failures), static_cast(stats.stale), static_cast(stats.labelOverlaps), static_cast(stats.writtenBefore), static_cast(stats.deviceRejects), static_cast(stats.argumentMismatches)); stats = GroupCaptureStats{}; stats.reported = now; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp index f567a93e2eb1..2806f9a0df0b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp @@ -1,10 +1,32 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/IndirectDispatch.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include #include namespace AgcDriver::DriverDetail { +namespace { + +const char* IndirectPathName(int path) { + switch (path) { + case IndirectGpu: return "gpu-side"; + case IndirectPendingImage: return "pending image results"; + case IndirectCopiedWrite: return "pending label or copied write"; + case IndirectNotImported: return "not imported"; + case IndirectThreadDimensions: return "thread dimensions"; + case IndirectFillKernel: return "fill kernel"; + case IndirectMisaligned: return "misaligned"; + case IndirectDisabled: return "disabled"; + case IndirectWorkgroupMemory: return "device memory LDS"; + default: return "unknown"; + } +} + +} + void Driver::countIndirect(int path, double readMs) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile || path < 0 || path >= IndirectPaths) return; @@ -31,23 +53,56 @@ int Driver::indirectArgumentsPath(std::span packet, std::ui return IndirectGpu; } +Driver::IndirectSource& Driver::indirectSource() { + static thread_local IndirectSource source; + return source; +} + +std::string Driver::describeIndirectSource() { + const auto& source = indirectSource(); + if (source.arguments == 0) return {}; + const char* ahead = source.ahead == AheadArguments::Matched ? "adopted, its counts matched the re-read" : source.ahead == AheadArguments::Differed ? "dropped, its counts differed from the re-read" : "none"; + char text[256]; + std::snprintf(text, sizeof(text), "; DISPATCH_INDIRECT on queue 0x%x, arguments at 0x%llx, driver path %s, read-ahead %s", source.queue, static_cast(source.arguments), IndirectPathName(source.path), ahead); + return text; +} + +std::array Driver::readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator) { + recordQueuedLabelsBeforeRead(queue); + if (!resolvingAhead()) { + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Indirect); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + Graphics::FinishLeaseWork(); + } + return Pm4::ReadDispatchArguments(arguments, initiator); +} + void Driver::dispatchIndirect(QueueState& queue, std::span packet, const Submission& submission) { const auto arguments = Pm4::DispatchArgumentAddress(packet, queue); const auto initiator = packet.back(); const int path = indirectArgumentsPath(packet, arguments); + struct Scope { + IndirectSource& source; + IndirectSource previous; + ~Scope() { source = previous; } + } scope{indirectSource(), indirectSource()}; + scope.source = IndirectSource{arguments, path, submission.queue, AheadArguments::None}; if (path != IndirectGpu) { + const auto readStart = std::chrono::steady_clock::now(); + const auto direct = readDispatchArguments(submission.queue, arguments, initiator); + countIndirect(path, std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); if (!resolvingAhead()) { - const auto& resolved = resolvedAhead(); + auto& resolved = resolvedAhead(); if (const auto found = resolved.find(currentPacketOffset()); found != resolved.end() && found->second.indirectArguments == 0 && found->second.packet[0] == 0xc0031500u) { - const auto direct = found->second.packet; - dispatch(queue, direct, submission); - return; + if (found->second.packet == direct) { + scope.source.ahead = AheadArguments::Matched; + } else { + resolved.erase(found); + ++groupCaptureStats().argumentMismatches; + scope.source.ahead = AheadArguments::Differed; + } } } - recordQueuedLabelsBeforeRead(submission.queue); - const auto readStart = std::chrono::steady_clock::now(); - const auto direct = Pm4::ReadDispatchArguments(arguments, initiator); - countIndirect(path, std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); dispatch(queue, direct, submission); return; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 582f3c702730..cce32d9847f7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -3301,6 +3301,8 @@ struct RecordedDispatch { // Whether the data refresh recorded anything. bool refreshed = false; bool resourceKeyMatched = false; + std::uint64_t cpuArguments = 0; + int cpuReason = 0; }; VkDevice VulkanDevice::Device() const { @@ -3558,15 +3560,16 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out } timer.phase(PhaseDecide); if (outcome.cpuReason == 0) return; - // The read waits for the producing batch through the flush hook, as the driver's resolve - // did for every indirect dispatch before; the counts then go through the direct checks. const auto readStart = std::chrono::steady_clock::now(); + Graphics::FinishLeaseWork(); std::array groups{}; GuestMemory::Read(arguments, std::as_writable_bytes(std::span(groups)), 4); outcome.argumentReadMs = std::chrono::duration(std::chrono::steady_clock::now() - readStart).count(); record.x = groups[0]; record.y = groups[1]; record.z = groups[2]; + record.cpuArguments = arguments; + record.cpuReason = outcome.cpuReason; // Timed before `arguments` is cleared, so the read counts as an indirect phase. timer.phase(PhaseArgumentRead); // From here on the phases of a CPU-resolved indirect dispatch are charged as direct ones. @@ -3692,14 +3695,16 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { if (record.x <= limit[0] && record.y <= limit[1] && record.z <= limit[2]) { context.Resolved(&Graphics::DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, record.x, record.y, record.z); } else { + char counted[128] = ""; + if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast(record.cpuArguments), record.cpuReason); if (record.shader->program->workgroupMemoryDwords != 0) { - char text[256]; - std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, limit[0], limit[1], limit[2]); + char text[384]; + std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]); throw std::runtime_error(text); } if (state->properties.vendorID == 0x10de) { - char text[192]; - std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u exceeds device limits %ux%ux%u and NVIDIA drops workgroups of a vkCmdDispatchBase split", record.x, record.y, record.z, limit[0], limit[1], limit[2]); + char text[320]; + std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and NVIDIA drops workgroups of a vkCmdDispatchBase split", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]); throw std::runtime_error(text); } const auto dispatchBase = context.Function("vkCmdDispatchBase"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index 23d0232df7b7..b518c805f650 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -125,6 +125,7 @@ std::uint64_t ImageMirrorSerial(const Context& context, std::uint64_t address, s // prints the [address-sync] leases line every 10 s from LeaseCounters (cumulative). bool SyncLeaseWork(); void CountLeaseOutcome(bool synced, std::uint64_t batchSerial); +void FinishLeaseWork(); struct LeaseStats { std::uint64_t deferred = 0; std::uint64_t synced = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 0fe94118f3ec..d73dfce3a149 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1842,6 +1842,17 @@ void CountLeaseOutcome(bool synced, std::uint64_t batchSerial) { AgcDriver::ProfilePrint_nid_no_patch("[address-sync] leases: %llu released at completion, %llu synced at once; %llu pin-contention waits by guest threads (%llu finished the lease batch, %llu drained the recorder, %llu cache-only drops) %.1f s; BDA table cache %llu hits / %llu misses (%zu tables held); snapshot compares: %llu skipped (live-backed region), %llu made\n", static_cast(stats.deferred), static_cast(stats.synced), static_cast(stats.contentionWaits), static_cast(stats.contentionSyncs), static_cast(stats.contentionDrains), static_cast(stats.cacheDrops), stats.contentionMs / 1000, static_cast(table.hits), static_cast(table.misses), table.held, static_cast(snapshots.skipped.load(std::memory_order_relaxed)), static_cast(snapshots.checked.load(std::memory_order_relaxed))); } +void FinishLeaseWork() { + GuestMemory::AssertGpuLockHeld("FinishLeaseWork"); + const auto& state = Leases(); + auto* recorder = Recorder::Active(); + const auto target = state.newestLeaseSerial; + if (recorder == nullptr || recorder != state.leaseRecorder || target <= recorder->CompletedThrough()) return; + if (target >= recorder->OpenSerial()) recorder->Submit(); + Recorder::AnnounceSyncSite(__builtin_return_address(0)); + recorder->FinishUpTo(target); +} + LeaseStats LeaseCounters() { auto& state = Leases(); std::lock_guard lock(state.mutex); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp new file mode 100644 index 000000000000..012363965a34 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp @@ -0,0 +1,365 @@ +#include "VulkanTestDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +alignas(256) std::array AddressWriterCode{0x7e000202u, 0x7e020203u, 0x7e040204u, 0x7e060280u, 0xdc7c8000u, 0x00000003u, 0xbf810000u}; +alignas(256) std::array CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array SpinCode{0xf4080200u, 0xfa000000u, 0xbe840381u, 0x9304ff04u, 0x0019660du, 0x8004ff04u, 0x3c6ef35fu, 0x80828102u, 0xbf078002u, 0xbf85fff9u, 0xbf8cc07fu, 0x7e000204u, 0xe0700000u, 0x80020000u, 0xbf810000u}; + +constexpr std::uint32_t Groups = 5; +constexpr std::array Stale{3u, 288u, 0xffffffffu}; +constexpr std::uint32_t SpinIterations = 1u << 20u; +constexpr std::uint32_t SpinDispatches = 16; +constexpr std::uint32_t ThreadDimensions = 0x8061u; +constexpr std::uint32_t GroupCounts = 0x8041u; +constexpr std::uint32_t Compute = 0x20u; +constexpr std::uint32_t OtherCompute = 0x28u; +constexpr std::uint32_t Graphics = 0u; +constexpr std::size_t GuestBytes = 1u << 20u; + +struct Slot { + alignas(256) std::array arguments{}; + alignas(256) std::array counter{}; + alignas(256) std::array counterDescriptor{}; + alignas(256) std::array spin{}; + alignas(256) std::array spinDescriptor{}; + alignas(256) std::uint32_t label = 0; + alignas(256) std::uint32_t done = 0; +}; + +enum class Order { SeparateSubmissions, OneSubmission, LabelWaitInOneSubmission, LabelWaitBetweenSubmissions, LabelWaitAcrossQueues, ArgumentLabelInEarlierBatch }; + +struct Variant { + const char* name; + std::uint32_t initiator; + Order order; +}; + +constexpr std::array Variants{{ + {"thread dimensions, separate submissions", ThreadDimensions, Order::SeparateSubmissions}, + {"thread dimensions, one submission", ThreadDimensions, Order::OneSubmission}, + {"thread dimensions, label wait in one submission", ThreadDimensions, Order::LabelWaitInOneSubmission}, + {"thread dimensions, label wait between submissions", ThreadDimensions, Order::LabelWaitBetweenSubmissions}, + {"thread dimensions, label wait across compute queues", ThreadDimensions, Order::LabelWaitAcrossQueues}, + {"group counts read on the CPU behind an argument label of an earlier graphics batch", GroupCounts, Order::ArgumentLabelInEarlierBatch}, +}}; + +std::uint32_t Low(const volatile void* pointer) { + return static_cast(reinterpret_cast(pointer)); +} + +std::uint32_t High(const volatile void* pointer) { + return static_cast(static_cast(reinterpret_cast(pointer)) >> 32u); +} + +std::array Descriptor(const void* pointer, std::uint32_t bytes) { + return {Low(pointer), High(pointer) & 0xffffu, bytes, 0x31016facu}; +} + +std::uint32_t Load(const std::uint32_t& value) { + return reinterpret_cast(value); +} + +void SetShaderRegister(std::vector& words, std::uint32_t offset, std::uint32_t value) { + words.insert(words.end(), {0xc0017600u, offset, value}); +} + +void Program(std::vector& words, const void* code, std::initializer_list userData) { + const auto address = static_cast(reinterpret_cast(code)); + SetShaderRegister(words, 0x20c, static_cast(address >> 8u)); + SetShaderRegister(words, 0x20d, static_cast(address >> 40u) & 0xffu); + for (const std::uint32_t offset : {0x207u, 0x208u, 0x209u}) SetShaderRegister(words, offset, 1); + SetShaderRegister(words, 0x212, 0); + SetShaderRegister(words, 0x213, static_cast(userData.size()) << 1u); + std::uint32_t offset = 0x240; + for (const auto value : userData) SetShaderRegister(words, offset++, value); +} + +void WriteData(std::vector& words, std::uint32_t* target, std::uint32_t value) { + words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(target), High(target), value}); +} + +void WaitEqual(std::vector& words, std::uint32_t* target, std::uint32_t value) { + words.insert(words.end(), {0xc0053c00u, 0x13u, Low(target), High(target), value, 0xffffffffu, 0x19u}); +} + +void Spin(std::vector& words, Slot& slot, std::uint32_t dispatches) { + for (std::uint32_t index = 0; index < dispatches; ++index) { + Program(words, SpinCode.data(), {Low(slot.spinDescriptor.data()), High(slot.spinDescriptor.data()), SpinIterations}); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, GroupCounts}); + } +} + +void Produce(std::vector& words, Slot& slot) { + Program(words, AddressWriterCode.data(), {Low(slot.arguments.data()), High(slot.arguments.data()), Groups, 1u, 1u}); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, GroupCounts}); +} + +void Consume(std::vector& words, Slot& slot, std::uint32_t initiator) { + Program(words, CounterCode.data(), {Low(slot.counterDescriptor.data()), High(slot.counterDescriptor.data())}); + words.insert(words.end(), {0xc0021102u, 1u, Low(slot.arguments.data()), High(slot.arguments.data())}); + words.insert(words.end(), {0xc0011600u, 0u, initiator}); +} + +void Submit(std::uint32_t queue, std::vector& words) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + if (queue == Graphics) Require(sceAgcDriverSubmitDcb(&packet) == 0, "graphics submit failed"); + else Require(sceAgcDriverSubmitAcb(queue, &packet) == 0, "compute submit failed"); +} + +ShaderRecompiler::RecompileResult Recompile(AgcDriver::VulkanDevice& device, Slot& slot, std::span code, std::initializer_list userData) { + const std::array memory{{ + {reinterpret_cast(code.data()), std::as_bytes(code)}, + {reinterpret_cast(slot.counterDescriptor.data()), std::as_bytes(std::span(slot.counterDescriptor))}, + }}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{1, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderRecompiler::ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, std::span(userData.begin(), userData.size()), compute, std::nullopt, std::nullopt, memory}, + device.ComputeTarget(32), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +class ProducerSubmissionGate { +public: + explicit ProducerSubmissionGate(AgcDriver::VulkanDevice& device) : device(device) { +#ifdef _WIN32 + constexpr const char* libraryName = "vulkan-1.dll"; +#else + constexpr const char* libraryName = "libvulkan.so.1"; +#endif + library.reset(SDL_LoadObject(libraryName)); + Require(library != nullptr, "cannot load Vulkan for the producer submission gate"); + resolve = reinterpret_cast(SDL_LoadFunction(library.get(), "vkGetDeviceProcAddr")); + Require(resolve != nullptr, "cannot resolve vkGetDeviceProcAddr for the producer submission gate"); + const auto createEvent = function("vkCreateEvent"); + destroyEvent = function("vkDestroyEvent"); + setEvent = function("vkSetEvent"); + waitEvents = function("vkCmdWaitEvents"); + const VkEventCreateInfo info{VK_STRUCTURE_TYPE_EVENT_CREATE_INFO}; + Require(createEvent(device.Device(), &info, nullptr, &event) == VK_SUCCESS, "cannot create the producer submission gate"); + } + + ~ProducerSubmissionGate() { + if (worker.joinable()) { + worker.request_stop(); + worker.join(); + } + setEvent(device.Device(), event); + try { + device.WaitIdle(); + } catch (...) { + } + destroyEvent(device.Device(), event, nullptr); + } + + void Record(AgcDriver::Graphics::Recorder& recorder) { + waitEvents(recorder.Commands(), 1, &event, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, 0, nullptr, 0, nullptr); + } + + void ReleaseAfterSubmission(AgcDriver::Graphics::Recorder& recorder, std::uint64_t serial) { + worker = std::jthread([this, &recorder, serial](std::stop_token stop) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!stop.stop_requested() && recorder.NewestSubmitted() < serial && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::microseconds(100)); + producerSubmitted = recorder.NewestSubmitted() >= serial; + releaseResult = setEvent(device.Device(), event); + if (!producerSubmitted && !stop.stop_requested()) std::fprintf(stderr, "pending producer gate: watchdog expired with producer batch %llu unsubmitted (vkSetEvent %d)\n", static_cast(serial), static_cast(releaseResult)); + }); + } + + void Finish() { + worker.join(); + Require(releaseResult == VK_SUCCESS, "cannot release the producer submission gate"); + Require(producerSubmitted, "the CPU argument read did not submit the later leased producer before waiting for its earlier argument label"); + device.WaitIdle(); + } + +private: + template + TFunction function(const char* name) const { + const auto function = reinterpret_cast(resolve(device.Device(), name)); + Require(function != nullptr, std::string("cannot resolve ") + name + " for the producer submission gate"); + return function; + } + + AgcDriver::VulkanDevice& device; + std::unique_ptr library{nullptr, &SDL_UnloadObject}; + PFN_vkGetDeviceProcAddr resolve = nullptr; + PFN_vkDestroyEvent destroyEvent = nullptr; + PFN_vkSetEvent setEvent = nullptr; + PFN_vkCmdWaitEvents waitEvents = nullptr; + VkEvent event = VK_NULL_HANDLE; + std::jthread worker; + bool producerSubmitted = false; + VkResult releaseResult = VK_NOT_READY; +}; + +void RunArgumentLabel(Slot& slot) { + AgcDriverShutdown_nid_postfix(); + slot.arguments = {3, 1, 1, 0}; + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "cannot open the argument-label test device"); + const auto producer = Recompile(*device, slot, AddressWriterCode, {Low(slot.arguments.data()), High(slot.arguments.data()), Groups, 1, 1}); + const auto consumer = Recompile(*device, slot, CounterCode, {Low(slot.counterDescriptor.data()), High(slot.counterDescriptor.data())}); + const auto preparedProducer = device->PrepareDispatch(producer, {}, 1); + const auto preparedConsumer = device->PrepareDispatch(consumer, {}, 0); + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "the argument-label test has no active recorder"); + ProducerSubmissionGate gate(*device); + gate.Record(*recorder); + const std::uint32_t labelValue = 2; + const auto labelAddress = reinterpret_cast(&slot.arguments[1]); + Require(device->WriteLabelOnGpu(labelAddress, std::as_bytes(std::span(&labelValue, 1)), 1, Graphics, false) == 0, "the argument label was not recorded on the GPU"); + device->SubmitRecorded(false); + const auto producerSerial = recorder->OpenSerial(); + gate.ReleaseAfterSubmission(*recorder, producerSerial); + const auto deferredBefore = AgcDriver::Graphics::LeaseCounters().deferred; + device->Dispatch(producer, 1, 1, 1, {}, reinterpret_cast(AddressWriterCode.data()), preparedProducer); + Require(AgcDriver::Graphics::LeaseCounters().deferred > deferredBefore, "the address-based producer did not defer its registry lease"); + Require(recorder->Submissions() < producerSerial && recorder->Recording(), "the producer was submitted before the CPU argument decision"); + Require(recorder->PendingLabel(labelAddress, 4, 0).has_value(), "the earlier argument label retired before the CPU argument decision"); + const auto outcome = device->DispatchIndirect(consumer, reinterpret_cast(slot.arguments.data()), {}, reinterpret_cast(CounterCode.data()), preparedConsumer); + Require(outcome.cpuReason == 2, "the indirect dispatch did not take the pending-label CPU fallback (reason " + std::to_string(outcome.cpuReason) + ")"); + std::fprintf(stderr, "pending producer CPU fallback: cpuReason %d\n", outcome.cpuReason); + gate.Finish(); + slot.done = 1; +} + +void Run(Slot& slot, const Variant& variant) { + slot.arguments = {Stale[0], Stale[1], Stale[2], 0}; + slot.counter = {}; + slot.counterDescriptor = Descriptor(slot.counter.data(), sizeof(slot.counter)); + slot.spinDescriptor = Descriptor(slot.spin.data(), sizeof(slot.spin)); + slot.label = 0; + slot.done = 0; + std::vector first; + std::vector second; + switch (variant.order) { + case Order::SeparateSubmissions: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + Consume(second, slot, variant.initiator); + WriteData(second, &slot.done, 1); + Submit(Compute, first); + Submit(Compute, second); + break; + case Order::OneSubmission: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + Consume(first, slot, variant.initiator); + WriteData(first, &slot.done, 1); + Submit(Compute, first); + break; + case Order::LabelWaitInOneSubmission: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + WriteData(first, &slot.label, 1); + WaitEqual(first, &slot.label, 1); + Consume(first, slot, variant.initiator); + WriteData(first, &slot.done, 1); + Submit(Compute, first); + break; + case Order::LabelWaitBetweenSubmissions: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + WriteData(first, &slot.label, 1); + WaitEqual(second, &slot.label, 1); + Consume(second, slot, variant.initiator); + WriteData(second, &slot.done, 1); + Submit(Compute, first); + Submit(Compute, second); + break; + case Order::LabelWaitAcrossQueues: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + WriteData(first, &slot.label, 1); + WaitEqual(second, &slot.label, 1); + Consume(second, slot, variant.initiator); + WriteData(second, &slot.done, 1); + Submit(OtherCompute, first); + Submit(Compute, second); + break; + case Order::ArgumentLabelInEarlierBatch: + RunArgumentLabel(slot); + break; + } + AgcDriverWaitIdle_nid_postfix(); + const auto start = std::chrono::steady_clock::now(); + while (Load(slot.done) == 0) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(10), std::string(variant.name) + ": the consumer's completion label did not land"); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + Require(Load(slot.arguments[0]) == Groups && Load(slot.arguments[1]) == 1 && Load(slot.arguments[2]) == 1, std::string(variant.name) + ": the producer's address-based store did not write the indirect arguments"); + Require(Load(slot.counter[0]) == Groups, std::string(variant.name) + ": the indirect dispatch ran " + std::to_string(Load(slot.counter[0])) + " threads, expected the " + std::to_string(Groups) + " its producer's address-based store wrote"); +} + +} + +int main(int argc, char** argv) { + try { + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + device.reset(); +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, GuestBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* block = static_cast(std::aligned_alloc(GuestBytes, GuestBytes)); +#endif + Require(block != nullptr, "cannot allocate guest memory"); + GuestAllocations::Mutation().Add(block, GuestBytes, true, true, true); + auto* slots = new (block) Slot[Variants.size()]; + for (std::size_t index = 0; index < Variants.size(); ++index) { + if (argc > 1 && std::to_string(index) != argv[1]) continue; + Run(slots[index], Variants[index]); + std::cout << Variants[index].name << ": passed\n"; + } + std::cout << "indirect arguments pending producer tests passed\n"; + AgcDriverShutdown_nid_postfix(); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { + AgcDriverShutdown_nid_postfix(); + } catch (const std::exception& shutdownError) { + std::cerr << shutdownError.what() << '\n'; + } + return 1; + } +} diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 2fe5afabbd9c..66a9ea56429a 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -110,6 +110,7 @@ - [SET_*_REG_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write. - The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. `v_min_f64` and `v_max_f64` flush subnormal inputs to signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3, and an unknown mode, preserve inputs. The fully flushed and preserved f64 behavior was [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/2036); split-mode selection follows [LLVM's denormal-mode enumeration](https://llvm.org/docs/AMDGPUUsage.html#floating-point-denorm-mode-enumeration-values). Other f64 arithmetic does not model these denormal controls. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. - A [WAIT_REG_MEM](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp) (libSceAgcDriver) that a compute queue passes on another queue's recorded label store, before the GPU has executed it, is landed before the queue's next dispatch, draw, COND_EXEC or predicated packet reads guest memory: the worker polls until memory satisfies the wait or no unfinished batch writes the label. The graphics queue (queue 0) is exempt, as landing its waits costs PPSA01341 about a quarter of its frame rate; a title that fills memory a graphics command buffer reads after it submits the buffer behind such a wait reads it early there. `APS5_LAND_WAITS_ALL_QUEUES=1` lands the graphics queue's waits too. +- [CPU reads behind an address-based build](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) (libSceAgcDriver) - an address-based store (FLAT/GLOBAL, or through a descriptor the recompiler cannot resolve to a static buffer) is not recorded as a pending write: its batch holds a lease on the registered allocations instead, and the flush hook does not wait for it. A DISPATCH_INDIRECT whose arguments are read on the CPU first finishes the batches recorded with a lease (`Graphics::FinishLeaseWork`). These reads do not, so they can see guest memory from before such a build ran: the indirect draw arguments and draw count of a draw resolved on the CPU (`Pm4::ReadDrawArguments`, `Pm4::ReadDrawCount`), the count record of the [copy kernel](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp) HLE (guarded only by its shape check), and the checks in [Submission.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) that decide whether a queue awaits a label only the title writes (`awaitsTitle`, `orderReleased`). No test covers them and no title has been seen to read such memory there. - The [dispatches that follow a compute queue's wait](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp) (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (`APS5_GROUP_CAPTURE_ALL_QUEUES=1` includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory. - [PA_CL_CLIP_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. - [10_11_11 unorm color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion. From f0c4a2791be2e238ec14dfbae5f76603075f6121 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:19:42 -0500 Subject: [PATCH 172/176] fix(build): drop duplicate test target definitions guest_game_live_streaming_tests was added twice by this sync; the agc_constant_select and guest_binary_word duplicates are already on main. Any configure that reaches these directories with BUILD_TESTING failed. Co-Authored-By: Claude Opus 5.5 --- core/libs/CMakeLists.txt | 10 ---------- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 13 ------------- core/libs/prx/libc/CMakeLists.txt | 11 ----------- 3 files changed, 34 deletions(-) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 0ada1259dc6c..e34f190e52bf 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -305,16 +305,6 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() - add_test_executable(guest_game_live_streaming_tests tests/GuestGameLiveStreaming.cpp) - target_include_directories(guest_game_live_streaming_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_game_live_streaming_tests PRIVATE libSceGameLiveStreaming libc) - configure_windows_unwind(guest_game_live_streaming_tests) - add_test(NAME guest_game_live_streaming COMMAND guest_game_live_streaming_tests) - if(WIN32) - set_tests_properties(guest_game_live_streaming PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - add_test_executable(guest_netctl_ap_dialog_tests tests/GuestNetCtlApDialog.cpp) target_include_directories(guest_netctl_ap_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_netctl_ap_dialog_tests PRIVATE libSceNetCtlApDialog libc) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 3885589e1bc1..99138fd45f55 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -2056,19 +2056,6 @@ if(BUILD_TESTING) endif() -add_test_executable(agc_constant_select_tests EXCLUDE_FROM_ALL tests/ConstantSelect.cpp) -target_link_libraries(agc_constant_select_tests PRIVATE shader_recompiler_agc_driver libc) -set_target_properties(agc_constant_select_tests PROPERTIES CXX_EXTENSIONS OFF) -configure_windows_unwind(agc_constant_select_tests) -if(BUILD_TESTING) - set_target_properties(agc_constant_select_tests PROPERTIES EXCLUDE_FROM_ALL OFF) - add_test(NAME agc_constant_select COMMAND agc_constant_select_tests) - if(WIN32) - set_tests_properties(agc_constant_select PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() -endif() - add_test_executable(agc_constant_select_tests EXCLUDE_FROM_ALL tests/ConstantSelect.cpp) target_link_libraries(agc_constant_select_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_constant_select_tests PROPERTIES CXX_EXTENSIONS OFF) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 925c5de2fcd7..8aa0eef4b483 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -97,17 +97,6 @@ if(WIN32) endif() if(BUILD_TESTING) - add_test_executable(guest_binary_word_tests ${LIBS_INCLUDE_DIR}/tests/GuestBinaryWord.cpp) - target_include_directories(guest_binary_word_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_binary_word_tests PRIVATE libc) - configure_windows_unwind(guest_binary_word_tests) - add_test(NAME guest_binary_word COMMAND guest_binary_word_tests guest_binary_word.tmp) - set_tests_properties(guest_binary_word PROPERTIES TIMEOUT 10) - if(WIN32) - set_tests_properties(guest_binary_word PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - if(UNIX) add_test_executable(guest_fopen_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestFopenErrors.cpp) target_include_directories(guest_fopen_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) From 25405c38faddc236c060ec1853e097e253860c45 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:22:32 -0500 Subject: [PATCH 173/176] fix(libc): restore Backtrace.cpp to the libc sources The Dinkumware atomics pick (5e45c8dc6) dropped it, removing the only definitions of sceLibcBacktraceSelf and sceLibcBacktraceGetBufferSize. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 8aa0eef4b483..4cf09d0c2208 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -82,6 +82,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RealPath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Threads.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/BinaryWordIo.cpp ) From 51a5d97eae3974fd192a2e7614c2b7416c3d51a4 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:22:43 -0500 Subject: [PATCH 174/176] fix(agc): restore Windows unwind setup for the graphics tests The fence-failure test pick replaced the target's tail and dropped CXX_EXTENSIONS OFF and configure_windows_unwind, which MinGW needs for the libc unwinder to see the test's throw/catch frames. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 99138fd45f55..ba12cf5293e3 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1209,6 +1209,8 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() +set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_driver_graphics_tests) add_test_executable(agc_driver_bda_device_tests EXCLUDE_FROM_ALL tests/BdaDevice.cpp From 25119fb8cca91e3eae31af1761a4c0cbbb9e4006 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:28:56 -0500 Subject: [PATCH 175/176] fix(libc): register the application_heap_scripting test 8dd0b5d0d added the name to the Windows set_tests_properties list, but the add_test from upstream was never picked, so Windows configure failed on the unknown test. Register it as upstream does. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 4cf09d0c2208..a70e62e048ec 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -768,6 +768,7 @@ if(BUILD_TESTING) add_test(NAME application_heap_invalid_metadata COMMAND application_heap_tests invalid) add_test(NAME application_heap_default COMMAND application_heap_tests default) add_test(NAME application_heap_exit_order COMMAND application_heap_tests exit-order) + add_test(NAME application_heap_scripting COMMAND application_heap_tests scripting) # The lazy mode needs process parameters that ApplicationHeap.cpp installs only on Windows and Linux. if(NOT APPLE) add_test(NAME application_heap_lazy COMMAND application_heap_tests lazy) From 85b180d2dd68612452a87e8c2272967c301e4bf1 Mon Sep 17 00:00:00 2001 From: RINNECODER <93304526+RINNECODER@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:29:39 -0500 Subject: [PATCH 176/176] fix(tests): let the shader cache fake compiler answer CMake's linker probes CMake 3.28 calls the compiler with -Wl,--help, -Wl,--version and -v --help; the fake compiler logged them as unexpected and every case failed. Take upstream's probe handling. Co-Authored-By: Claude Opus 5.5 --- tools/tests/test_shader_cache_runtime_sources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/tests/test_shader_cache_runtime_sources.py b/tools/tests/test_shader_cache_runtime_sources.py index 64221cceb252..6b11c5d5048c 100644 --- a/tools/tests/test_shader_cache_runtime_sources.py +++ b/tools/tests/test_shader_cache_runtime_sources.py @@ -30,9 +30,9 @@ def __init__(self, root, spirv_tools=False): self.guard.write_text('int fixture_driver;\n', encoding='utf-8') compiler = root / ('compiler.cmd' if os.name == 'nt' else 'compiler') if os.name == 'nt': - compiler.write_text('@echo off\nif "%~1"=="-print-sysroot" if "%~2"=="" exit /b 0\nif "%~1"=="--version" if "%~2"=="" exit /b 0\n>>"%~dp0compiler-invoked" echo %*\nexit /b 99\n', encoding='utf-8') + compiler.write_text('@echo off\nif "%*"=="-print-sysroot" exit /b 0\nif "%*"=="--version" exit /b 0\nif "%*"=="-v --help" exit /b 0\nif "%*"=="-Wl,--help" exit /b 0\nif "%*"=="-Wl,--version" exit /b 0\n>>"%~dp0compiler-invoked" echo %*\nexit /b 99\n', encoding='utf-8') else: - compiler.write_text('#!/bin/sh\nif [ "$#" -eq 1 ]; then case "$1" in -print-sysroot|--version) exit 0;; esac; fi\nprintf "%s\\n" "$*" >> "$(dirname "$0")/compiler-invoked"\nexit 99\n', encoding='utf-8') + compiler.write_text('#!/bin/sh\nif [ "$#" -eq 1 ]; then case "$1" in -print-sysroot|--version|-Wl,--help|-Wl,--version) exit 0;; esac; fi\nif [ "$#" -eq 2 ] && [ "$1" = "-v" ] && [ "$2" = "--help" ]; then exit 0; fi\nprintf "%s\\n" "$*" >> "$(dirname "$0")/compiler-invoked"\nexit 99\n', encoding='utf-8') compiler.chmod(0o755) mode = 'ON' if spirv_tools else 'OFF' (root / 'CMakeLists.txt').write_text(f'''cmake_minimum_required(VERSION 3.20)