diff --git a/.github/sanitizer-known-failures.txt b/.github/sanitizer-known-failures.txt new file mode 100644 index 0000000000..0d7883838c --- /dev/null +++ b/.github/sanitizer-known-failures.txt @@ -0,0 +1,8 @@ +agc_driver_graphics undefined UBSan stops on the misaligned guest word loads of the ASan build's CopyGuestBytes (Execution/src/GuestMemory.cpp), which #10973 fixes, and then on Graphics/src/DepthSurface.cpp:730, which binds a TextureDetiler reference to a null pointer when the context has no detiler. +agc_driver_graphics_maps_fallback undefined UBSan stops on the misaligned guest word loads of the ASan build's CopyGuestBytes (Execution/src/GuestMemory.cpp), which #10973 fixes, and then on Graphics/src/DepthSurface.cpp:730, which binds a TextureDetiler reference to a null pointer when the context has no detiler. +agc_driver_pm4 undefined UBSan stops on the misaligned guest word loads of the ASan build's CopyGuestBytes (Execution/src/GuestMemory.cpp); #10973 fixes them. +agc_performance_overlay address,undefined Runs past its 30 s ctest TIMEOUT in the sanitizer build on ubuntu-latest (timed out at 30.18 s); it passes without sanitizers, and alone it takes about 19 s; #11555 gives it 120 s in ASan builds. +guest_backtrace address Fails only in ASan builds, with 'guest backtrace: first frame is not in the calling module', because the sanitizer adds a frame to backtrace(); #9240 starts the frames at the caller. +guest_getrlimit address The test lowers RLIMIT_DATA to 1 TiB, below the private memory ASan has already mapped, so ASan's next internal mapping fails with ENOMEM ('failed to allocate ... DTLS_NextBlock'). +guest_math address,undefined ubuntu-latest's GCC 15 targets x86-64-v3 and fuses libc's float multiply-adds into FMA, so atanf misses the console bits by one ulp; #11059 builds libc with -ffp-contract=off. +guest_vorbis_dec undefined The bundled libvorbis left-shifts a negative value (3rdparty/vorbis/lib/psy.c:319); #11480 updates libvorbis to the upstream fix. diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml new file mode 100644 index 0000000000..914ad341bf --- /dev/null +++ b/.github/workflows/sanitizers.yml @@ -0,0 +1,111 @@ +name: Sanitizers + +on: + schedule: + - cron: '41 3 * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: sanitizers-${{ github.ref }} + cancel-in-progress: true + +env: + CONFIGURE: >- + -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON + -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + -DANYPS5_ENABLE_ASAN=ON + SANITIZER_FLAGS: -g1 -fsanitize=undefined -fno-sanitize=vptr + +jobs: + address-undefined: + runs-on: ubuntu-latest + timeout-minutes: 330 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.sha }} + persist-credentials: false + - id: submodules + name: Key the submodules on their commits + run: | + echo "key=$(git ls-files -s | awk '$1 == "160000" { print $2, $4 }' | cat - .gitmodules | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" + echo "ffmpeg=$(git rev-parse HEAD:3rdparty/ffmpeg-core)" >> "$GITHUB_OUTPUT" + - id: submodule-cache + uses: actions/cache/restore@v6 + with: + path: ${{ runner.temp }}/submodule-gitdirs.tar + key: submodule-gitdirs-${{ runner.os }}-${{ steps.submodules.outputs.key }} + - name: Check out the submodules + run: | + if [ -f "$RUNNER_TEMP/submodule-gitdirs.tar" ]; then tar -xf "$RUNNER_TEMP/submodule-gitdirs.tar"; fi + git submodule update --init --recursive --depth 1 --jobs 8 + - if: steps.submodule-cache.outputs.cache-hit != 'true' + run: tar -cf "$RUNNER_TEMP/submodule-gitdirs.tar" .git/modules + - if: steps.submodule-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + continue-on-error: true + with: + path: ${{ runner.temp }}/submodule-gitdirs.tar + key: submodule-gitdirs-${{ runner.os }}-${{ steps.submodules.outputs.key }} + - name: Install the build and test dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends ninja-build libx11-dev libxext-dev libxrandr-dev libasound2-dev libpulse-dev mesa-vulkan-drivers + - name: Free disk space for the sanitizer build + run: | + df -h / + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL + df -h / + - uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: sanitizers + max-size: 2G + save: ${{ github.ref == 'refs/heads/main' }} + evict-old-files: job + - id: ffmpeg-cache + uses: actions/cache/restore@v6 + with: + path: build/externals + key: ffmpeg-sanitizers-${{ runner.os }}-${{ steps.submodules.outputs.ffmpeg }} + - id: configure + run: cmake -S . -B build $CONFIGURE "-DCMAKE_C_FLAGS=$SANITIZER_FLAGS" "-DCMAKE_CXX_FLAGS=$SANITIZER_FLAGS" "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown" + - if: steps.ffmpeg-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v6 + continue-on-error: true + with: + path: build/externals + key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} + - name: Check that every test executable is registered with ctest + run: python3 tools/check_test_registration.py build + - name: Build, keeping on after a target fails so its tests report as not run + if: ${{ !cancelled() && steps.configure.outcome == 'success' }} + continue-on-error: true + run: cmake --build build -- -k 0 + - if: ${{ !cancelled() && steps.configure.outcome == 'success' }} + continue-on-error: true + run: cmake --build build --target libs -- -k 0 + - name: Run every test under ASan and UBSan + if: ${{ !cancelled() && steps.configure.outcome == 'success' }} + working-directory: build + env: + ASAN_OPTIONS: detect_leaks=0 + UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 + run: ctest --output-on-failure --timeout 300 --parallel 4 --output-log ctest.log --output-junit ctest.xml || true + - name: Summarize the sanitizer findings + if: ${{ !cancelled() && steps.configure.outcome == 'success' }} + run: >- + python3 tools/sanitizer_report.py build/ctest.xml --title "ASan and UBSan" + --known .github/sanitizer-known-failures.txt --sanitizer address --sanitizer undefined + --summary "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: sanitizer-tests + path: | + build/ctest.log + build/ctest.xml + if-no-files-found: ignore diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 4c42ec63a0..cd39336b6f 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -33,7 +33,8 @@ jobs: node --check demo/web/app.mjs node --check demo/web/archive.mjs node --check demo/web/batch.mjs + node --check demo/web/failure-report.mjs node --check demo/web/output-directory.mjs node --check demo/web/worker.mjs - name: Test browser converter helpers - run: node --test demo/web/tests/app.test.mjs demo/web/tests/batch.test.mjs demo/web/tests/output-directory.test.mjs + run: node --test demo/web/tests/app.test.mjs demo/web/tests/batch.test.mjs demo/web/tests/failure-report.test.mjs demo/web/tests/output-directory.test.mjs diff --git a/CMakeLists.txt b/CMakeLists.txt index 7db1bb7432..ea9d809261 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,13 @@ function(add_test_executable target) endif() endfunction() +function(configure_host_test_executable target) + if(MINGW) + target_compile_options(${target} PRIVATE -fasynchronous-unwind-tables) + target_link_options(${target} PRIVATE -static -static-libgcc -static-libstdc++) + endif() +endfunction() + function(add_asan_test_options test options) if(ANYPS5_ENABLE_ASAN) set_property(TEST ${test} APPEND PROPERTY ENVIRONMENT_MODIFICATION "ASAN_OPTIONS=string_append::${options}") @@ -89,7 +96,18 @@ if(ANYPS5_ENABLE_TSAN) endif() endif() -if(ANYPS5_ENABLE_ASAN) +option(ANYPS5_BUILD_FUZZERS "Build the relinker libFuzzer targets with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) +if(ANYPS5_BUILD_FUZZERS) + if(NOT ANYPS5_RELINKER_ONLY) + message(FATAL_ERROR "ANYPS5_BUILD_FUZZERS requires ANYPS5_RELINKER_ONLY") + endif() + if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR APPLE OR WIN32) + message(FATAL_ERROR "ANYPS5_BUILD_FUZZERS requires Clang on Linux") + endif() + set(ANYPS5_RELINKER_CLI OFF CACHE BOOL "Build the native relinker command line" FORCE) + add_compile_options(-fsanitize=address,undefined,fuzzer-no-link -fno-sanitize=function -fno-sanitize-recover=undefined -fno-omit-frame-pointer) + add_link_options(-fsanitize=address,undefined) +elseif(ANYPS5_ENABLE_ASAN) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") message(FATAL_ERROR "ANYPS5_ENABLE_ASAN is supported only on Linux") endif() @@ -228,12 +246,29 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_test_registration.py --scripts) add_test(NAME build_eligibility COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_build_eligibility.py) + add_test(NAME check_skips_ctest COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips_ctest.py) + set_tests_properties(check_skips_ctest PROPERTIES TIMEOUT 30) add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips.py) + add_test(NAME sanitizer_report COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_sanitizer_report.py) add_test(NAME select_tests COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_select_tests.py) add_test(NAME check_main_sync COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_main_sync.py) + set(select_tests_source "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_select_tests.py") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${select_tests_source}") + file(STRINGS "${select_tests_source}" select_tests_classes REGEX "^class [A-Za-z0-9_]*Tests\\(") + foreach(test_class IN LISTS select_tests_classes) + string(REGEX REPLACE "^class ([A-Za-z0-9_]+).*" "\\1" test_class "${test_class}") + if(test_class STREQUAL "SelectTests") + set_tests_properties(select_tests PROPERTIES ENVIRONMENT "ANYPS5_SELECT_TEST_CLASS=SelectTests" TIMEOUT 30) + else() + add_test(NAME select_tests_${test_class} COMMAND ${Python3_EXECUTABLE} "${select_tests_source}" ${test_class}) + set_tests_properties(select_tests_${test_class} PROPERTIES TIMEOUT 30) + endif() + endforeach() add_test(NAME configure_seed COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_configure_seed.py ConfigureSeedTests) add_test(NAME configure_seed_paths COMMAND ${Python3_EXECUTABLE} @@ -267,6 +302,14 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_own_branch.py) add_test(NAME pr_overlap_errors COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_errors.py) + add_test(NAME pr_overlap_branch_scan COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_branch_scan.py) + add_test(NAME pr_overlap_progress COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_progress.py) + add_test(NAME pr_overlap_pages COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_pages.py) + add_test(NAME pr_overlap_export_cache COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_export_cache.py) add_test(NAME nid_database_cache COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_nid_names.py) add_test(NAME release_package COMMAND ${Python3_EXECUTABLE} diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index b1de7b4e4a..3bd539bcac 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -288,6 +288,15 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_sys_console_identity PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_cpumode_tests tests/GuestKernelSysCpumode.cpp) + target_include_directories(guest_kernel_sys_cpumode_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_cpumode_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_cpumode_tests) + add_test(NAME guest_kernel_sys_cpumode COMMAND guest_kernel_sys_cpumode_tests) + if(WIN32) + set_tests_properties(guest_kernel_sys_cpumode PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(nid_buffer_validation_tests nid/tests/BufferValidation.cpp) target_include_directories(nid_buffer_validation_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) target_compile_features(nid_buffer_validation_tests PRIVATE cxx_std_20) @@ -369,10 +378,7 @@ if(BUILD_TESTING) add_test(NAME nid_pe_references COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_pe_references.py $) endif() - if(MINGW) - target_compile_options(patched_libraries_tests PRIVATE -fasynchronous-unwind-tables) - target_link_options(patched_libraries_tests PRIVATE -static -static-libgcc -static-libstdc++) - endif() + configure_host_test_executable(patched_libraries_tests) add_test(NAME patched_libraries COMMAND patched_libraries_tests ${PATCHED_DIR}) if(MINGW) get_filename_component(patchedLibrariesRuntimeDirectory "${CMAKE_CXX_COMPILER}" DIRECTORY) @@ -452,10 +458,7 @@ if(BUILD_TESTING) ${PATCHED_DIR}/libkernel_sys.prx ${PATCHED_DIR}/libkernel.prx ${LIBC_PATCHED}) add_dependencies(guest_kernel_sys_threads_tests guest_kernel_sys_threads_libraries) configure_windows_unwind(guest_kernel_sys_threads_tests) - if(MINGW) - target_compile_options(guest_kernel_sys_threads_tests PRIVATE -fasynchronous-unwind-tables) - target_link_options(guest_kernel_sys_threads_tests PRIVATE -static -static-libgcc -static-libstdc++) - endif() + configure_host_test_executable(guest_kernel_sys_threads_tests) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_test(NAME guest_kernel_sys_threads_main_late_load COMMAND guest_kernel_sys_threads_tests ${UNPATCHED_DIR} unpatched main_late_load) add_test(NAME guest_kernel_sys_threads_main_late_load_patched COMMAND guest_kernel_sys_threads_tests ${PATCHED_DIR} patched main_late_load) @@ -812,6 +815,16 @@ if(BUILD_TESTING) set_tests_properties(guest_rudp_poll PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(WIN32) + add_test_executable(rudp_poll_wait_exception_tests tests/RudpPollWaitException.cpp + prx/libSceRudp/Export.cpp prx/libkernel/Time/TimedWait.cpp) + target_include_directories(rudp_poll_wait_exception_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(rudp_poll_wait_exception_tests PRIVATE libc) + configure_windows_unwind(rudp_poll_wait_exception_tests) + add_test(NAME rudp_poll_wait_exception COMMAND rudp_poll_wait_exception_tests) + set_tests_properties(rudp_poll_wait_exception PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_memory_pool_stats_tests tests/GuestMemoryPoolStats.cpp) target_include_directories(guest_memory_pool_stats_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -922,15 +935,8 @@ if(BUILD_TESTING) "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) - configure_windows_unwind(savedata_core_write_replacement_tests) - add_test(NAME savedata_core_write_replacement COMMAND savedata_core_write_replacement_tests) - add_test_executable(savedata_native_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_native_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_compile_definitions(savedata_native_write_replacement_tests PRIVATE SAVEDATA_NATIVE_BACKEND) target_link_libraries(savedata_native_write_replacement_tests PRIVATE libSceSaveData.native) configure_windows_unwind(savedata_native_write_replacement_tests) add_test(NAME savedata_native_write_replacement COMMAND savedata_native_write_replacement_tests) @@ -962,13 +968,10 @@ if(BUILD_TESTING) endif() if(MINGW) - target_compile_options(savedata_core_write_replacement_tests PRIVATE -fasynchronous-unwind-tables) target_compile_options(savedata_native_write_replacement_tests PRIVATE -fasynchronous-unwind-tables) target_compile_options(savedata_replace_file_failure_tests PRIVATE -fasynchronous-unwind-tables) endif() if(WIN32) - set_tests_properties(savedata_core_write_replacement PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") set_tests_properties(savedata_native_write_replacement PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() @@ -1005,6 +1008,8 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_subdirectory(tests/floating_prefix) + add_test_executable(MemoryPoolTest tests/MemoryPoolTest.cpp prx/libkernel/DirectMemory/MemoryPool.cpp diff --git a/core/libs/allocator/PeImportFirst.cpp b/core/libs/allocator/PeImportFirst.cpp index aa4b040ba2..cb4f561d11 100644 --- a/core/libs/allocator/PeImportFirst.cpp +++ b/core/libs/allocator/PeImportFirst.cpp @@ -42,7 +42,9 @@ void MoveImportFirst(std::vector& bytes, const std::string& dll) const auto optionalSize = read16(bytes, pe + 20); const auto optional = pe + 24; if (read16(bytes, optional) != 0x20bu) throw std::runtime_error("not a PE32+ image"); - const auto importRva = read32(bytes, optional + 112 + 8); + constexpr std::size_t ImportDirectory = 112 + 8; + if (optionalSize < ImportDirectory + 8 || read32(bytes, optional + 108) < 2) throw std::runtime_error("the image has no imports"); + const auto importRva = read32(bytes, optional + ImportDirectory); if (importRva == 0) throw std::runtime_error("the image has no imports"); const auto sectionTable = optional + optionalSize; const auto fileOffset = [&](std::uint32_t rva) -> std::size_t { @@ -62,18 +64,24 @@ void MoveImportFirst(std::vector& bytes, const std::string& dll) const auto directory = fileOffset(importRva); constexpr std::size_t DescriptorBytes = 20; std::vector> descriptors; - std::size_t found = SIZE_MAX; for (std::size_t offset = directory;; offset += DescriptorBytes) { if (offset + DescriptorBytes > bytes.size()) throw std::runtime_error("truncated import directory"); std::array descriptor{}; std::memcpy(descriptor.data(), bytes.data() + offset, DescriptorBytes); if (std::all_of(descriptor.begin(), descriptor.end(), [](unsigned char value) { return value == 0; })) break; - const auto nameOffset = fileOffset(read32(bytes, offset + 12)); - std::string name; - for (auto at = nameOffset; at < bytes.size() && bytes[at] != 0; ++at) name.push_back(static_cast(bytes[at])); - if (lower(name) == lower(dll)) found = descriptors.size(); descriptors.push_back(descriptor); } + std::size_t found = SIZE_MAX; + for (std::size_t index = 0; index < descriptors.size(); ++index) { + std::uint32_t nameRva = 0; + std::memcpy(&nameRva, descriptors[index].data() + 12, sizeof(nameRva)); + const auto nameOffset = fileOffset(nameRva); + if (nameOffset >= bytes.size()) throw std::runtime_error("RVA outside the file data"); + const auto terminator = std::find(bytes.begin() + static_cast(nameOffset), bytes.end(), 0); + if (terminator == bytes.end()) throw std::runtime_error("unterminated import name"); + const std::string name(bytes.begin() + static_cast(nameOffset), terminator); + if (lower(name) == lower(dll)) found = index; + } if (found == SIZE_MAX) throw std::runtime_error("the image does not import " + dll); std::rotate(descriptors.begin(), descriptors.begin() + static_cast(found), descriptors.begin() + static_cast(found) + 1); for (std::size_t index = 0; index < descriptors.size(); ++index) std::memcpy(bytes.data() + directory + index * DescriptorBytes, descriptors[index].data(), DescriptorBytes); diff --git a/core/libs/allocator/tests/PeImportFirstTests.cpp b/core/libs/allocator/tests/PeImportFirstTests.cpp index 5d40857cdc..62ff951c4d 100644 --- a/core/libs/allocator/tests/PeImportFirstTests.cpp +++ b/core/libs/allocator/tests/PeImportFirstTests.cpp @@ -17,7 +17,6 @@ namespace { constexpr std::size_t PeOffset = 0x40; constexpr std::size_t OptionalOffset = PeOffset + 24; constexpr std::size_t OptionalBytes = 240; -constexpr std::size_t SectionTable = OptionalOffset + OptionalBytes; constexpr std::uint32_t SectionRva = 0x1000; constexpr std::size_t SectionOffset = 0x200; constexpr std::size_t SectionBytes = 0x200; @@ -41,7 +40,7 @@ std::uint32_t get32(const std::vector& bytes, std::size_t offset) return value; } -std::vector image(const std::vector& dlls, std::uint16_t magic = 0x20b) { +std::vector image(const std::vector& dlls, std::uint16_t magic = 0x20b, std::uint32_t directories = 16, std::uint16_t optionalBytes = OptionalBytes) { std::vector bytes(SectionOffset + SectionBytes, 0); bytes[0] = 'M'; bytes[1] = 'Z'; @@ -49,16 +48,17 @@ std::vector image(const std::vector& dlls, std::uint put32(bytes, PeOffset, 0x00004550u); put16(bytes, PeOffset + 4, 0x8664); put16(bytes, PeOffset + 6, 1); - put16(bytes, PeOffset + 20, OptionalBytes); + put16(bytes, PeOffset + 20, optionalBytes); put16(bytes, OptionalOffset, magic); - put32(bytes, OptionalOffset + 108, 16); + put32(bytes, OptionalOffset + 108, directories); put32(bytes, OptionalOffset + 112 + 8, SectionRva); put32(bytes, OptionalOffset + 112 + 12, static_cast((dlls.size() + 1) * DescriptorBytes)); - std::memcpy(bytes.data() + SectionTable, ".idata", 6); - put32(bytes, SectionTable + 8, SectionBytes); - put32(bytes, SectionTable + 12, SectionRva); - put32(bytes, SectionTable + 16, SectionBytes); - put32(bytes, SectionTable + 20, SectionOffset); + const auto sectionTable = OptionalOffset + optionalBytes; + std::memcpy(bytes.data() + sectionTable, ".idata", 6); + put32(bytes, sectionTable + 8, SectionBytes); + put32(bytes, sectionTable + 12, SectionRva); + put32(bytes, sectionTable + 16, SectionBytes); + put32(bytes, sectionTable + 20, SectionOffset); auto name = (dlls.size() + 1) * DescriptorBytes; for (std::size_t index = 0; index < dlls.size(); ++index) { const auto descriptor = SectionOffset + index * DescriptorBytes; @@ -110,6 +110,28 @@ void movesTheNamedImportFirst() { require(unchanged == original, "moving the first import changed the image"); } +void movesTheImportFirstWithATwoDirectoryOptionalHeader(const std::filesystem::path& directory) { + const auto original = image({"kernel32.dll", "ucrtbase.dll", "mimalloc-redirect.dll", "advapi32.dll"}, 0x20b, 2, 128); + auto moved = original; + Allocator::MoveImportFirst(moved, "mimalloc-redirect.dll"); + require(importName(moved, 0) == "mimalloc-redirect.dll" && importName(moved, 1) == "kernel32.dll" && importName(moved, 2) == "ucrtbase.dll" && importName(moved, 3) == "advapi32.dll", + "the import did not move first with a 128-byte optional header holding two directories"); + require(descriptor(moved, 0) == descriptor(original, 2) && descriptor(moved, 1) == descriptor(original, 0) && descriptor(moved, 2) == descriptor(original, 1) && descriptor(moved, 3) == descriptor(original, 3), + "a descriptor did not move whole with a 128-byte optional header"); + const auto directoryEnd = SectionOffset + 4 * DescriptorBytes; + require(std::equal(moved.begin(), moved.begin() + SectionOffset, original.begin()) && std::equal(moved.begin() + directoryEnd, moved.end(), original.begin() + directoryEnd), + "bytes outside the import descriptors changed with a 128-byte optional header"); + auto repeated = moved; + Allocator::MoveImportFirst(repeated, "mimalloc-redirect.dll"); + require(repeated == moved, "repeating the move changed the image with a 128-byte optional header"); + const auto path = directory / "two-directory.dll"; + writeFile(path, original); + Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll"); + require(readFile(path) == moved, "the file move differs from the in-memory move with a 128-byte optional header"); + Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll"); + require(readFile(path) == moved, "repeating the move changed the file with a 128-byte optional header"); +} + void movesTheImportInAFile(const std::filesystem::path& directory) { const auto path = directory / "moved.dll"; writeFile(path, image({"kernel32.dll", "mimalloc-redirect.dll"})); @@ -139,12 +161,32 @@ int main() { try { std::filesystem::create_directories(directory); movesTheNamedImportFirst(); + movesTheImportFirstWithATwoDirectoryOptionalHeader(directory); movesTheImportInAFile(directory); failsWithoutTouchingTheFile(directory, "missing-import", image({"kernel32.dll", "ucrtbase.dll"}), "does not import mimalloc-redirect.dll"); auto truncated = image({"kernel32.dll", "mimalloc-redirect.dll", "ucrtbase.dll"}); truncated.resize(SectionOffset + DescriptorBytes + DescriptorBytes / 2); failsWithoutTouchingTheFile(directory, "truncated-directory", truncated, "truncated import directory"); failsWithoutTouchingTheFile(directory, "pe32", image({"kernel32.dll", "mimalloc-redirect.dll"}, 0x10b), "not a PE32+ image"); + auto undeclared = image({"kernel32.dll", "mimalloc-redirect.dll"}); + put32(undeclared, OptionalOffset + 108, 0); + failsWithoutTouchingTheFile(directory, "directory-count-zero", undeclared, "the image has no imports"); + auto oneDirectory = image({"kernel32.dll", "mimalloc-redirect.dll"}); + put32(oneDirectory, OptionalOffset + 108, 1); + failsWithoutTouchingTheFile(directory, "directory-count-one", oneDirectory, "the image has no imports"); + auto shortHeader = image({"kernel32.dll", "mimalloc-redirect.dll"}); + put16(shortHeader, PeOffset + 20, 112); + failsWithoutTouchingTheFile(directory, "optional-header-without-imports", shortHeader, "the image has no imports"); + auto incompleteEntry = image({"kernel32.dll", "mimalloc-redirect.dll"}, 0x20b, 2, 128); + put16(incompleteEntry, PeOffset + 20, 127); + failsWithoutTouchingTheFile(directory, "optional-header-incomplete-import-entry", incompleteEntry, "the image has no imports"); + auto unterminated = image({"kernel32.dll", "mimalloc-redirect.dll"}); + unterminated.resize(SectionOffset + 3 * DescriptorBytes + 13 + 21); + failsWithoutTouchingTheFile(directory, "unterminated-name", unterminated, "unterminated import name"); + auto outsideFile = image({"kernel32.dll", "mimalloc-redirect.dll", "ucrtbase.dll"}); + put32(outsideFile, SectionOffset + 2 * DescriptorBytes + 12, static_cast(SectionRva + 0x1ff)); + outsideFile.resize(0x300); + failsWithoutTouchingTheFile(directory, "name-past-the-file", outsideFile, "RVA outside the file data"); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; std::error_code ignored; diff --git a/core/libs/nid/src/PeNidPatcher.cpp b/core/libs/nid/src/PeNidPatcher.cpp index 638f3bb29e..564d23aa22 100644 --- a/core/libs/nid/src/PeNidPatcher.cpp +++ b/core/libs/nid/src/PeNidPatcher.cpp @@ -253,12 +253,6 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l std::uint32_t FirstThunk; }; - if (options.executable) { - for (const auto index : {4u, 11u, 13u}) { - const auto directory = Read(pe, dataDirectoryOffset + index * sizeof(PeDataDirectory)); - if (directory.VirtualAddress || directory.Size) throw std::runtime_error("signed, bound or delay-loaded PE imports are not supported in --exec mode"); - } - } struct ImportUse { std::size_t NameOffset; std::size_t LookupOffset; @@ -315,19 +309,27 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l importDescOffset += sizeof(PeImportDescriptor); } const auto importMap = ResolveImportNids(importNames, excludedExports, options); - if (!options.executable) { - for (const auto& item : imports) { - const auto& name = importMap.at(item.Name); - if (name.size() > item.Name.size()) throw std::runtime_error("import NID longer than original name: " + item.Name); - std::memcpy(pe.data() + item.NameOffset, name.c_str(), name.size() + 1); - } - return; + const auto fitsInPlace = [&](const ImportUse& item) { + return !options.executable && importMap.at(item.Name).size() <= item.Name.size(); + }; + const bool growsImports = options.executable || std::any_of(imports.begin(), imports.end(), [&](const ImportUse& item) { + return !fitsInPlace(item) && importMap.at(item.Name) != item.Name; + }); + for (const auto index : {4u, 11u, 13u}) { + const auto directory = Read(pe, dataDirectoryOffset + index * sizeof(PeDataDirectory)); + if (growsImports && (directory.VirtualAddress || directory.Size)) + throw std::runtime_error("signed, bound or delay-loaded PE imports are not supported when import names move to a new section"); + } + for (const auto& item : imports) { + if (!fitsInPlace(item)) continue; + const auto& name = importMap.at(item.Name); + std::memcpy(pe.data() + item.NameOffset, name.c_str(), name.size() + 1); } std::vector strings; std::unordered_map stringOffsets; for (const auto& item : imports) { const auto& name = importMap.at(item.Name); - if (name == item.Name || stringOffsets.contains(name)) continue; + if (name == item.Name || fitsInPlace(item) || stringOffsets.contains(name)) continue; if (strings.size() & 1u) strings.push_back(0); if (strings.size() > 0xffffffffu - name.size() - 3u) throw std::runtime_error("PE import string table overflow"); stringOffsets.emplace(name, static_cast(strings.size())); @@ -372,7 +374,7 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l Write(pe, optionalHeaderOffset + 64u, std::uint32_t{0}); for (const auto& item : imports) { const auto& name = importMap.at(item.Name); - if (name == item.Name) continue; + if (name == item.Name || fitsInPlace(item)) continue; const auto rva = section.VirtualAddress + stringOffsets.at(name); for (const auto offset : {item.LookupOffset, item.AddressOffset}) { if (magic == 0x20bu) Write(pe, offset, std::uint64_t{rva}); diff --git a/core/libs/nid/tests/test_pe_references.py b/core/libs/nid/tests/test_pe_references.py index 2992a55093..1368a3cac8 100644 --- a/core/libs/nid/tests/test_pe_references.py +++ b/core/libs/nid/tests/test_pe_references.py @@ -4,6 +4,7 @@ import sys import tempfile import unittest from pathlib import Path +from types import SimpleNamespace from test_macho_nids import build_dylib, exports_of, nid, PATCHER @@ -441,5 +442,68 @@ class ExecutableImportTests(unittest.TestCase): self.assertEqual(path.read_bytes(), image) +@unittest.skipUnless(PATCHER is not None and PATCHER.is_file(), "nid_patcher executable required") +class LibraryImportTests(unittest.TestCase): + def library(self, names): + image = ExecutableImportTests.pe(SimpleNamespace(names=names)) + struct.pack_into(" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" namespace { -std::uint32_t* RejectUnregisteredWorkloadStream(CommandBuffer* buf, std::uint32_t streamId, const char* function) { - if (buf == nullptr || !AgcDriverWorkloadStreamRegistered_nid_postfix(streamId)) return nullptr; - NotImplemented_nid_no_patch(function); - return nullptr; +constexpr std::uint32_t OpcodeNop = 0x10; +constexpr std::uint32_t ActivePacketWords = 18; +constexpr std::uint32_t CompletePacketWords = 12; +constexpr std::uint32_t InactivePacketWords = 9; +constexpr std::uint32_t MaxWorkloadId = 63; +constexpr std::uint32_t MaxActiveCount = 63; + +std::uint32_t* NewWorkloadPacket(CommandBuffer* buf, std::uint32_t words, const char* function) { + auto* packet = Agc::Command::Allocate(buf, words, function); + std::memset(packet, 0, words * sizeof(std::uint32_t)); + packet[0] = Agc::Command::Header(OpcodeNop, words, 0); + return packet; } } @@ -83,18 +92,38 @@ std::uint32_t* APS5_VABI sceAgcAcbSetFlip(CommandBuffer* buf, std::uint32_t vide } std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t streamId, const std::uint32_t* workloadIds, std::uint32_t workloadCount) { - (void)workloadIds; - (void)workloadCount; - return RejectUnregisteredWorkloadStream(buf, streamId, __func__); + if (buf == nullptr || workloadIds == nullptr || workloadCount == 0 || workloadCount > MaxActiveCount || !AgcDriverWorkloadStreamRegistered_nid_postfix(streamId)) return nullptr; + std::uint64_t mask = 0; + for (std::uint32_t index = 0; index < workloadCount; ++index) { + const auto id = workloadIds[index]; + if (id > MaxWorkloadId) return nullptr; + const std::uint64_t bit = std::uint64_t{1} << id; + if ((mask & bit) != 0) return nullptr; + mask |= bit; + } + auto* packet = NewWorkloadPacket(buf, ActivePacketWords, __func__); + packet[1] = streamId; + packet[2] = static_cast(mask); + packet[3] = static_cast(mask >> 32u); + return packet; } std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t streamId, std::uint32_t workloadId) { - (void)workloadId; - return RejectUnregisteredWorkloadStream(buf, streamId, __func__); + if (buf == nullptr || workloadId > MaxWorkloadId || !AgcDriverWorkloadStreamRegistered_nid_postfix(streamId)) return nullptr; + const std::uint64_t clearMask = ~(std::uint64_t{1} << workloadId); + auto* packet = NewWorkloadPacket(buf, CompletePacketWords, __func__); + packet[1] = streamId; + packet[2] = workloadId; + packet[3] = static_cast(clearMask); + packet[4] = static_cast(clearMask >> 32u); + return packet; } std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId) { - return RejectUnregisteredWorkloadStream(buf, streamId, __func__); + if (buf == nullptr || !AgcDriverWorkloadStreamRegistered_nid_postfix(streamId)) return nullptr; + auto* packet = NewWorkloadPacket(buf, InactivePacketWords, __func__); + packet[1] = streamId; + return packet; } uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, uint32_t color) { diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 82331aea05..ef3b5c6ee3 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -97,6 +97,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_rewind_patch_tests EXCLUDE_FROM_ALL tests/RewindPatch.cpp) +target_include_directories(agc_rewind_patch_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_rewind_patch_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_rewind_patch_tests) +if(BUILD_TESTING) + set_target_properties(agc_rewind_patch_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_rewind_patch COMMAND agc_rewind_patch_tests) + if(WIN32) + set_tests_properties(agc_rewind_patch PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_draw_index_multi_instanced_tests EXCLUDE_FROM_ALL tests/DrawIndexMultiInstanced.cpp) target_include_directories(agc_draw_index_multi_instanced_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_draw_index_multi_instanced_tests PRIVATE libSceAgc libc) @@ -109,6 +122,19 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +add_test_executable(agc_atomic_mem_tests EXCLUDE_FROM_ALL tests/AtomicMem.cpp) +target_include_directories(agc_atomic_mem_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_atomic_mem_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_atomic_mem_tests) +if(BUILD_TESTING) + set_target_properties(agc_atomic_mem_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_atomic_mem COMMAND agc_atomic_mem_tests) + if(WIN32) + set_tests_properties(agc_atomic_mem PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() configure_windows_unwind(libSceAgc) add_test_executable(agc_static_buffer_tests EXCLUDE_FROM_ALL tests/StaticBuffer.cpp) @@ -227,6 +253,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_end_of_pipe_type_patch_tests EXCLUDE_FROM_ALL tests/EndOfPipeActionType.cpp) +target_include_directories(agc_end_of_pipe_type_patch_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_end_of_pipe_type_patch_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_end_of_pipe_type_patch_tests) +if(BUILD_TESTING) + set_target_properties(agc_end_of_pipe_type_patch_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_end_of_pipe_type_patch COMMAND agc_end_of_pipe_type_patch_tests) + if(WIN32) + set_tests_properties(agc_end_of_pipe_type_patch PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_branch_patch_tests EXCLUDE_FROM_ALL tests/BranchPatch.cpp Command/src/Packet.cpp @@ -320,6 +359,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_lod_stats_tests EXCLUDE_FROM_ALL tests/LodStats.cpp) +target_include_directories(agc_lod_stats_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_lod_stats_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_lod_stats_tests) +if(BUILD_TESTING) + set_target_properties(agc_lod_stats_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_lod_stats COMMAND agc_lod_stats_tests) + if(WIN32) + set_tests_properties(agc_lod_stats PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL tests/InterpolantMapping.cpp Shader/src/InterpolantMapping.cpp @@ -395,6 +447,7 @@ add_test_executable(agc_dma_data_tests EXCLUDE_FROM_ALL Command/src/Memory.cpp Acb/src/Memory.cpp DcbFlow/src/Memory.cpp + Patch/src/Dma.cpp ) target_include_directories(agc_dma_data_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_dma_data_tests PRIVATE libSceAgcDriver libc) @@ -488,3 +541,21 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +add_test_executable(agc_create_shader_tests EXCLUDE_FROM_ALL + tests/CreateShader.cpp + Shader/src/CreateShader.cpp + Shader/src/ShaderUtils.cpp +) +target_include_directories(agc_create_shader_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_create_shader_tests PRIVATE libc libSceAgcDriver) +configure_windows_unwind(agc_create_shader_tests) +if(BUILD_TESTING) + set_target_properties(agc_create_shader_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_create_shader COMMAND agc_create_shader_tests) + set_tests_properties(agc_create_shader PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_create_shader PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgc/tests/AtomicMem.cpp b/core/libs/prx/libSceAgc/tests/AtomicMem.cpp new file mode 100644 index 0000000000..b98e83ce99 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/AtomicMem.cpp @@ -0,0 +1,63 @@ +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t Sentinel = 0xabcdef01u; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + + Storage() { + words.fill(Sentinel); + } +}; + +using Writer = std::uint32_t* (APS5_VABI *)(CommandBuffer*, std::uint8_t, std::uint8_t, std::uint8_t, const volatile void*, std::uint64_t, std::uint64_t, std::uint16_t); + +void testPacket(Writer writer, std::uint32_t size, std::uint8_t atomicOp, std::uintptr_t address) { + check(size == 36, "ATOMIC_MEM packet size"); + Storage storage; + auto* packet = writer(&storage.buffer, atomicOp, 0x5, 2, reinterpret_cast(address), 0x1122334455667788ull, 0x99aabbccddeeff00ull, 0x1abc); + const std::array expected{ + 0xc0071e00u, + atomicOp | (0x5u << 8u) | (2u << 25u), + static_cast(address), + static_cast(address >> 32u), + 0x55667788u, + 0x11223344u, + 0xddeeff00u, + 0x99aabbccu, + 0x1abcu, + }; + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "incorrect ATOMIC_MEM packet"); + check(storage.buffer.cursor_up == packet + size / sizeof(std::uint32_t), "the cursor did not advance by the packet size"); + check(std::all_of(storage.words.begin() + expected.size(), storage.words.end(), [](std::uint32_t word) { return word == Sentinel; }), "ATOMIC_MEM wrote past its nine words"); +} + +} + +int main() { + try { + testPacket(sceAgcDcbAtomicMem, sceAgcDcbAtomicMemGetSize(), 0x2f, 0x0000123456789ab4ull); + testPacket(sceAgcDcbAtomicMem, sceAgcDcbAtomicMemGetSize(), 0x6f, 0x0000123456789ab8ull); + testPacket(sceAgcAcbAtomicMem, sceAgcAcbAtomicMemGetSize(), 0x2f, 0x0000123456789ab4ull); + testPacket(sceAgcAcbAtomicMem, sceAgcAcbAtomicMemGetSize(), 0x6f, 0x0000123456789ab8ull); + std::puts("AGC atomic memory tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/Commands.cpp b/core/libs/prx/libSceAgc/tests/Commands.cpp index b65ee3c459..5379206a97 100644 --- a/core/libs/prx/libSceAgc/tests/Commands.cpp +++ b/core/libs/prx/libSceAgc/tests/Commands.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Resource/include/Registration.hpp" #include "prx/libSceAgc/Patch/include/WaitRegMem.hpp" #include "prx/libSceAgc/Patch/include/Nop.hpp" +#include "prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp" #include "prx/libSceAgc/Misc/include/Suspend.hpp" #include "prx/libSceAgc/Misc/include/RegisterDefaults.hpp" #include "prx/libSceAgc/Misc/include/PacketInfo.hpp" @@ -12,8 +13,13 @@ #include "prx/libSceAgc/DcbState/include/Display.hpp" #include "prx/libSceAgc/DcbState/include/ContextState.hpp" #include "prx/libSceAgc/DcbFlow/include/Sync.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" #include "prx/libSceAgc/DcbFlow/include/Control.hpp" #include "prx/libSceAgc/DcbDraw/include/Instancing.hpp" +#include "prx/libSceAgc/Acb/include/Dispatch.hpp" +#include "prx/libSceAgc/Cb/include/Dispatch.hpp" +#include "prx/libSceAgc/DcbFlow/include/Dispatch.hpp" #include "prx/libSceAgc/DcbDraw/include/IndexBuffer.hpp" #include "prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp" #include "prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp" @@ -332,16 +338,33 @@ void testWorkloadStreams() { check(sceAgcDriverRegisterWorkloadStream(1, record) == 0, "workload stream registration failed"); Storage registered; registered.words.fill(0xdeadbeefu); - const auto untouched = registered.words; - expectFailure([&] { sceAgcDcbSetWorkloadStreamInactive(®istered.buffer, 1); }); - expectFailure([&] { sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, ids, 2); }); - expectFailure([&] { sceAgcAcbSetWorkloadComplete(®istered.buffer, 1, 3); }); - expectFailure([&] { sceAgcAcbSetWorkloadStreamInactive(®istered.buffer, 1); }); - check(registered.words == untouched && registered.buffer.cursor_up == registered.words.data(), "a workload call that threw modified the buffer"); + const auto* acbActive = sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, ids, 2); + check(acbActive == registered.words.data() && acbActive[0] == Agc::Command::Header(0x10, 18, 0) && acbActive[1] == 1 && acbActive[2] == 0x18 && acbActive[3] == 0, "ACB workloads active packet"); + for (std::uint32_t word = 4; word < 18; ++word) check(acbActive[word] == 0, "ACB workloads active packet tail is not zero"); + const auto* acbComplete = sceAgcAcbSetWorkloadComplete(®istered.buffer, 1, 33); + check(acbComplete == registered.words.data() + 18 && acbComplete[0] == Agc::Command::Header(0x10, 12, 0) && acbComplete[1] == 1 && acbComplete[2] == 33 && acbComplete[3] == 0xffffffffu && acbComplete[4] == 0xfffffffdu, "ACB workload complete packet"); + for (std::uint32_t word = 5; word < 12; ++word) check(acbComplete[word] == 0, "ACB workload complete packet tail is not zero"); + const auto* acbInactive = sceAgcAcbSetWorkloadStreamInactive(®istered.buffer, 1); + check(acbInactive == registered.words.data() + 30 && acbInactive[0] == Agc::Command::Header(0x10, 9, 0) && acbInactive[1] == 1, "ACB workload stream inactive packet"); + for (std::uint32_t word = 2; word < 9; ++word) check(acbInactive[word] == 0, "ACB workload stream inactive packet tail is not zero"); + check(registered.buffer.cursor_up == registered.words.data() + 39, "incorrect ACB workload cursor advance"); + const std::uint32_t duplicate[2] = {5, 5}; + const std::uint32_t outOfRange[1] = {64}; + const std::uint32_t top[1] = {63}; + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, duplicate, 2) == nullptr, "ACB duplicate workload ids accepted"); + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, outOfRange, 1) == nullptr, "ACB workload id 64 accepted"); + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, top, 1) != nullptr, "ACB workload id 63 rejected"); + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, ids, 0) == nullptr, "ACB workload count 0 accepted"); + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 1, nullptr, 2) == nullptr, "ACB null workload ids accepted"); + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 0, ids, 2) == nullptr, "ACB stream id 0 accepted"); + check(sceAgcAcbSetWorkloadsActive(®istered.buffer, 32, ids, 2) == nullptr, "ACB stream id 32 accepted"); + check(sceAgcAcbSetWorkloadComplete(®istered.buffer, 1, 64) == nullptr, "ACB workload complete id 64 accepted"); + check(sceAgcAcbSetWorkloadsActive(nullptr, 1, ids, 2) == nullptr, "ACB null command buffer accepted for active"); check(sceAgcDcbSetWorkloadStreamInactive(nullptr, 1) == nullptr && sceAgcAcbSetWorkloadsActive(nullptr, 1, ids, 2) == nullptr && sceAgcAcbSetWorkloadComplete(nullptr, 1, 3) == nullptr && sceAgcAcbSetWorkloadStreamInactive(nullptr, 1) == nullptr, "a workload call with a null command buffer for a registered stream returned a packet"); check(sceAgcDriverUnregisterWorkloadStream(1) == 0, "workload stream unregistration failed"); + check(sceAgcAcbSetWorkloadComplete(®istered.buffer, 1, 3) == nullptr, "ACB workload complete packet after unregistration"); } void testIndexBuffer() { @@ -593,6 +616,32 @@ void testMemory() { check(storage.words == beforeMask, "invalid mask patch modified packet memory"); } +void testEndOfPipePatches() { + std::array packet{}; + packet[0] = Agc::Command::Header(0x49u, 8); + packet[1] = 0xf0000fffu; + packet[2] = 2u << 29u; + check(sceAgcQueueEndOfPipeActionPatchData(packet.data(), 0x1122334455667788ull) == 0, "end-of-pipe data patch failed"); + check(packet[5] == 0x55667788u && packet[6] == 0x11223344u, "64-bit end-of-pipe data patch did not write both halves"); + packet[2] = 1u << 29u; + check(sceAgcQueueEndOfPipeActionPatchData(packet.data(), 0x0badf00du) == 0, "32-bit end-of-pipe data patch failed"); + check(packet[5] == 0x0badf00du && packet[6] == 0u, "32-bit end-of-pipe data patch wrote the wrong words"); + expectFailure([&] { sceAgcQueueEndOfPipeActionPatchData(packet.data(), 0x100000000ull); }); + check(packet[5] == 0x0badf00du && packet[6] == 0u, "a rejected 32-bit data patch changed the packet"); + check(sceAgcQueueEndOfPipeActionPatchGcrCntl(packet.data(), 0x0a5u) == 0, "end-of-pipe GCR control patch failed"); + check(packet[1] == (0xf0000fffu | (0x0a5u << 12u)), "end-of-pipe GCR control patch did not keep the other control bits"); + check(sceAgcQueueEndOfPipeActionPatchGcrCntl(packet.data(), 0x100u) == 0 && packet[1] == (0xf0000fffu | (0x300u << 12u)), "end-of-pipe GCR control 0x100 did not gain 0x200"); + check(sceAgcQueueEndOfPipeActionPatchGcrCntl(packet.data(), 0x200u) == 0 && packet[1] == (0xf0000fffu | (0x200u << 12u)), "end-of-pipe GCR control 0x200 changed"); + const auto before = packet; + expectFailure([&] { sceAgcQueueEndOfPipeActionPatchGcrCntl(packet.data(), 0x1000u); }); + check(packet == before, "a rejected GCR control patch changed the packet"); + packet[0] = Agc::Command::Header(0x47u, 6); + expectFailure([&] { sceAgcQueueEndOfPipeActionPatchData(packet.data(), 1); }); + expectFailure([&] { sceAgcQueueEndOfPipeActionPatchGcrCntl(packet.data(), 1); }); + packet[0] = before[0]; + check(packet == before, "a data or GCR control patch of another packet changed it"); +} + struct AtomicGdsAllocation { Storage storage; std::uint32_t capacity = 15; @@ -798,6 +847,32 @@ void testDefaultsTypes() { checkCompactTypes(sceAgcGetRegisterDefaults2Internal(0), 15, 0xf58fea31u, 0x00040400u, 0); } +void testDispatchPackets() { + Storage graphics; + const auto* indirect = sceAgcDcbDispatchIndirect(&graphics.buffer, 0x40, 0x8); + check(indirect == graphics.words.data() && graphics.words[0] == 0xc0011600u && graphics.words[1] == 0x40u && graphics.words[2] == 0x49u, "graphics DISPATCH_INDIRECT packet mismatch"); + Storage compute; + const auto* arguments = reinterpret_cast(0x123456789abcull); + sceAgcAcbDispatchIndirect(&compute.buffer, arguments, 0x2000u); + const std::array computeExpected{0xc0021600u, 0x56789abcu, 0x1234u, 0x2041u}; + check(std::equal(computeExpected.begin(), computeExpected.end(), compute.words.begin()), "compute DISPATCH_INDIRECT packet mismatch"); + Storage direct; + sceAgcCbDispatch(&direct.buffer, 7, 3, 1, 0x8000u); + const std::array directExpected{0xc0031500u, 7, 3, 1, 0x8041u}; + check(std::equal(directExpected.begin(), directExpected.end(), direct.words.begin()), "DISPATCH_DIRECT packet mismatch"); + Storage rejected; + rejected.words.fill(0xa5a5a5a5u); + const auto rejectedWords = rejected.words; + const auto* rejectedCursor = rejected.buffer.cursor_up; + expectFailure([&] { sceAgcCbDispatch(&rejected.buffer, 1, 1, 1, 0x100u); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcAcbDispatchIndirect(&rejected.buffer, reinterpret_cast(0x1002ull), 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcAcbDispatchIndirect(&rejected.buffer, nullptr, 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + check(sceAgcDcbDispatchIndirectGetSize() == 12 && sceAgcAcbDispatchIndirectGetSize() == 16 && sceAgcCbDispatchGetSize() == 20, "dispatch packet sizes"); +} + void testEventWrite() { Storage storage; const auto* samplePacket = sceAgcDcbEventWrite(&storage.buffer, 0x38, reinterpret_cast(0x1000)); @@ -839,6 +914,35 @@ void testEventWrite() { check(cacheFlushWithAddress.words[0] == 0xc0004600u && cacheFlushWithAddress.words[1] == 0x16u, "incorrect event 0x16 with address packet"); } +void testAcquireMem() { + Storage storage; + const auto* base = reinterpret_cast(0x1234567890ull); + const auto* packet = sceAgcDcbAcquireMem(&storage.buffer, 0, 3, 0x1234u, base, 0x1000, 400); + const std::array expected{0xc0065800u, 3, 0x11, 0, 0x12345678u, 0, 10, 0x1234u}; + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), storage.words.begin()), "acquire packet does not round its range outward to 256 bytes"); + Storage whole; + sceAgcDcbAcquireMem(&whole.buffer, 0, 0, 0, base, 0xffffffffffffffffull, 0); + check(whole.words[0] == 0xc0065800u && whole.words[2] == 0 && whole.words[4] == 0 && whole.words[6] == 0, "an unbounded acquire did not cover the whole address space"); + Storage large; + sceAgcDcbAcquireMem(&large.buffer, 0, 0, 0, base, (1ull << 40u) + 1u, 0); + check(large.words[2] == 0 && large.words[4] == 0, "an acquire over 1 TiB did not cover the whole address space"); + Storage asynchronous; + sceAgcAcbAcquireMem(&asynchronous.buffer, 0x55u, base, 0x100, 80); + const std::array compute{0xc0065800u, 0x80000000u, 2, 0, 0x12345678u, 0, 2, 0x55u}; + check(std::equal(compute.begin(), compute.end(), asynchronous.words.begin()), "the asynchronous compute acquire did not select engine 1 with no CB/DB operation"); + Storage rejected; + rejected.words.fill(0xa5a5a5a5u); + const auto rejectedWords = rejected.words; + const auto* rejectedCursor = rejected.buffer.cursor_up; + expectFailure([&] { sceAgcDcbAcquireMem(&rejected.buffer, 2, 0, 0, base, 0x100, 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcDcbAcquireMem(&rejected.buffer, 0, 0, 0x80000u, base, 0x100, 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcDcbAcquireMem(&rejected.buffer, 0, 0, 0, base, 0x100, 40u * 0x10000u); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + check(sceAgcDcbAcquireMemGetSize() == 32 && sceAgcAcbAcquireMemGetSize() == 32, "acquire packet size"); +} + } int main(int argc, char** argv) { @@ -852,7 +956,9 @@ int main(int argc, char** argv) { } testPackets(); testEventWrite(); + testAcquireMem(); testNop(); + testDispatchPackets(); testClearState(); testIndexedIndirectDraws(); testTagHeaders(); @@ -869,6 +975,7 @@ int main(int argc, char** argv) { testRegisterRange(); testPacketPayloadAddress(); testMemory(); + testEndOfPipePatches(); testAtomicGds(); testDebugRaiseException(); testDefaults(); diff --git a/core/libs/prx/libSceAgc/tests/CreateShader.cpp b/core/libs/prx/libSceAgc/tests/CreateShader.cpp new file mode 100644 index 0000000000..90e87de37f --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/CreateShader.cpp @@ -0,0 +1,117 @@ +#include "prx/libSceAgc/Shader/include/CreateShader.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" + +#include +#include +#include +#include +#include + +namespace { + +const Shader* registered = nullptr; +unsigned registrations = 0; + +alignas(256) const std::array code{0xbf810000u}; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +T* relative(T* target, const void* field) { + return reinterpret_cast(reinterpret_cast(target) - reinterpret_cast(field)); +} + +struct Binary { + Shader shader{}; + std::array registers{}; + std::array context{}; + ShaderSpecialRegs specials{}; + ShaderUserData userData{}; + std::array direct{}; + std::array sharps{}; + std::array inputs{}; + std::array outputs{}; +}; + +void fill(Binary& binary, ShaderRegs::ShaderBinaryType type, std::uint32_t low) { + binary.shader.file_header = ShaderRegs::SHADER_FILE_HEADER_MAGIC; + binary.shader.version = ShaderRegs::SHADER_VERSION; + binary.shader.header_size = sizeof(Shader); + binary.shader.shader_size = sizeof(code); + binary.shader.type = static_cast(type); + binary.registers = {{{low, 0x10u}, {low + 1u, 0xabcdef00u}, {0x20au, 0x1234u}}}; + binary.shader.num_sh_registers = static_cast(binary.registers.size()); + binary.shader.sh_registers = relative(binary.registers.data(), &binary.shader.sh_registers); + binary.shader.specials = relative(&binary.specials, &binary.shader.specials); +} + +Shader* create(Binary& binary) { + registered = nullptr; + registrations = 0; + Shader* created = nullptr; + check(sceAgcCreateShader(&created, &binary.shader, code.data()) == 0, "sceAgcCreateShader failed"); + check(created == &binary.shader && registered == &binary.shader && registrations == 1, "the created shader is not the header, registered once"); + check(binary.shader.code == code.data(), "the code address was not stored in the header"); + return created; +} + +void testCompute() { + Binary binary; + fill(binary, ShaderRegs::ShaderBinaryType::Cs, ShaderRegs::COMPUTE_PGM_LO); + create(binary); + check(binary.shader.sh_registers == binary.registers.data() && binary.shader.specials == &binary.specials, "the relative register and special pointers were not resolved"); + check(binary.shader.user_data == nullptr && binary.shader.cx_registers == nullptr && binary.shader.input_semantics == nullptr && binary.shader.output_semantics == nullptr, "a null relative pointer did not stay null"); + const auto program = reinterpret_cast(code.data()) + 0x1000u; + check(binary.registers[0].value == static_cast(program >> 8u), "COMPUTE_PGM_LO does not hold the code address plus its offset"); + check(binary.registers[1].value == (0xabcdef00u | static_cast((program >> 40u) & 0xffu)), "COMPUTE_PGM_HI did not keep its upper bits around the address byte"); + check(binary.registers[2].offset == 0x20au && binary.registers[2].value == 0x1234u, "a register other than the program address changed"); +} + +void testPixelWithUserData() { + Binary binary; + fill(binary, ShaderRegs::ShaderBinaryType::Ps, ShaderRegs::SPI_SHADER_PGM_LO_PS); + binary.shader.user_data = relative(&binary.userData, &binary.shader.user_data); + binary.shader.cx_registers = relative(binary.context.data(), &binary.shader.cx_registers); + binary.shader.num_cx_registers = 1; + binary.shader.input_semantics = relative(binary.inputs.data(), &binary.shader.input_semantics); + binary.shader.output_semantics = relative(binary.outputs.data(), &binary.shader.output_semantics); + binary.userData.direct_resource_offset = relative(binary.direct.data(), &binary.userData.direct_resource_offset); + for (std::size_t i = 0; i < binary.sharps.size(); ++i) binary.userData.sharp_resource_offset[i] = relative(&binary.sharps[i], &binary.userData.sharp_resource_offset[i]); + create(binary); + check(binary.shader.user_data == &binary.userData && binary.shader.cx_registers == binary.context.data(), "the relative user data and context register pointers were not resolved"); + check(binary.shader.input_semantics == binary.inputs.data() && binary.shader.output_semantics == binary.outputs.data(), "the relative semantic pointers were not resolved"); + check(binary.userData.direct_resource_offset == binary.direct.data(), "the user data's direct resource pointer was not resolved"); + for (std::size_t i = 0; i < binary.sharps.size(); ++i) check(binary.userData.sharp_resource_offset[i] == &binary.sharps[i], "a user data sharp resource pointer was not resolved"); + const auto program = reinterpret_cast(code.data()) + 0x1000u; + check(binary.registers[0].value == static_cast(program >> 8u) && binary.registers[1].value == (0xabcdef00u | static_cast((program >> 40u) & 0xffu)), "SPI_SHADER_PGM_LO_PS and _HI_PS do not hold the code address"); +} + +void testWithoutProgramAddress() { + Binary binary; + fill(binary, ShaderRegs::ShaderBinaryType::Fs, 0x30u); + const auto registers = binary.registers; + create(binary); + check(binary.registers[0].offset == registers[0].offset && binary.registers[0].value == registers[0].value && binary.registers[1].value == registers[1].value && binary.registers[2].value == registers[2].value, "a fetch shader's registers changed"); +} + +} + +extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader) { + registered = shader; + ++registrations; +} + +int main() { + try { + testCompute(); + testPixelWithUserData(); + testWithoutProgramAddress(); + std::puts("AGC create shader tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/DmaData.cpp b/core/libs/prx/libSceAgc/tests/DmaData.cpp index 880035d394..2000994c62 100644 --- a/core/libs/prx/libSceAgc/tests/DmaData.cpp +++ b/core/libs/prx/libSceAgc/tests/DmaData.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgc/DcbFlow/include/Memory.hpp" #include "prx/libSceAgc/Acb/include/Memory.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" +#include "prx/libSceAgc/Patch/include/Dma.hpp" #include #include @@ -73,6 +74,26 @@ void testCommand(TWriter writer, TSize size, std::uint32_t control) { check(shortBuffer.words == before && shortBuffer.buffer.cursor_up == shortBuffer.words.data(), "failed DMA write modified the buffer"); } +void testPatches() { + constexpr std::uint64_t PatchedDestination = 0x0000aabbccddeeffull; + constexpr std::uint64_t PatchedSource = 0x0000102030405060ull; + for (auto* writer : {writeCompute, writeDraw}) { + Storage storage; + auto* packet = writer(&storage.buffer); + auto expected = storage.words; + check(sceAgcDmaDataPatchSetDstAddressOrOffset(packet, PatchedDestination) == 0, "the destination patch failed"); + expected[4] = 0xccddeeffu; + expected[5] = 0xaabbu; + check(storage.words == expected, "the destination patch did not replace only words 4 and 5"); + check(sceAgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate(packet, PatchedSource) == 0, "the source patch failed"); + expected[2] = 0x30405060u; + expected[3] = 0x1020u; + check(storage.words == expected, "the source patch did not replace only words 2 and 3"); + check(sceAgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate(packet, 0xfedcba9876543210ull) == 0 && packet[2] == 0x76543210u && packet[3] == 0xfedcba98u, "the source patch did not keep a 64-bit immediate whole"); + } + +} + } int main() { @@ -80,6 +101,7 @@ int main() { testCommand(writeCompute, sceAgcAcbDmaDataGetSize, 0x44102000u); testCommand(writeDraw, sceAgcDcbDmaDataGetSize, 0xc4102001u); check(sceAgcAcbDmaDataGetSize() == sceAgcDcbDmaDataGetSize(), "compute and draw DMA sizes differ"); + testPatches(); std::puts("AGC DMA data tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgc/tests/EndOfPipeActionType.cpp b/core/libs/prx/libSceAgc/tests/EndOfPipeActionType.cpp new file mode 100644 index 0000000000..dd2ecba3fd --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/EndOfPipeActionType.cpp @@ -0,0 +1,43 @@ +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/Patch/include/QueueEndOfPipeAction.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include +#include + +namespace { + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; +}; + +std::array release(std::uint8_t action, const volatile Label* label) { + Storage storage; + const auto* packet = sceAgcCbReleaseMem(&storage.buffer, action, 0x0a5, 1, 2, label, 2, 0x1122334455667788ull, 0, 0, 0, 0); + std::array words{}; + for (std::size_t index = 0; index < words.size(); ++index) words[index] = packet[index]; + return words; +} + +int fail(const char* message) { + std::fprintf(stderr, "%s\n", message); + return 1; +} + +} + +int main() { + alignas(8) static Label label{}; + auto packet = release(0x28, &label); + const auto original = packet; + for (const std::uint8_t action : {0x00u, 0x14u, 0x2eu, 0x2fu, 0x30u, 0x3fu}) { + if (sceAgcQueueEndOfPipeActionPatchType(packet.data(), action) != 0) return fail("the end-of-pipe type patch failed"); + if (packet != release(action, &label)) return fail("a patched end-of-pipe action differs from a release built with that action"); + } + if (sceAgcQueueEndOfPipeActionPatchType(packet.data(), 0x28) != 0 || packet != original) return fail("patching the action back did not restore the packet"); + std::puts("AGC end-of-pipe type patch tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAgc/tests/LodStats.cpp b/core/libs/prx/libSceAgc/tests/LodStats.cpp new file mode 100644 index 0000000000..78994aa2ca --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/LodStats.cpp @@ -0,0 +1,43 @@ +#include "prx/libSceAgc/DcbDraw/include/Stats.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include +#include + +namespace { + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; +}; + +int fail(const char* message) { + std::fprintf(stderr, "%s\n", message); + return 1; +} + +} + +int main() { + alignas(64) static std::array report{}; + const auto address = reinterpret_cast(report.data()); + for (const std::uint8_t policy : {0u, 2u}) { + Storage dcb; + const auto* first = sceAgcDcbGetLodStats(&dcb.buffer, policy, nullptr, 0, 0, 0, 0, 0); + const auto* second = sceAgcDcbGetLodStats(&dcb.buffer, policy, report.data(), 0x840, 0, 0, 1, 0); + if (first != dcb.words.data() || second != dcb.words.data() + 5 || dcb.buffer.cursor_up != dcb.words.data() + 10) return fail("LOD stats packets are not five words each"); + const std::array expectedFirst{0xc0031000u, 0, 0, 0, static_cast(policy) << 28u}; + const std::array expectedSecond{0xc0031000u, 0x840u, static_cast(address), static_cast(address >> 32u), (static_cast(policy) << 28u) | (1u << 19u)}; + for (std::size_t index = 0; index < 5; ++index) { + if (dcb.words[index] != expectedFirst[index]) return fail("the sizing LOD stats packet differs"); + if (dcb.words[5 + index] != expectedSecond[index]) return fail("the reporting LOD stats packet differs"); + } + Storage fields; + sceAgcDcbGetLodStats(&fields.buffer, 3, report.data(), 0x40, 0xff, 1, 0, 0xff); + if (fields.words[4] != ((3u << 28u) | (1u << 18u) | (0xffu << 10u) | (0xffu << 2u))) return fail("the LOD stats control word lost a field"); + } + std::puts("AGC LOD stats tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAgc/tests/RewindPatch.cpp b/core/libs/prx/libSceAgc/tests/RewindPatch.cpp new file mode 100644 index 0000000000..bdeb5cbd5e --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/RewindPatch.cpp @@ -0,0 +1,58 @@ +#include "prx/libSceAgc/DcbFlow/include/Control.hpp" +#include "prx/libSceAgc/Patch/include/Rewind.hpp" + +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t Sentinel = 0xabcdef01u; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + + Storage() { + words.fill(Sentinel); + } +}; + +void testRewind() { + check(sceAgcDcbRewindGetSize() == 8, "REWIND packet size"); + for (std::uint32_t state : {0u, 1u}) { + Storage storage; + auto* packet = sceAgcDcbRewind(&storage.buffer, state); + check(packet == storage.words.data() && packet[0] == 0xc0005900u && packet[1] == state << 31u, "REWIND does not carry the initial state in bit 31"); + check(storage.buffer.cursor_up == packet + sceAgcDcbRewindGetSize() / sizeof(std::uint32_t), "REWIND did not advance the cursor by its size"); + check(storage.words[2] == Sentinel, "REWIND wrote past its two words"); + } +} + +void testRewindStatePatch() { + Storage storage; + auto* packet = sceAgcDcbRewind(&storage.buffer, 0); + check(sceAgcRewindPatchSetRewindState(packet, 1) == 0 && packet[0] == 0xc0005900u && packet[1] == 0x80000000u, "the rewind state patch did not set bit 31"); + check(sceAgcRewindPatchSetRewindState(packet, 0) == 0 && packet[0] == 0xc0005900u && packet[1] == 0, "the rewind state patch did not clear bit 31"); + check(sceAgcAsyncRewindPatchSetRewindState(packet, 1) == 0 && packet[1] == 0x80000000u, "the asynchronous rewind state patch did not set bit 31"); + check(storage.words[2] == Sentinel, "a rewind state patch wrote past the packet"); +} + +} + +int main() { + try { + testRewind(); + testRewindStatePatch(); + std::puts("AGC rewind tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 401e0a2b9d..34a8b197e4 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1568,6 +1568,29 @@ add_custom_command( VERBATIM ) +add_test_executable(agc_pipeline_cache_allocation_tests EXCLUDE_FROM_ALL tests/PipelineCacheFailures.cpp + Graphics/src/PipelineCache.cpp tests/AllocationFailure.cpp) +target_include_directories(agc_pipeline_cache_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +target_link_libraries(agc_pipeline_cache_allocation_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_pipeline_cache_allocation_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_pipeline_cache_allocation_tests) +if(BUILD_TESTING) + set_target_properties(agc_pipeline_cache_allocation_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + foreach(mode startup now busy destructor worker) + add_test(NAME agc_pipeline_cache_allocation_${mode} COMMAND agc_pipeline_cache_allocation_tests ${mode}) + set_tests_properties(agc_pipeline_cache_allocation_${mode} PROPERTIES TIMEOUT 25) + if(WIN32) + set_tests_properties(agc_pipeline_cache_allocation_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + if(WIN32) + add_test(NAME agc_pipeline_cache_read_retry COMMAND agc_pipeline_cache_allocation_tests read-retry) + set_tests_properties(agc_pipeline_cache_read_retry PROPERTIES TIMEOUT 25 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL tests/Recorder.cpp tests/HostImportLifetime.cpp @@ -1846,6 +1869,8 @@ if(BUILD_TESTING) set_tests_properties(agc_draw_snapshot_epoch PROPERTIES SKIP_RETURN_CODE 77) add_test(NAME agc_multisample_guest_access COMMAND agc_driver_recorder_tests --multisample-only) set_tests_properties(agc_multisample_guest_access PROPERTIES SKIP_RETURN_CODE 77) + add_test(NAME agc_repeat_storage_guard COMMAND agc_driver_recorder_tests --repeat-storage-guard-only) + set_tests_properties(agc_repeat_storage_guard PROPERTIES SKIP_RETURN_CODE 77) add_test(NAME agc_draw_inputs_in_place COMMAND agc_driver_recorder_tests --draw-inputs-in-place-only) add_test(NAME agc_pending_write_snapshot COMMAND agc_driver_recorder_tests --pending-write-snapshot-only) set_tests_properties(agc_pending_write_snapshot PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) @@ -1866,7 +1891,7 @@ if(BUILD_TESTING) add_test(NAME agc_metadata_pass COMMAND agc_driver_recorder_tests --metadata-pass-only) set_tests_properties(agc_metadata_pass PROPERTIES SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_driver_recorder agc_idle_publish_wait agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_draw_snapshot_epoch agc_multisample_guest_access agc_draw_inputs_in_place agc_draw_inputs_copied agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass agc_tiled_resolve PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_driver_recorder agc_idle_publish_wait agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_draw_snapshot_epoch agc_multisample_guest_access agc_repeat_storage_guard agc_draw_inputs_in_place agc_draw_inputs_copied agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass agc_tiled_resolve PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") add_test(NAME agc_host_import_second_mapping COMMAND agc_driver_recorder_tests --host-import-second-mapping-only) set_tests_properties(agc_host_import_second_mapping PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION @@ -2068,6 +2093,9 @@ if(BUILD_TESTING) add_test(NAME agc_performance_overlay_device_selection_cpu COMMAND agc_performance_overlay_tests device-selection-cpu) set_tests_properties(agc_performance_overlay agc_performance_overlay_device_selection PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) set_tests_properties(agc_performance_overlay_device_selection_cpu PROPERTIES TIMEOUT 30) + if(ANYPS5_ENABLE_ASAN) + set_tests_properties(agc_performance_overlay PROPERTIES TIMEOUT 120) + endif() if(WIN32) set_tests_properties(agc_performance_overlay_statistics agc_keyboard_overlay agc_performance_overlay agc_performance_overlay_device_selection agc_performance_overlay_device_selection_cpu PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -2247,6 +2275,9 @@ file(GLOB agcExecutionTestSources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ set(agcExecutionTests) foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources) get_filename_component(agcExecutionTestName ${agcExecutionTestSource} NAME_WE) + if(NOT WIN32 AND agcExecutionTestName STREQUAL "CommittedReadMapping") + continue() + endif() if(ANYPS5_AGC_EXECUTION_TESTS AND NOT agcExecutionTestName IN_LIST ANYPS5_AGC_EXECUTION_TESTS) set(agcExecutionTestSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/ExecutionTestNotBuilt.cpp) endif() @@ -2427,6 +2458,9 @@ if(BUILD_TESTING) add_test(NAME agc_driver_two_sample_dithered_alpha_to_coverage COMMAND agc_driver_alpha_to_coverage_tests --dithered2) set(multisampledDccClearTests) foreach(samples IN ITEMS 2 4 8) + set(testName "agc_driver_msaa_dcc_srgb_clear_${samples}") + add_test(NAME ${testName} COMMAND agc_driver_alpha_to_coverage_tests --dcc-srgb-clear ${samples}) + list(APPEND multisampledDccClearTests ${testName}) set(testName "agc_driver_msaa_dcc_clear_${samples}") add_test(NAME ${testName} COMMAND agc_driver_alpha_to_coverage_tests --dcc-fast-clear ${samples}) list(APPEND multisampledDccClearTests ${testName}) @@ -2503,6 +2537,16 @@ target_include_directories(agc_driver_color565_target_tests PRIVATE $) +if(BUILD_TESTING) + add_test(NAME agc_driver_tessellation_depth_range_remap COMMAND agc_driver_tessellation_tests --depth-remap) + set_tests_properties(agc_driver_tessellation_depth_range_remap PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_tessellation_depth_range_remap PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + target_include_directories(agc_driver_depth_bounds_emulation_draw_tests PRIVATE $) foreach(stage IN ITEMS vert frag) @@ -2554,6 +2598,7 @@ target_include_directories(agc_driver_shutdown_threads_tests PRIVATE $) target_include_directories(agc_driver_failed_capture_dump_tests PRIVATE $) +target_include_directories(agc_driver_gcn_buffer_format_lookup_tests PRIVATE $) target_include_directories(agc_driver_prepared_publication_tests PRIVATE $) @@ -2666,6 +2711,10 @@ if(BUILD_TESTING) set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME ${agcTestName} COMMAND ${agcTest}) set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(agcTestName STREQUAL "agc_driver_image_table") + set_tests_properties(${agcTestName} PROPERTIES TIMEOUT 30 + FAIL_REGULAR_EXPRESSION "SYNC-HAZARD-|VUID-|was not found but was requested by env var VK_INSTANCE_LAYERS") + endif() if(WIN32) set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") @@ -2715,12 +2764,15 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_sparse_indirect_indices_unreadable agc_driver_sparse_indirect_indices_no_size agc_driver_sparse_indirect_indices_no_size_unreadable agc_driver_sparse_indirect_indices_no_size_unreadable_record PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_draw_cache_relocation_recipe_verify COMMAND agc_driver_draw_cache_relocation_tests) + set_tests_properties(agc_driver_draw_cache_relocation_recipe_verify PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_VERIFY_DRAW_RECIPE=1;APS5_PIPELINED_DRAWS=0" SKIP_RETURN_CODE 77 TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_draw_cache_relocation_evicted COMMAND agc_driver_draw_cache_relocation_tests --evicted) add_test(NAME agc_driver_draw_cache_relocation_kept COMMAND agc_driver_draw_cache_relocation_tests --kept) set_tests_properties(agc_driver_draw_cache_relocation_evicted agc_driver_draw_cache_relocation_kept PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_DRAW_CACHE_ENTRIES=2" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) - set_tests_properties(agc_driver_draw_cache_relocation_evicted agc_driver_draw_cache_relocation_kept PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_driver_draw_cache_relocation_recipe_verify agc_driver_draw_cache_relocation_evicted agc_driver_draw_cache_relocation_kept PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() set_tests_properties(agc_driver_pixel_implicit_derivatives PROPERTIES TIMEOUT 30) @@ -2780,6 +2832,11 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_driver_partially_resident_pending_stores PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_shader_disk_cache_pack_ownership COMMAND agc_shader_disk_cache_tests --prebuilt-pack-ownership) + set_tests_properties(agc_shader_disk_cache_pack_ownership PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_shader_disk_cache_pack_ownership PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_shader_disk_cache_large_file COMMAND agc_shader_disk_cache_tests --large-file) if(Python3_Interpreter_FOUND) set(housekeepingModes plain) @@ -2894,6 +2951,7 @@ if(BUILD_TESTING) set_tests_properties(agc_pipeline_journal_long_paths PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_pipeline_library_clear_waits_for_build COMMAND agc_driver_graphics_tests --pipeline-library-clear-waits-for-build) add_test(NAME agc_pipeline_store_lazy_buffer_pool COMMAND agc_driver_graphics_tests --pipeline-store-lazy-buffer-pool) + add_test(NAME agc_pipeline_store_touch COMMAND agc_driver_graphics_tests --pipeline-store-touch) add_test(NAME agc_pipeline_store_growth COMMAND agc_driver_graphics_tests --pipeline-store-growth) add_test(NAME agc_pipeline_store_fixed_bound COMMAND agc_driver_graphics_tests --pipeline-store-growth) add_test(NAME agc_pipeline_store_replay_growth COMMAND agc_driver_graphics_tests --pipeline-store-replay-growth) @@ -2901,12 +2959,12 @@ if(BUILD_TESTING) set_tests_properties(agc_pipeline_store_fixed_bound PROPERTIES ENVIRONMENT "APS5_PIPELINE_STORE_ENTRIES=1024") set_tests_properties(agc_pipeline_journal PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") set_tests_properties(agc_pipeline_library_clear_waits_for_build PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") - set_tests_properties(agc_pipeline_store_lazy_buffer_pool PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + set_tests_properties(agc_pipeline_store_lazy_buffer_pool agc_pipeline_store_touch PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) add_test(NAME agc_pipeline_journal_extended_paths COMMAND agc_driver_graphics_tests --pipeline-journal-extended-paths) set_tests_properties(agc_pipeline_journal_extended_paths PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") set_tests_properties(agc_pipeline_journal_long_paths agc_pipeline_journal_extended_paths PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") - set_tests_properties(agc_pipeline_journal agc_pipeline_library_clear_waits_for_build agc_pipeline_store_lazy_buffer_pool agc_pipeline_store_growth agc_pipeline_store_fixed_bound agc_pipeline_store_replay_growth PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_pipeline_journal agc_pipeline_library_clear_waits_for_build agc_pipeline_store_lazy_buffer_pool agc_pipeline_store_touch agc_pipeline_store_growth agc_pipeline_store_fixed_bound agc_pipeline_store_replay_growth PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() set_tests_properties(agc_driver_sparse_buffer_holes PROPERTIES TIMEOUT 30) if(WIN32) @@ -3005,6 +3063,13 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_driver_f64_denorm_preserve_spirv PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_volume_direct_readback COMMAND agc_driver_layered_volume_draw_tests) + set_tests_properties(agc_driver_volume_direct_readback PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_TEST_DIRECT_READBACK=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + if(WIN32) + set_tests_properties(agc_driver_volume_direct_readback PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_f16_to_f32_unpack_spirv COMMAND agc_driver_f16_to_f32_unpack_tests --verify-spirv) set_tests_properties(agc_driver_f16_to_f32_unpack_spirv PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) @@ -3111,6 +3176,7 @@ if(BUILD_TESTING) $) set_tests_properties(agc_driver_guest_memory_lifetime_converted PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIN_WAIT_MS=1000" SKIP_RETURN_CODE 77 TIMEOUT 30) + add_asan_test_options(agc_driver_guest_memory_lifetime_converted verify_asan_link_order=0) endif() set_tests_properties(agc_driver_flat_store PROPERTIES RUN_SERIAL TRUE) set_target_properties(agc_driver_flat_apertures_tests PROPERTIES EXCLUDE_FROM_ALL OFF) @@ -3292,6 +3358,13 @@ if(BUILD_TESTING) endif() endforeach() endforeach() + add_test(NAME agc_driver_depth_plane_cpu_write_after_memo COMMAND agc_driver_depth_plane_partial_store_tests --cpu-write-after-memo) + set_tests_properties(agc_driver_depth_plane_cpu_write_after_memo PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_depth_plane_cpu_write_after_memo PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_depth_plane_partial_store_unimported COMMAND agc_driver_depth_plane_partial_store_tests) add_test(NAME agc_driver_depth_plane_partial_store_buffer_copy COMMAND agc_driver_depth_plane_partial_store_tests --buffer-copy) foreach(partialStoreTest agc_driver_depth_plane_partial_store_unimported agc_driver_depth_plane_partial_store_buffer_copy) @@ -3715,6 +3788,12 @@ if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS) add_test(NAME agc_compute_pipeline_replay COMMAND agc_compute_pipeline_replay_tests) set_tests_properties(agc_compute_pipeline_replay PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77) add_dependencies(agc_compute_pipeline_replay_tests agc_compute_pipeline_replay agc_driver_branch_past_endpgm_tests) + add_test(NAME agc_compute_pipeline_inspection COMMAND ${CMAKE_COMMAND} + -DREPLAY=$ + -DVULKAN_PROBE=$ + -DOUTPUT_ROOT=${CMAKE_CURRENT_BINARY_DIR}/capture-tests + -P ${CMAKE_CURRENT_SOURCE_DIR}/tests/ComputePipelineInspectRun.cmake) + set_tests_properties(agc_compute_pipeline_inspection PROPERTIES TIMEOUT 60 SKIP_REGULAR_EXPRESSION "compute pipeline inspection: skipped") add_test(NAME agc_compute_pipeline_capture_run COMMAND ${CMAKE_COMMAND} -DFIXTURE=$ -DREPLAY=$ @@ -3722,7 +3801,7 @@ if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS) -P ${CMAKE_CURRENT_SOURCE_DIR}/tests/ComputePipelineCaptureRun.cmake) set_tests_properties(agc_compute_pipeline_capture_run PROPERTIES TIMEOUT 30 SKIP_REGULAR_EXPRESSION "compute pipeline capture run: skipped, no usable Vulkan device") if(WIN32) - set_tests_properties(agc_compute_pipeline_replay agc_compute_pipeline_capture_run PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_compute_pipeline_replay agc_compute_pipeline_capture_run agc_compute_pipeline_inspection PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() 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 d3f3da21fa..7ae26d17b2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -98,7 +98,7 @@ struct DrawEntryCounters { std::uint64_t registerKeyLookups = 0, registerKeyHits = 0, decodeSkipped = 0, decodePartial = 0, facadeMismatches = 0, verifyDecodes = 0, verifyDecodeMismatches = 0; double keyUs = 0; - std::uint64_t absentNewRegisters = 0, absentUserWords = 0, absentEvicted = 0, absentNeverInserted = 0, evictions = 0; + std::uint64_t absentNewRegisters = 0, absentUserWords = 0, absentEvicted = 0, absentNeverInserted = 0, evictions = 0, byteEvictions = 0; std::array registerKeyMissChanges{}; std::uint64_t differingSameRuns = 0, differingWords = 0, differingRunsChanged = 0; std::uint64_t dataHits = 0, dataStagesReused = 0, dataStagesCompiled = 0, dataVerified = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 7d89a2589f..9d6845ff7f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -107,7 +107,9 @@ public: std::uint64_t variantBytes = 0; }; DispatchCacheFootprint DispatchCacheSize(); + DispatchCacheFootprint DrawCacheSize(); static std::uint64_t DispatchCacheBytes(); + static std::uint64_t DrawCacheBytes(); static ShaderMemory::PendingWrite QueryPendingSpan(std::uint64_t address, std::size_t bytes, std::span known); std::shared_ptr RegisteredShaders(); @@ -143,6 +145,7 @@ private: void completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites, const std::shared_ptr& progress); static bool waitFree(const Submission& submission); bool queue0Before(std::uint64_t received) const; + bool waitFreeBefore(std::uint64_t received) const; bool orderReleased(std::uint32_t queue, std::uint64_t received) const; void noteWaitBlocked(std::uint32_t queue, std::uint64_t awaited, bool blocked); void reportPresents(double waitedMs, std::size_t inFlight); @@ -274,7 +277,7 @@ private: void noteForeignWriter(std::uint64_t begin, std::uint64_t end, std::uint32_t queue); void noteDrawWriters(std::span stages, std::uint32_t queue); static std::vector> drawWriteRanges(const Graphics::State& graphics, std::span stages); - void commitDraw(std::shared_ptr localDevice, std::uint32_t queue, const Graphics::State& graphics, const Pm4::DrawParameters& drawParameters, std::span stages, std::span snapshots, const std::shared_ptr& recipe, const std::vector>& recipeStages, std::uint64_t drawKey, VulkanDevice::DrawTraceContext trace = {}); + void commitDraw(std::shared_ptr localDevice, std::uint32_t queue, const Graphics::State& graphics, const Pm4::DrawParameters& drawParameters, std::span stages, std::span snapshots, const std::shared_ptr& recipe, const std::vector>& recipeStages, std::uint64_t drawKey, VulkanDevice::DrawTraceContext trace = {}, std::uint64_t shape = 0); bool labelPacketQueues(std::span packet, std::uint32_t opcode); bool enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue); bool enqueueDmaPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue, const QueueState& state); @@ -428,6 +431,7 @@ private: std::uint64_t drawCacheHits = 0, drawCacheEvictions = 0, drawCacheVariants = 0, drawCacheVariantBytes = 0; std::optional drawKeySightings; std::optional drawShapeSightings; + std::uint64_t drawCacheByteEvictions = 0; DrawEntryCounters drawEntryCounters; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp index e45f794362..fdd4ff1baa 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp @@ -53,6 +53,8 @@ struct QueueWorker { std::atomic queued{0}; std::unordered_map unfinishedWrites; + std::uint64_t executingWaitFree = 0; + bool executingWaits = false; std::thread thread; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp index a29def6cff..52776f22e6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp @@ -9,7 +9,8 @@ namespace AgcDriver::DriverDetail { -inline constexpr std::uint32_t SubmissionQueueEnd = 0x58; +inline constexpr std::uint32_t SubmissionQueueEnd = 0x59; +inline constexpr std::uint32_t UnmeasuredSubmissionQueueEnd = 0x58; std::size_t SubmissionRingCapacity(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp index 0d93c2c2a4..3e6af23dcb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp @@ -12,7 +12,7 @@ struct Float32FlushControl { inline Float32FlushControl Float32FlushControlFor(VkDriverId driver, VkBool32 flushToZero, VkShaderFloatControlsIndependence independence) { const bool declared = flushToZero == VK_TRUE && independence != VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE; - return {declared || driver == VK_DRIVER_ID_MOLTENVK, declared}; + return {declared || driver == VK_DRIVER_ID_MOLTENVK || driver == VK_DRIVER_ID_NVIDIA_PROPRIETARY, declared}; } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 7c10d006f5..44e6d64646 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -251,11 +251,24 @@ inline Registers BuildInitialContextRegisters() { return result; } -inline Registers InitialContextRegisters() { +inline const Registers& InitialContextRegisterSet() { static const Registers& initial = *new Registers(BuildInitialContextRegisters()); return initial; } +inline Registers InitialContextRegisters() { + return InitialContextRegisterSet(); +} + +struct SpareRegisters { + Registers registers; + SpareRegisters() = default; + SpareRegisters(const SpareRegisters&) {} + SpareRegisters& operator=(const SpareRegisters&) { return *this; } + SpareRegisters(SpareRegisters&&) noexcept = default; + SpareRegisters& operator=(SpareRegisters&&) noexcept = default; +}; + struct Predication { std::uint64_t address = 0; std::uint32_t operation = 0; @@ -268,6 +281,7 @@ struct QueueState { Registers context = InitialContextRegisters(); Registers userConfig{{0x24a, 0}, {0x24b, 0}}; std::optional savedContext; + SpareRegisters spareContext; std::vector constantRam; std::uint64_t indexBase = 0; std::uint64_t drawIndirectBase = 0; @@ -279,7 +293,18 @@ struct QueueState { Predication predication; void ClearContext() { - context = InitialContextRegisters(); + context = InitialContextRegisterSet(); + } + + void PushContext() { + spareContext.registers = context; + savedContext.emplace(std::move(spareContext.registers)); + } + + void PopContext() { + std::swap(context, *savedContext); + spareContext.registers = std::move(*savedContext); + savedContext.reset(); } std::span ConstantRam() const { diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp index dacb784275..269104b0bc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp @@ -81,6 +81,7 @@ public: }; static SupplyCounts Supplies(); [[nodiscard]] std::vector> SuppliedBlocks() const; + [[nodiscard]] std::size_t Bytes() const; struct VouchCounts { std::uint64_t calls, blocks, refused, words; }; @@ -100,6 +101,10 @@ private: return (bits[index / 64u] & block) == block; } void Set(std::size_t index) { bits[index / 64u] |= std::uint64_t{1} << (index % 64u); } + void Clear(std::size_t index) { bits[index / 64u] &= ~(std::uint64_t{1} << (index % 64u)); } + void ClearMask(const WordMask& other) { + for (std::size_t i = 0; i < bits.size(); ++i) bits[i] &= ~other.bits[i]; + } void SetAll() { bits.fill(~std::uint64_t{0}); } void SetBlock(std::size_t index) { bits[index / 64u] |= ((std::uint64_t{1} << BlockWords) - 1u) << (index % 64u / BlockWords * BlockWords); } void Reset() { bits.fill(0u); } @@ -117,6 +122,7 @@ private: struct Page { std::array words; WordMask valid; + WordMask raw; WordMask read; WordMask recent; bool wordwise = false; @@ -137,6 +143,8 @@ private: static ShaderRecompiler::SrtWriteStamp stampWrites(void* context, std::uint64_t address, std::uint64_t bytes); static bool unchangedSince(void* context, std::uint64_t address, std::uint64_t bytes, const ShaderRecompiler::SrtWriteStamp& stamp); static bool supplyWords(void* context, const std::shared_ptr& words); + static std::uint64_t descriptorScope(void* context, std::size_t bytes); + bool invalidateRawPages(); Page& page(std::uint64_t base); [[nodiscard]] std::size_t pageAtOrAfter(std::uint64_t base) const; Page& addPage(std::size_t position, std::uint64_t base); @@ -158,6 +166,9 @@ private: PendingWriteObserver observe = nullptr; HookWaitCounter hookWaits = nullptr; PendingWriteQuery pendingSpan = nullptr; + std::size_t descriptorSpanBytes = 0; + std::uint64_t rawInvalidations = 0; + std::uint32_t forceSync = 0; }; // The positions, among a dispatch-cache variant's stored words, of the pure flat-SRT leaves a diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp index 8675650a20..d89d7cea43 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp @@ -13,6 +13,10 @@ inline bool WorkgroupScopeFenceUsable(VkDriverId driver) { return driver != VK_DRIVER_ID_AMD_PROPRIETARY; } +inline bool LdsLockFencesRequired(VkDriverId driver) { + return driver == VK_DRIVER_ID_MOLTENVK; +} + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index 1e380f74b4..2b76acfa94 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -344,7 +344,7 @@ public: using DrawTraceContext = Graphics::DrawTraceContext; // `recipe`, when given, receives the DrawRecipe a recorded, cacheable, reusable, direct draw // built for its draw-cache entry (design_cpu_final M8); null otherwise. - void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr, const DrawTraceContext* trace = nullptr); + void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr, const DrawTraceContext* trace = nullptr, std::uint64_t shape = 0); bool DrawInputsInPlace() const; std::optional KnownDrawRejection(const Graphics::State& graphics, std::span shaders) const; void ColorMetadataPass(const Graphics::ColorMetadataPass& pass); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp index 732d7733b4..a691951f22 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp @@ -65,7 +65,8 @@ Driver::DispatchCacheFootprint Driver::DispatchCacheSize() { } std::uint64_t Driver::variantBytes(const DispatchVariant& variant) { - return sizeof(DispatchVariant) + variant.words.size() * sizeof(std::uint32_t) + variant.shared.size() * sizeof(SharedWords) + variant.runs.size() * sizeof(std::pair) + variant.captured.size() * sizeof(ShaderRecompiler::MemoryRegion) + (variant.vertexInfo != nullptr ? sizeof(ShaderRecompiler::ShaderVertexStageInfo) : 0); + const auto held = [](const auto& values) { return static_cast(values.capacity() * sizeof(values[0])); }; + return sizeof(DispatchVariant) + held(variant.runs) + held(variant.words) + held(variant.shared) + held(variant.dataPositions) + held(variant.dataSlots) + held(variant.runBases) + held(variant.shaderDataPatches) + held(variant.captured) + held(variant.spans) + (variant.vertexInfo != nullptr ? sizeof(ShaderRecompiler::ShaderVertexStageInfo) : 0) + (variant.memory != nullptr ? variant.memory->Bytes() : 0); } void Driver::accountVariant(const DispatchVariant& variant, bool added) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 69d9803f93..0069a51f06 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -503,6 +503,11 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } decode = std::move(refreshed); } + if (decode != nullptr && !userPatches.empty() && verifyDrawRecipe()) { + auto patched = std::make_shared(*decode); + for (const auto& patch : userPatches) patched->programs.at(patch.program).userData.at(patch.index) = patch.value; + decode = std::move(patched); + } try { resolveDrawDecode(queue, submission, localDevice->graphicsContext(), decode, registerKey, drawKey, profile); } catch (...) { @@ -1240,10 +1245,10 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { const auto commitPacket = tail.packetOpcode; const auto epoch = DrawFinisher::OnFinisherThread() ? tail.epoch : DrawPipeline::EpochToken().load(std::memory_order_relaxed); auto reads = QueuedDrawReadRanges(drawParameters, stages); - auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), drawParameters, stages = std::move(stages), snapshots = std::move(snapshots), recipe = std::move(recipe), recipeStages = std::move(recipeStages), drawKey, commitQueue, commitPacket, epoch, trace = trace, writablePrograms = std::move(writablePrograms), results = std::move(results), memory = std::move(memory), linked = std::move(linked), stageCaptures = std::move(stageCaptures), matched = std::move(matched), matchedRegions = std::move(matchedRegions), fresh = std::move(fresh), decodeReads = std::move(decodeReads), shaderMemory = std::move(shaderMemory), entry = std::move(entry), dataEntry = std::move(dataEntry), compiledStages = std::move(compiledStages), stageData = std::move(stageData)]() mutable { + auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), drawParameters, stages = std::move(stages), snapshots = std::move(snapshots), recipe = std::move(recipe), recipeStages = std::move(recipeStages), drawKey, shape = tail.shapeKey, commitQueue, commitPacket, epoch, trace = trace, writablePrograms = std::move(writablePrograms), results = std::move(results), memory = std::move(memory), linked = std::move(linked), stageCaptures = std::move(stageCaptures), matched = std::move(matched), matchedRegions = std::move(matchedRegions), fresh = std::move(fresh), decodeReads = std::move(decodeReads), shaderMemory = std::move(shaderMemory), entry = std::move(entry), dataEntry = std::move(dataEntry), compiledStages = std::move(compiledStages), stageData = std::move(stageData)]() mutable { DrawPipeline::FollowEpoch(epoch); GuestMemory::SetCurrentPacket(commitPacket, commitQueue); - commitDraw(localDevice, commitQueue, decode->state, drawParameters, stages, snapshots, recipe, recipeStages, drawKey, trace); + commitDraw(localDevice, commitQueue, decode->state, drawParameters, stages, snapshots, recipe, recipeStages, drawKey, trace, shape); }; auto held = std::make_shared(std::move(commit)); DrawPipeline::Queue0().Enqueue([held] { (*held)(); }, std::move(writes), 0, {}, std::move(reads), true); @@ -1265,14 +1270,14 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { VulkanDevice::NoteRecipe(VulkanDevice::RecipeEvent::Restart, VulkanDevice::RecipeKind::Draw); } std::shared_ptr built; - localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built, tracePtr); + localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built, tracePtr, tail.shapeKey); phaseTiming.Phase(DrawRowGraphics); if (built != nullptr) attachDrawRecipe(drawKey, recipeStages, std::move(built)); timing.Mark("draw_and_resource_release"); return drawn(); } -void Driver::commitDraw(std::shared_ptr localDevice, std::uint32_t queue, const Graphics::State& graphics, const Pm4::DrawParameters& drawParameters, std::span stages, std::span snapshots, const std::shared_ptr& recipe, const std::vector>& recipeStages, std::uint64_t drawKey, VulkanDevice::DrawTraceContext trace) { +void Driver::commitDraw(std::shared_ptr localDevice, std::uint32_t queue, const Graphics::State& graphics, const Pm4::DrawParameters& drawParameters, std::span stages, std::span snapshots, const std::shared_ptr& recipe, const std::vector>& recipeStages, std::uint64_t drawKey, VulkanDevice::DrawTraceContext trace, std::uint64_t shape) { const auto* tracePtr = trace.token != 0 ? &trace : nullptr; GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); std::lock_guard gpuLock(GuestMemory::GpuMutex()); @@ -1291,7 +1296,7 @@ void Driver::commitDraw(std::shared_ptr localDevice, std::uint32_t VulkanDevice::NoteRecipe(VulkanDevice::RecipeEvent::Restart, VulkanDevice::RecipeKind::Draw); } std::shared_ptr built; - localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built, tracePtr); + localDevice->Draw(graphics, drawParameters, stages, snapshots, recipeStages.empty() ? nullptr : &built, tracePtr, shape); if (built != nullptr) attachDrawRecipe(drawKey, recipeStages, std::move(built)); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp index 8d05f4693f..c42ca7d2e4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp @@ -65,6 +65,15 @@ std::size_t Driver::drawCacheEntries() { return entries; } +std::uint64_t Driver::DrawCacheBytes() { + static const std::uint64_t bytes = [] { + const char* text = std::getenv("APS5_DRAW_CACHE_BYTES"); + const auto parsed = text != nullptr ? std::strtoull(text, nullptr, 10) : 0ull; + return parsed != 0 ? static_cast(parsed) : std::uint64_t{256} << 20u; + }(); + return bytes; +} + void Driver::accountDrawVariant(const DispatchVariant& variant, bool added) { if (added) { ++drawCacheVariants; @@ -180,7 +189,8 @@ void Driver::insertDrawEntry(std::uint64_t key, std::vectororder); found->second = std::move(replacement); } - while (drawCache.size() > drawCacheEntries()) { + while (drawCache.size() > drawCacheEntries() || (drawCache.size() > 1 && drawCacheVariantBytes > DrawCacheBytes())) { + const bool overBytes = drawCache.size() <= drawCacheEntries(); const auto last = drawCache.find(drawOrder.back()); for (const auto& variants : last->second->stages) { for (const auto& variant : variants) accountDrawVariant(*variant, false); @@ -191,6 +201,10 @@ void Driver::insertDrawEntry(std::uint64_t key, std::vector(counters.hits) / static_cast(counters.lookups) : 0.0, count(misses), count(counters.absent), count(counters.dataHits), count(counters.dataStagesReused), count(counters.dataStagesCompiled), count(counters.dataVerified), count(counters.absentNewRegisters), count(counters.absentUserWords), count(counters.absentEvicted), count(counters.absentNeverInserted), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.differingSameRuns), counters.differingSameRuns != 0 ? static_cast(counters.differingWords) / static_cast(counters.differingSameRuns) : 0.0, count(counters.differingRunsChanged), drawCache.size(), count(counters.evictions)); + std::snprintf(line, sizeof(line), "[draw-cache] 10 s: %llu lookups, %llu hits (%.1f%%), %llu stage misses, %llu without an entry, %llu data-only hits (stages: %llu reused, %llu compiled; %llu verified); no entry: new registers %llu, user words/pointers only %llu, evicted %llu, seen but never inserted %llu; stage misses: front differing %llu, fragment differing %llu, other differing %llu, layout %llu, gate %llu, stage count %llu; differing captures: same runs %llu (%.1f words changed each), other runs %llu; %zu entries, %llu evicted (%llu over the byte budget)\n", count(counters.lookups), count(counters.hits), counters.lookups != 0 ? 100.0 * static_cast(counters.hits) / static_cast(counters.lookups) : 0.0, count(misses), count(counters.absent), count(counters.dataHits), count(counters.dataStagesReused), count(counters.dataStagesCompiled), count(counters.dataVerified), count(counters.absentNewRegisters), count(counters.absentUserWords), count(counters.absentEvicted), count(counters.absentNeverInserted), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.differingSameRuns), counters.differingSameRuns != 0 ? static_cast(counters.differingWords) / static_cast(counters.differingSameRuns) : 0.0, count(counters.differingRunsChanged), drawCache.size(), count(counters.evictions), count(counters.byteEvictions)); text += line; std::snprintf(line, sizeof(line), "[draw-cache] register-key miss component changes (10 s): stable registers %llu, user data %llu, auxiliary registers %llu, device %llu, first shader set %llu, unchanged components %llu\n", count(counters.registerKeyMissChanges[0]), count(counters.registerKeyMissChanges[1]), count(counters.registerKeyMissChanges[2]), count(counters.registerKeyMissChanges[3]), count(counters.registerKeyMissChanges[4]), count(counters.registerKeyMissChanges[5])); text += line; @@ -47,6 +47,11 @@ DrawEntryCounters Driver::DrawCacheCounters() { return drawEntryCounters; } +Driver::DispatchCacheFootprint Driver::DrawCacheSize() { + std::lock_guard cacheLock(drawCacheMutex); + return {drawCache.size(), drawCacheVariantBytes}; +} + OverlayDriverCounters Driver::OverlayCounters() { OverlayDriverCounters counters{flipsCounted.load(std::memory_order_relaxed), std::nullopt}; std::unique_lock cacheLock(drawCacheMutex, std::try_to_lock); @@ -70,7 +75,7 @@ std::string Driver::profileDrawCacheLines(const DrawEntryCounters& counters) { std::snprintf(item, sizeof(item), "%s%llu", rank == 0 ? "" : " / ", count(counters.variantHitsByRank[rank])); ranks += item; } - append("[draw-cache] %llu lookups (10 s): %llu no entry, %llu validated in %.1f us each (key, lookup and every stage): %llu hits (every stage equal; stage hits by variant rank 1..k %s), misses by reason: front stage differing %llu, fragment differing %llu, other stage differing %llu, layout (push offset) %llu, gate %llu, stage count %llu; %llu stage validations (%llu equal, %.2f variants compared each); %llu inserts (%llu shape updates, %llu variants inserted, %llu evicted beyond k, %llu already present, %llu unstable), %zu entries (%llu variants, ~%.1f MiB), %llu evictions in total, %llu LRU moves; verify: %llu hits captured again, %llu stages differed\n", count(counters.lookups), count(counters.absent), count(validated), validated != 0 ? counters.validateUs / static_cast(validated) : 0.0, count(counters.hits), ranks.c_str(), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.stageValidations), count(counters.stageEqual), counters.stageValidations != 0 ? static_cast(counters.variantsCompared) / static_cast(counters.stageValidations) : 0.0, count(counters.inserts), count(counters.shapeUpdates), count(counters.variantsInserted), count(counters.variantsEvicted), count(counters.present), count(counters.unstable), drawCache.size(), count(drawCacheVariants), static_cast(drawCacheVariantBytes) / (1024.0 * 1024.0), count(drawCacheEvictions), count(counters.touches), count(counters.verifyHits), count(counters.verifyMismatches)); + append("[draw-cache] %llu lookups (10 s): %llu no entry, %llu validated in %.1f us each (key, lookup and every stage): %llu hits (every stage equal; stage hits by variant rank 1..k %s), misses by reason: front stage differing %llu, fragment differing %llu, other stage differing %llu, layout (push offset) %llu, gate %llu, stage count %llu; %llu stage validations (%llu equal, %.2f variants compared each); %llu inserts (%llu shape updates, %llu variants inserted, %llu evicted beyond k, %llu already present, %llu unstable), %zu entries (%llu variants, ~%.1f MiB of a %.1f MiB budget), %llu evictions in total (%llu over the byte budget), %llu LRU moves; verify: %llu hits captured again, %llu stages differed\n", count(counters.lookups), count(counters.absent), count(validated), validated != 0 ? counters.validateUs / static_cast(validated) : 0.0, count(counters.hits), ranks.c_str(), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.stageValidations), count(counters.stageEqual), counters.stageValidations != 0 ? static_cast(counters.variantsCompared) / static_cast(counters.stageValidations) : 0.0, count(counters.inserts), count(counters.shapeUpdates), count(counters.variantsInserted), count(counters.variantsEvicted), count(counters.present), count(counters.unstable), drawCache.size(), count(drawCacheVariants), static_cast(drawCacheVariantBytes) / (1024.0 * 1024.0), static_cast(DrawCacheBytes()) / (1024.0 * 1024.0), count(drawCacheEvictions), count(drawCacheByteEvictions), count(counters.touches), count(counters.verifyHits), count(counters.verifyMismatches)); append("[draw-cache] register key (10 s): %llu lookups, %llu hits, key build %.1f us each; decode skipped %llu, partial (state/pixel/programs from the entry) %llu; facade log != table %llu; verify: %llu decodes compared, %llu differed\n", count(counters.registerKeyLookups), count(counters.registerKeyHits), counters.registerKeyLookups != 0 ? counters.keyUs / static_cast(counters.registerKeyLookups) : 0.0, count(counters.decodeSkipped), count(counters.decodePartial), count(counters.facadeMismatches), count(counters.verifyDecodes), count(counters.verifyDecodeMismatches)); append("[draw-cache] stage data hits (10 s): %llu stages (%llu words refreshed), verified %llu; stage inserts with data positions %llu (%.1f positions each)\n", count(counters.stageDataHits), count(counters.stageDataWords), count(counters.stageDataVerified), count(counters.stageDataInserts), counters.stageDataInserts != 0 ? static_cast(counters.stageDataPositions) / static_cast(counters.stageDataInserts) : 0.0); return text; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp index 30b3692d90..6697f87de7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -73,6 +73,9 @@ std::shared_ptr Driver::materializeDraw {0, 0, pushOffset, Graphics::StagePushLimit(graphics.stages, pushOffset, graphics.depthRemap.mode != 0u) - pushOffset % Graphics::PipelinePushSlotBytes}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, Graphics::StageMeshConfiguration(graphics.stages, program.binary.stage), graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}, program.binary.stage == Stage::Mesh ? meshBarycentrics : std::nullopt} }; + if (graphics.depthRemap.mode != 0u && pushOffset < Graphics::PipelinePushSlotBytes) { + request.layout.pushConstantSizeBytes = std::min(request.layout.pushConstantSizeBytes, ShaderRecompiler::DepthRemapPushOffsetBytes - pushOffset); + } const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0; auto invocation = [&] { try { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp index e32a645641..2f3a32e609 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -74,6 +74,8 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr(pixel.depthExportMin)); + mix(std::bit_cast(pixel.depthExportMax)); for (const auto value : pixel.targetOutputMode) mix(value); for (const auto value : pixel.targetExportMapping) mix(value); for (const auto value : pixel.targetExportPacking) mix(static_cast(value)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index 876709f645..a30e3b7542 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -255,7 +255,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { if (std::memcmp(&s.viewport, &t.viewport, sizeof(VkViewport)) != 0 || std::memcmp(&s.scissor, &t.scissor, sizeof(VkRect2D)) != 0) return false; const auto& p = a.pixel; const auto& q = b.pixel; - if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.frontFaceAllBits != q.frontFaceAllBits || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.stencilRefExportEnable != q.stencilRefExportEnable || p.stencilOperationValueExport != q.stencilOperationValueExport || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false; + if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.frontFaceAllBits != q.frontFaceAllBits || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.depthExportMin != q.depthExportMin || p.depthExportMax != q.depthExportMax || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.stencilRefExportEnable != q.stencilRefExportEnable || p.stencilOperationValueExport != q.stencilOperationValueExport || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false; if (a.roles != b.roles || a.programs.size() != b.programs.size()) return false; for (std::size_t i = 0; i < a.programs.size(); ++i) { const auto& x = a.programs[i]; 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 0acd2860fa..db1978a17e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -11,6 +11,7 @@ #include "prx/libSceAgcDriver/Execution/include/PerformanceOverlay.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Eq/include/Event.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include @@ -407,6 +408,7 @@ void Driver::execute(const Submission& submission) { } if (!flipInterruptDeferred) deliverFlipEopInterrupt(submission.queue); ++flipsCounted; + Graphics::NoteHostImportFrame(); if (batchesAtFlip != 0) flipSerial = batchesAtFlip; flipBatchesUnsignaled += unsignaledAtFlip; if (Graphics::AbConfigured()) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp index b6a5b5689c..34ca75b543 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp @@ -243,15 +243,29 @@ void Driver::run(std::uint32_t id) noexcept { pending.pop_front(); if (APS5_ENABLE_TIMING_LOG) submission.dequeuedAt = std::chrono::steady_clock::now(); if (id == 0) queue0Executing = submission.suspend ? 0 : submission.received; + struct OrderHold { + std::atomic& holders; + explicit OrderHold(std::atomic& holders) : holders(holders) { holders.fetch_add(1, std::memory_order_acq_rel); } + ~OrderHold() { holders.fetch_sub(1, std::memory_order_acq_rel); } + OrderHold(const OrderHold&) = delete; + OrderHold& operator=(const OrderHold&) = delete; + }; + if (id == 0 && !submission.suspend) { + { + OrderHold hold{orderHolders}; + const auto released = [&] { return failure || stopping || !waitFreeBefore(submission.received); }; + StallWatch stall(id); + while (!released() && !poll(released)) stall.Check("order behind other queues for event", submission.received); + } + rethrowFailure(); + } + if (id != 0) { + worker.executingWaitFree = submission.waitFree ? submission.received : 0; + worker.executingWaits = !submission.waitFree && !submission.suspend; + } if (submission.waitFree) { { - struct OrderHold { - std::atomic& holders; - explicit OrderHold(std::atomic& holders) : holders(holders) { holders.fetch_add(1, std::memory_order_acq_rel); } - ~OrderHold() { holders.fetch_sub(1, std::memory_order_acq_rel); } - OrderHold(const OrderHold&) = delete; - OrderHold& operator=(const OrderHold&) = delete; - } hold{orderHolders}; + OrderHold hold{orderHolders}; const auto released = [&] { return failure || stopping || orderReleased(id, submission.received); }; StallWatch stall(id); while (!released() && !poll(released)) stall.Check("order behind queue 0 for event", submission.received); @@ -279,6 +293,10 @@ void Driver::run(std::uint32_t id) noexcept { forgetUnfinishedWrites(workers.at(id), submission); } if (id == 0) queue0Executing = 0; + if (id != 0) { + workers.at(id).executingWaitFree = 0; + workers.at(id).executingWaits = false; + } notify = idleWaiters != 0 || orderHolders.load(std::memory_order_acquire) != 0; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index eeaea3e126..7b452b32f3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -7,6 +7,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/SubmissionCapture.hpp" #include "prx/libSceAgcDriver/Execution/include/ThreadPriority.hpp" #include "prx/libSceAgcDriver/Eq/include/Event.hpp" @@ -440,6 +441,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { ++flipsCounted; submission.flips.begin()->second->GpuReady(frame); lock.unlock(); + Graphics::NoteHostImportFrame(); AgcDriverDeliverEopInterrupt(0); return; } @@ -537,6 +539,18 @@ bool Driver::queue0Before(std::uint64_t received) const { return false; } +bool Driver::waitFreeBefore(std::uint64_t received) const { + for (const auto& [queue, worker] : workers) { + if (queue == 0 || worker.executingWaits) continue; + if (worker.executingWaitFree != 0 && worker.executingWaitFree < received) return true; + for (const auto& pending : worker.pending) { + if (!pending.waitFree || pending.received >= received) break; + return true; + } + } + return false; +} + bool Driver::orderReleased(std::uint32_t queue, std::uint64_t received) const { if (!queue0Before(received)) return true; const auto awaited = queue0Awaited.load(std::memory_order_acquire); 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 3a69eaa097..069a5e418f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1802,7 +1802,10 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod const auto wave = Graphics::StageWaveSize(decoded.state.stages, program.binary.stage); pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots); const auto offset = pushOffset; - const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes; + auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes; + if (decoded.state.depthRemap.mode != 0u && offset < Graphics::PipelinePushSlotBytes) { + room = std::min(room, ShaderRecompiler::DepthRemapPushOffsetBytes - offset); + } if (deferUnprepared && DeferFailedRegistration(*program.snapshot)) { prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); continue; @@ -1818,6 +1821,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } if (vertex) { vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl, program.tailContinuation.get()) | decoded.state.layerExports; + vertex->depthRemapMode = decoded.state.depthRemap.mode; Graphics::ApplyPointSize(decoded.state, *vertex); } 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, Graphics::StageMeshConfiguration(decoded.state.stages, program.binary.stage), decoded.state.stages.tessellation, {}}}; 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 f1b7df1757..99edca9a11 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -135,9 +135,14 @@ void Driver::waitMemory(std::span packet, std::uint32_t que if (bump) bumpEpoch(&EpochBumps::waits); } } epochPoint; + const auto storedByOwnLabel = [&] { + std::uint32_t writer = queue; + return !requireMemory && storedSince(packet, awaited, awaitedBytes, received, &writer) && writer == queue; + }; GuestMemory::CheckRange(reinterpret_cast(awaited), awaitedBytes, awaitedBytes); if (Pm4::WaitSatisfiedUnchecked(packet)) { ++outcomes.atEntry; + if (storedByOwnLabel()) epochPoint.bump = false; return; } if (heldAtSubmit) return; @@ -406,6 +411,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que } } ++outcomes.polled; + if (storedByOwnLabel()) epochPoint.bump = false; } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 3f08fa469e..7735842922 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -8,6 +8,7 @@ #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libc/include/HostThreadSlot.hpp" #include #include @@ -2677,13 +2678,74 @@ void CopyGuestBytes(void* destination, const void* source, std::size_t bytes) { namespace { +#ifdef _WIN32 +bool readPages(std::byte* destination, std::uint64_t source, std::size_t bytes) { + SIZE_T copied = 0; + if (ReadProcessMemory(GetCurrentProcess(), reinterpret_cast(source), destination, bytes, &copied) != 0) { + require(copied == bytes, "short committed guest memory read"); + return true; + } + const auto error = GetLastError(); + require(error == ERROR_PARTIAL_COPY || error == ERROR_NOACCESS || error == ERROR_INVALID_ADDRESS, "cannot read committed guest memory"); + return false; +} + +template +void visitReadablePages(std::byte* destination, std::uint64_t source, std::size_t bytes, Visit&& visit) { + if (readPages(destination, source, bytes)) { + visit(std::size_t{0}, bytes, true); + return; + } + ForgetPages(static_cast(source), bytes); + for (std::size_t offset = 0; offset < bytes;) { + const auto cursor = source + offset; + const auto chunk = std::min(bytes - offset, PageBytes - static_cast(cursor % PageBytes)); + MEMORY_BASIC_INFORMATION memory{}; + require(VirtualQuery(reinterpret_cast(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query guest memory after a failed committed read"); + const bool readable = memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0 && readableProtection(memory.Protect & 0xffu); + visit(offset, chunk, readable && readPages(destination + offset, cursor, chunk)); + offset += chunk; + } +} +#endif + +void copyCommittedBytes(std::byte* destination, std::uint64_t source, std::size_t bytes) { +#ifdef _WIN32 + visitReadablePages(destination, source, bytes, [&](std::size_t offset, std::size_t count, bool read) { + if (!read) std::memset(destination + offset, 0, count); + }); +#else + std::memcpy(destination, reinterpret_cast(source), bytes); +#endif +} + +bool equalsCommittedBytes(std::uint64_t address, const std::byte* expected, std::size_t bytes) { +#ifdef _WIN32 + constexpr std::size_t ChunkBytes = 64 * PageBytes; + struct CompareChunkStorage {}; + auto& current = HostThreadLocal, CompareChunkStorage>(); + if (current.size() < ChunkBytes) current.resize(ChunkBytes); + for (std::size_t done = 0; done < bytes; done += ChunkBytes) { + const auto count = std::min(ChunkBytes, bytes - done); + bool equal = true; + visitReadablePages(current.data(), address + done, count, [&](std::size_t offset, std::size_t length, bool read) { + if (read && equal && std::memcmp(current.data() + offset, expected + done + offset, length) != 0) equal = false; + }); + if (!equal) return false; + } + return true; +#else + return std::memcmp(reinterpret_cast(address), expected, bytes) == 0; +#endif +} + void copyCommitted(std::uint64_t address, std::span destination) { if (Accessible(reinterpret_cast(address), destination.size())) { - std::memcpy(destination.data(), reinterpret_cast(address), destination.size()); + copyCommittedBytes(destination.data(), address, destination.size()); return; } std::memset(destination.data(), 0, destination.size()); - for (const auto& [begin, end] : CommittedRanges(address, destination.size())) std::memcpy(destination.data() + (begin - address), reinterpret_cast(begin), static_cast(end - begin)); + for (const auto& [begin, end] : CommittedRanges(address, destination.size())) copyCommittedBytes(destination.data() + (begin - address), begin, static_cast(end - begin)); } } @@ -2714,9 +2776,9 @@ bool EqualsCommitted(std::uint64_t address, std::span bytes) { bool EqualsCommittedUnsynced(std::uint64_t address, std::span bytes) { if (bytes.empty()) return true; const TimedAccess timed(CounterCompare, bytes.size()); - if (Accessible(reinterpret_cast(address), bytes.size())) return std::memcmp(reinterpret_cast(address), bytes.data(), bytes.size()) == 0; + if (Accessible(reinterpret_cast(address), bytes.size())) return equalsCommittedBytes(address, bytes.data(), bytes.size()); for (const auto& [begin, end] : CommittedRanges(address, bytes.size())) { - if (std::memcmp(reinterpret_cast(begin), bytes.data() + (begin - address), static_cast(end - begin)) != 0) return false; + if (!equalsCommittedBytes(begin, bytes.data() + (begin - address), static_cast(end - begin))) return false; } return true; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/PerformanceOverlay.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/PerformanceOverlay.cpp index ad19a30724..96c60dbd56 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/PerformanceOverlay.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/PerformanceOverlay.cpp @@ -34,6 +34,9 @@ #include #else #include +#ifdef __APPLE__ +#include +#endif #endif namespace AgcDriver { @@ -616,6 +619,11 @@ std::optional ReadProcessPrivateBytes() { counters.cb = sizeof(counters); if (K32GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters)) == 0) return std::nullopt; return static_cast(counters.PrivateUsage); +#elif defined(__APPLE__) + task_vm_info_data_t info{}; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast(&info), &count) != KERN_SUCCESS || info.phys_footprint == 0) return std::nullopt; + return static_cast(info.phys_footprint); #else std::FILE* status = std::fopen("/proc/self/status", "r"); if (status == nullptr) return std::nullopt; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 6a78d16662..a4e136fdcf 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -1034,13 +1034,12 @@ void Execute(std::span packet, QueueState& queue) { case 0: queue.ClearContext(); break; case 1: case 3: require(!queue.savedContext.has_value(), "context state is already pushed"); - queue.savedContext = queue.context; + queue.PushContext(); if (packet[1] == 3) queue.ClearContext(); break; case 2: require(queue.savedContext.has_value(), "context state has not been pushed"); - queue.context = std::move(*queue.savedContext); - queue.savedContext.reset(); + queue.PopContext(); break; } APS5_LOG_OUT_DEBUG("CONTEXT_STATE done operation=%u targetMaskAfter=0x%x shaderMaskAfter=0x%x", packet[1], queue.context.contains(0x8e) ? queue.context.at(0x8e) : 0u, queue.context.contains(0x8f) ? queue.context.at(0x8f) : 0u); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp index 660a9c007c..f9b21ce96a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp @@ -71,6 +71,7 @@ ShaderDeviceProfile::ShaderDeviceProfile(const ShaderRecompiler::SpirvTarget& ta checkFeature(spv::CapabilityDrawParameters, drawParameters != nullptr && drawParameters->shaderDrawParameters == VK_TRUE, "shaderDrawParameters"); checkFeature(spv::CapabilityShaderClockKHR, clock != nullptr && clock->shaderSubgroupClock == VK_TRUE && clock->shaderDeviceClock == VK_TRUE, "shader clock"); Require(!target.fragmentShaderBarycentricEnabled || barycentricEnabled, "shader device profile exposes disabled barycentrics"); + Require(!target.fragmentBarycentricBuiltins || barycentricEnabled, "shader device profile exposes disabled barycentric built-ins"); Require(!target.mesh || meshEnabled, "shader device profile exposes disabled mesh shaders"); Require(!target.nonConstantImageOffsets || (maintenance8 != nullptr && maintenance8->maintenance8 == VK_TRUE), "shader device profile exposes disabled maintenance8"); Require(!target.tessellation || core.tessellationShader == VK_TRUE, "shader device profile exposes disabled tessellation"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp index 4fc0fb89bc..c91decd2d2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp @@ -179,6 +179,10 @@ ShaderMemory::~ShaderMemory() { } } +std::size_t ShaderMemory::Bytes() const { + return sizeof(ShaderMemory) + initial.capacity() * sizeof(initial[0]) + pages.capacity() * sizeof(pages[0]) + pages.size() * sizeof(Page) + supplied.capacity() * sizeof(Supplied) + spilled.capacity() * sizeof(spilled[0]); +} + void ShaderMemory::WordMask::SetRun(std::size_t first, std::size_t count) { while (count != 0) { const auto shift = first % 64u; @@ -235,6 +239,7 @@ ShaderMemory::Page& ShaderMemory::addPage(std::size_t position, std::uint64_t ba pool.pop_back(); } owned->valid.Reset(); + owned->raw.Reset(); owned->read.Reset(); owned->recent.Reset(); owned->wordwise = false; @@ -299,12 +304,38 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val GuestMemory::CopyGuestBytes(&page.words[first], reinterpret_cast((address & ~static_cast(PageBytes - 1)) + first * sizeof(std::uint32_t)), BlockWords * sizeof(std::uint32_t)); page.valid.SetBlock(index); } + const auto spanNeedsSync = [](PendingWrite spanPolicy) { + return spanPolicy == PendingWrite::Sync || spanPolicy == PendingWrite::VerifyRaw || spanPolicy == PendingWrite::VerifyKnownValue; + }; + const auto spanInvalidatesCache = [&](PendingWrite spanPolicy) { + return spanNeedsSync(spanPolicy) || spanPolicy == PendingWrite::KnownValue; + }; + const auto checkSpanBytes = std::max(self.descriptorSpanBytes, sizeof(*value)); + if (page.wordwise && page.valid.Test(index) && + (self.forceSync != 0u ? self.pendingWrite(address, checkSpanBytes, {}) != PendingWrite::None + : (spanInvalidatesCache(self.pendingWrite(address, checkSpanBytes, {})) || + (checkSpanBytes > sizeof(*value) && spanInvalidatesCache(self.pendingWrite(address, sizeof(*value), {})))))) { + page.valid.Clear(index); + page.raw.Clear(index); + } if (!page.valid.Test(index)) { std::uint32_t word = 0; auto policy = PendingWrite::Sync; + bool forcedRaw = false; if (page.wordwise) { - policy = self.pendingWrite(address, sizeof(word), std::as_writable_bytes(std::span(&word, 1))); - if (policy != PendingWrite::None && policy != PendingWrite::Sync && !GuestMemory::Accessible(reinterpret_cast(address), sizeof(word))) policy = PendingWrite::Sync; + if (self.descriptorSpanBytes > sizeof(word) && + (self.forceSync != 0u ? self.pendingWrite(address, self.descriptorSpanBytes, {}) != PendingWrite::None + : spanNeedsSync(self.pendingWrite(address, self.descriptorSpanBytes, {})))) { + policy = PendingWrite::Sync; + forcedRaw = self.forceSync != 0u && !spanNeedsSync(self.pendingWrite(address, self.descriptorSpanBytes, {})); + } else { + policy = self.pendingWrite(address, sizeof(word), std::as_writable_bytes(std::span(&word, 1))); + if (self.forceSync != 0u && policy != PendingWrite::None) { + forcedRaw = !spanNeedsSync(policy); + policy = PendingWrite::Sync; + } + if (policy != PendingWrite::None && policy != PendingWrite::Sync && !GuestMemory::Accessible(reinterpret_cast(address), sizeof(word))) policy = PendingWrite::Sync; + } } // A read is evidence only when the hook waited for unfinished GPU work across it. const auto hookWaits = [&] { return self.hookWaits != nullptr ? self.hookWaits() : 0; }; @@ -313,38 +344,44 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val else ++CaptureTotals().observationsSkipped; }; const auto fetchStarted = CaptureProfiled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; + bool waited = false; if (policy == PendingWrite::KnownValue || policy == PendingWrite::VerifyKnownValue) { // The query stored the known dword in `word`. ++CaptureTotals().wordsKnown; if (policy == PendingWrite::VerifyKnownValue) { - std::uint32_t waited = 0; + std::uint32_t verifiedWord = 0; const auto waitsBefore = hookWaits(); - GuestMemory::Read(address, std::as_writable_bytes(std::span(&waited, 1)), alignof(std::uint32_t)); + GuestMemory::Read(address, std::as_writable_bytes(std::span(&verifiedWord, 1)), alignof(std::uint32_t)); ++CaptureTotals().wordsKnownVerified; - if (waited != word) ++CaptureTotals().wordsKnownMismatches; - if (self.observe != nullptr) report(waitsBefore, waited == word); - word = waited; + if (verifiedWord != word) ++CaptureTotals().wordsKnownMismatches; + if (self.observe != nullptr) report(waitsBefore, verifiedWord == word); + waited = page.wordwise && (self.hookWaits != nullptr ? self.hookWaits() != waitsBefore : true); + word = verifiedWord; } } else if (policy == PendingWrite::Raw || policy == PendingWrite::VerifyRaw) { GuestMemory::GuardRead(address, sizeof(word)); std::memcpy(&word, reinterpret_cast(address), sizeof(word)); ++CaptureTotals().wordsRaw; if (policy == PendingWrite::VerifyRaw) { - std::uint32_t waited = 0; + std::uint32_t verifiedWord = 0; const auto waitsBefore = hookWaits(); - GuestMemory::Read(address, std::as_writable_bytes(std::span(&waited, 1)), alignof(std::uint32_t)); + GuestMemory::Read(address, std::as_writable_bytes(std::span(&verifiedWord, 1)), alignof(std::uint32_t)); ++CaptureTotals().wordsVerified; - if (waited != word) ++CaptureTotals().wordMismatches; - if (self.observe != nullptr) report(waitsBefore, waited == word); - word = waited; + if (verifiedWord != word) ++CaptureTotals().wordMismatches; + if (self.observe != nullptr) report(waitsBefore, verifiedWord == word); + waited = page.wordwise && (self.hookWaits != nullptr ? self.hookWaits() != waitsBefore : true); + word = verifiedWord; } } else { // On a word-wise page the bytes before the hook's wait are kept for the observer. const bool observed = page.wordwise && policy == PendingWrite::Sync && self.observe != nullptr && GuestMemory::Accessible(reinterpret_cast(address), sizeof(word)); std::uint32_t before = 0; if (observed) std::memcpy(&before, reinterpret_cast(address), sizeof(before)); - const auto waitsBefore = observed ? hookWaits() : 0; + const auto waitsBefore = page.wordwise ? hookWaits() : 0; const auto started = page.wordwise ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; + if (page.wordwise && policy == PendingWrite::Sync && self.descriptorSpanBytes > sizeof(word)) { + GuestMemory::FlushGpuWrites(address, self.descriptorSpanBytes); + } GuestMemory::Read(address, std::as_writable_bytes(std::span(&word, 1)), alignof(std::uint32_t)); if (page.wordwise) { const auto nanoseconds = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count()); @@ -352,12 +389,16 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val ++CaptureTotals().wordWaits; CaptureTotals().wordWaitNanoseconds += nanoseconds; } + waited = policy != PendingWrite::None && (self.hookWaits != nullptr ? self.hookWaits() != waitsBefore : true); } if (observed) report(waitsBefore, before == word); } + if ((waited || (page.wordwise && policy == PendingWrite::Sync && !forcedRaw)) && self.invalidateRawPages()) ++self.rawInvalidations; if (CaptureProfiled()) CaptureTotals().wordReadNanoseconds += NanosecondsSince(fetchStarted); page.words[index] = word; page.valid.Set(index); + if (page.wordwise && (policy == PendingWrite::Raw || policy == PendingWrite::KnownValue || (forcedRaw && !waited))) page.raw.Set(index); + else page.raw.Clear(index); } page.read.Set(index); page.recent.Set(index); @@ -365,6 +406,17 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val return true; } +bool ShaderMemory::invalidateRawPages() { + bool invalidated = false; + for (auto& [pageBase, existing] : pages) { + if (!existing->wordwise || existing->raw.None()) continue; + existing->valid.ClearMask(existing->raw); + existing->raw.Reset(); + invalidated = true; + } + return invalidated; +} + bool ShaderMemory::overlapsInitial(std::uint64_t begin, std::uint64_t end) const { if (initial.empty()) return false; const auto after = std::lower_bound(initial.begin(), initial.end(), begin, [](const auto& entry, std::uint64_t value) { return entry.first < value; }); @@ -375,43 +427,96 @@ bool ShaderMemory::overlapsInitial(std::uint64_t begin, std::uint64_t end) const bool ShaderMemory::readWords(void* context, std::uint64_t address, std::uint32_t* values, std::uint64_t count) { auto& self = *static_cast(context); if (!self.supplied.empty() && count != 0) self.spillTouching(address, count > (std::numeric_limits::max() - address) / sizeof(std::uint32_t) ? std::numeric_limits::max() : address + count * sizeof(std::uint32_t)); - while (count != 0) { - const auto base = address & ~static_cast(PageBytes - 1); - const auto index = static_cast((address % PageBytes) / sizeof(std::uint32_t)); - const auto words = static_cast(std::min(count, PageWords - index)); - const auto bytes = static_cast(words) * sizeof(std::uint32_t); - Page* page = nullptr; - if (address % sizeof(std::uint32_t) == 0 && address <= std::numeric_limits::max() - bytes && !self.overlapsInitial(address, address + bytes)) page = &self.page(base); - if (page != nullptr && page->lazy) { - const auto end = (index + words + BlockWords - 1u) / BlockWords * BlockWords; - auto first = index / BlockWords * BlockWords; - while (first < end) { - if (page->valid.TestBlock(first)) { - first += BlockWords; - continue; + const auto startAddress = address; + auto* const startValues = values; + const auto startCount = count; + const auto savedSpanBytes = self.descriptorSpanBytes; + const auto savedForceSync = self.forceSync; + struct ScopeReset { + ShaderMemory& self; + std::size_t spanBytes; + std::uint32_t forceSync; + ~ScopeReset() { + self.descriptorSpanBytes = spanBytes; + self.forceSync = forceSync; + } + } scopeReset{self, savedSpanBytes, savedForceSync}; + const auto strideWords = savedSpanBytes / sizeof(std::uint32_t); + for (std::uint32_t attempt = 0; attempt < 3u; ++attempt) { + if (attempt == 2u) ++self.forceSync; + address = startAddress; + values = startValues; + count = startCount; + const auto invalidationsBefore = self.rawInvalidations; + while (count != 0) { + const auto base = address & ~static_cast(PageBytes - 1); + const auto index = static_cast((address % PageBytes) / sizeof(std::uint32_t)); + const auto words = static_cast(std::min(count, PageWords - index)); + const auto bytes = static_cast(words) * sizeof(std::uint32_t); + Page* page = nullptr; + if (address % sizeof(std::uint32_t) == 0 && address <= std::numeric_limits::max() - bytes && !self.overlapsInitial(address, address + bytes)) page = &self.page(base); + if (page != nullptr && page->lazy) { + const auto end = (index + words + BlockWords - 1u) / BlockWords * BlockWords; + auto first = index / BlockWords * BlockWords; + while (first < end) { + if (page->valid.TestBlock(first)) { + first += BlockWords; + continue; + } + auto last = first + BlockWords; + while (last < end && !page->valid.TestBlock(last)) last += BlockWords; + GuestMemory::CopyGuestBytes(&page->words[first], reinterpret_cast(base + first * sizeof(std::uint32_t)), (last - first) * sizeof(std::uint32_t)); + page->valid.SetRun(first, last - first); + first = last; } - auto last = first + BlockWords; - while (last < end && !page->valid.TestBlock(last)) last += BlockWords; - GuestMemory::CopyGuestBytes(&page->words[first], reinterpret_cast(base + first * sizeof(std::uint32_t)), (last - first) * sizeof(std::uint32_t)); - page->valid.SetRun(first, last - first); - first = last; } - } - if (page != nullptr && (page->lazy || page->valid.All())) { - std::memcpy(values, page->words.data() + index, bytes); - page->read.SetRun(index, words); - page->recent.SetRun(index, words); - CaptureTotals().reads += words; - } else { - for (std::size_t word = 0; word < words; ++word) { - if (!read(context, address + word * sizeof(std::uint32_t), values + word)) return false; + if (page != nullptr && (page->lazy || (!page->wordwise && page->valid.All()))) { + std::memcpy(values, page->words.data() + index, bytes); + page->read.SetRun(index, words); + page->recent.SetRun(index, words); + CaptureTotals().reads += words; + } else { + const auto offsetWords = static_cast(values - startValues); + for (std::size_t word = 0; word < words; ++word) { + if (strideWords > 1u) { + const auto inRecord = (offsetWords + word) % strideWords; + const auto remainingWords = std::min(strideWords - inRecord, startCount - (offsetWords + word)); + self.descriptorSpanBytes = static_cast(remainingWords * sizeof(std::uint32_t)); + } + const bool ok = read(context, address + word * sizeof(std::uint32_t), values + word); + self.descriptorSpanBytes = savedSpanBytes; + if (!ok) { + return false; + } + if (self.rawInvalidations != invalidationsBefore) break; + } + if (self.rawInvalidations != invalidationsBefore) break; } + address += bytes; + values += words; + count -= words; + } + if (self.rawInvalidations == invalidationsBefore) { + return true; } - address += bytes; - values += words; - count -= words; } - return true; + return false; +} + +std::uint64_t ShaderMemory::descriptorScope(void* context, std::size_t bytes) { + auto& self = *static_cast(context); + if (bytes == ShaderRecompiler::SrtDescriptorForceSync) { + ++self.forceSync; + self.descriptorSpanBytes = 0; + return self.rawInvalidations; + } + if (bytes == ShaderRecompiler::SrtDescriptorEndForceSync) { + if (self.forceSync != 0u) --self.forceSync; + self.descriptorSpanBytes = 0; + return self.rawInvalidations; + } + self.descriptorSpanBytes = bytes; + return self.rawInvalidations; } bool ShaderMemory::accessible(void* context, std::uint64_t address, std::uint64_t bytes) { @@ -644,6 +749,7 @@ std::shared_ptr ShaderMemory::capture(c runtime.stampWrites = &stampWrites; runtime.unchangedSince = &unchangedSince; runtime.supplyWords = &supplyWords; + runtime.descriptorScope = &descriptorScope; auto capture = invocation != nullptr ? invocation->Capture(runtime) : handle != nullptr ? ShaderRecompiler::CaptureResources(request, runtime, *handle) : ShaderRecompiler::CaptureResources(request, runtime); if (profile) { totals.captureNanoseconds += NanosecondsSince(started); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp index 9caddde7bd..606d6e0554 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/SubmissionCapture.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp" #include #include #include @@ -174,7 +175,7 @@ std::string_view SubmissionCapture::KindName(SubmissionKind kind) { std::string_view SubmissionCapture::QueueType(std::uint32_t queue) { if (queue == 0) return "graphics"; - if (queue >= 0x20 && queue < 0x58) return "compute"; + if (queue >= 0x20 && queue < DriverDetail::SubmissionQueueEnd) return "compute"; char what[64]; std::snprintf(what, sizeof(what), "queue 0x%x is neither the graphics queue nor a compute queue", queue); throw std::runtime_error(std::string("submission capture: ") + what); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 89305ccae5..74bc3828e0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -383,6 +383,7 @@ struct VulkanDevice::State { bool fragmentShaderBarycentric = false; bool shaderTerminateInvocation = false; bool fusedExactArithmetic = false; + bool fragmentBarycentricBuiltins = false; bool geometryShader = false; bool largePoints = false; bool sampleRateShading = false; @@ -404,6 +405,7 @@ struct VulkanDevice::State { bool exactRobustLoads = false; bool subgroupControlBarriers = true; bool workgroupScopeFences = true; + bool ldsLockFences = false; // VK_EXT_descriptor_indexing with non-uniform image array indexing (bindless image tables in // graphics stages, and compute workgroups wider than a wave). bool descriptorIndexing = false; @@ -1109,6 +1111,7 @@ ComputePipelineReplayResult VulkanDevice::ReplayComputePipeline(const ComputePip phase = std::chrono::steady_clock::now(); CreateComputePipeline(state->device, state->DeviceFunction("vkCreateComputePipelines"), replay.cache, objects.module, objects.layout, capture.flags, capture.stageFlags, specialization.Info(), capture.requiredSubgroupSize, &objects.pipeline); result.pipelineMilliseconds = elapsed(phase); + if ((capture.flags & VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR) != 0) Graphics::LogPipelineStatistics_nid_no_patch(graphicsContext(), objects.pipeline); phase = std::chrono::steady_clock::now(); const auto getCache = state->DeviceFunction("vkGetPipelineCacheData"); std::size_t cacheBytes = 0; @@ -1304,6 +1307,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : state->tessellationPipelineCache = TessellationPipelineCacheUsable(driver.driverID); state->subgroupControlBarriers = SubgroupControlBarrierUsable(driver.driverID); state->workgroupScopeFences = WorkgroupScopeFenceUsable(driver.driverID); + state->ldsLockFences = LdsLockFencesRequired(driver.driverID); } if (driverId == VK_DRIVER_ID_NVIDIA_PROPRIETARY && hasExtension(VK_NV_SHADER_SM_BUILTINS_EXTENSION_NAME)) { VkPhysicalDeviceShaderSMBuiltinsPropertiesNV smBuiltins{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SM_BUILTINS_PROPERTIES_NV}; @@ -1340,6 +1344,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &barycentricFeatures}; state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); state->fragmentShaderBarycentric = barycentricFeatures.fragmentShaderBarycentric == VK_TRUE; + state->fragmentBarycentricBuiltins = state->fragmentShaderBarycentric; if (state->fragmentShaderBarycentric && hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) { VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES}; VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver}; @@ -1394,7 +1399,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : deviceExtensions.push_back(VK_EXT_HDR_METADATA_EXTENSION_NAME); state->hdrMetadata = true; } - if (state->fragmentShaderBarycentric) { + if (state->fragmentBarycentricBuiltins) { deviceExtensions.push_back(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME); state->capabilities.push_back(spv::CapabilityFragmentBarycentricKHR); state->spirvExtensions.push_back("SPV_KHR_fragment_shader_barycentric"); @@ -1811,8 +1816,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : // Recompiled storage-image access declares no format (the guest descriptor decides it). enabled.shaderStorageImageMultisample = available.shaderStorageImageMultisample; state->storageImageMultisample = enabled.shaderStorageImageMultisample == VK_TRUE; - if (enabled.shaderStorageImageMultisample) state->capabilities.push_back(spv::CapabilityStorageImageMultisample); - state->capabilities.push_back(spv::CapabilityImageMSArray); + if (enabled.shaderStorageImageMultisample) { + state->capabilities.push_back(spv::CapabilityStorageImageMultisample); + state->capabilities.push_back(spv::CapabilityImageMSArray); + } enabled.shaderStorageImageWriteWithoutFormat = available.shaderStorageImageWriteWithoutFormat; state->storageImageWriteWithoutFormat = enabled.shaderStorageImageWriteWithoutFormat == VK_TRUE; enabled.shaderStorageImageReadWithoutFormat = available.shaderStorageImageReadWithoutFormat; @@ -1931,7 +1938,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : deviceInfo.pNext = &coverageReductionFeatures; } byteFeatures.pNext = const_cast(deviceInfo.pNext); - if (state->fragmentShaderBarycentric) { + if (state->fragmentBarycentricBuiltins) { barycentricFeatures.pNext = byteFeatures.pNext; byteFeatures.pNext = &barycentricFeatures; } @@ -4041,6 +4048,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { const auto& limits = state->properties.limits; ShaderRecompiler::SpirvTarget target{VK_API_VERSION_1_1, state->meshShader ? 0x00010400u : 0x00010300u, state->subgroup.subgroupSize, ShaderRecompiler::BdaAbi::Version, state->capabilities, state->spirvExtensions, false, {limits.maxComputeWorkGroupSize[0], limits.maxComputeWorkGroupSize[1], limits.maxComputeWorkGroupSize[2]}, limits.maxComputeWorkGroupInvocations, limits.maxComputeSharedMemorySize, {}, {}}; target.fragmentShaderBarycentricEnabled = state->fragmentShaderBarycentric; + target.fragmentBarycentricBuiltins = state->fragmentBarycentricBuiltins; target.nonConstantImageOffsets = state->maintenance8; target.narrowSubgroupClock = state->narrowSubgroupClock; target.float64DenormPreserve = state->float64DenormPreserve; @@ -4055,6 +4063,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { target.largePixelFunctionLdsBuffer = state->driverId == VK_DRIVER_ID_NVIDIA_PROPRIETARY; target.pixelFunctionStorageDwords = state->pixelFunctionStorageDwords; target.deviceScopeLdsMemoryBarriers = !state->workgroupScopeFences; + target.ldsLockFences = state->ldsLockFences; target.srgbDecodeFormats = state->srgbDecodeFormats & ~state->srgbCopyFormats; target.vertexSubgroups = (state->subgroup.supportedStages & VK_SHADER_STAGE_VERTEX_BIT) != 0; if (state->meshShader) { @@ -4127,7 +4136,7 @@ Graphics::Context VulkanDevice::buildContext() const { state->depthRangeUnrestricted, true, state->subgroup, - state->fragmentShaderBarycentric, + state->fragmentBarycentricBuiltins, state->samplerAnisotropy, state->textureCompressionBC, state->detiler.get(), @@ -4276,7 +4285,7 @@ bool VulkanDevice::PrebuildGraphicsLibraries(const Graphics::ShaderStages& stage return Graphics::Pipeline::Prebuild(context, state, Graphics::VertexInputLayout{}, layouts.View(), group); } -void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipe, const DrawTraceContext* trace) { +void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipe, const DrawTraceContext* trace, std::uint64_t shape) { startJournalReplay(); for (const auto& shader : shaders) { require(shader.program != nullptr, "missing compiled shader"); @@ -4287,7 +4296,7 @@ void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParamete static const bool traceDraws = std::getenv("APS5_TRACE_DRAWS") != nullptr; if (traceDraws) APS5_LOG_OUT("VulkanDevice::Draw indices=%u instances=%u indexSize=%u address=0x%llx shaders=%zu colorTarget=%u", draw.indexCount, draw.instanceCount, draw.indexSize, static_cast(draw.indexAddress), shaders.size(), static_cast(graphics.hasColorTarget)); const auto context = graphicsContext(); - Graphics::Draw(context, graphics, draw, shaders, snapshots, recipe, trace); + Graphics::Draw(context, graphics, draw, shaders, snapshots, recipe, trace, shape); if (traceDraws) APS5_LOG_CHARS_OUT("VulkanDevice::Draw complete"); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index ea21ef4638..fde185380f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -64,7 +64,7 @@ public: BufferPool(const BufferPool&) = delete; BufferPool& operator=(const BufferPool&) = delete; // The size a buffer for `bytes` is created with: its size class, or `bytes` itself when large. - static std::size_t Capacity(std::size_t bytes, VkMemoryPropertyFlags properties); + static std::size_t Capacity(std::size_t bytes); static VkBufferUsageFlags Usage(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties); VkDeviceMemory AllocateMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional hostType = std::nullopt, std::uint32_t* allocatedType = nullptr, bool newImage = false); static VkDeviceSize ImageBudget(VkDeviceSize deviceBudget); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index 74376eda61..e1f5f22c18 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -76,6 +76,7 @@ std::uint64_t HtileMetadataBytes(VkExtent2D extent); bool HtileFillCovers(std::uint64_t htile, VkExtent2D extent, std::uint64_t address, std::size_t bytes); std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D); std::size_t DepthPlaneCopyCount(); +VkDeviceSize DepthPlaneCopyBytes(); VkFormat HostDepthFormat(const Context& context, VkFormat format); float HostDepthBiasConstant(const Context& context, VkFormat format, float constant); std::shared_ptr DepthSurfaceStorage(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index ea02167400..4694fd5503 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -25,7 +25,7 @@ struct DrawTraceContext { // `recipe`, when given, receives the DrawRecipe a recorded, cacheable, reusable, non-indirect draw // built for its draw-cache entry (design_cpu_final M8; null otherwise, and always under // APS5_NO_DRAW_RECIPE=1). -void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr, const DrawTraceContext* trace = nullptr); +void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr, const DrawTraceContext* trace = nullptr, std::uint64_t shape = 0); struct PixelFunctionLdsLayouts { VkDescriptorSetLayout empty = VK_NULL_HANDLE; @@ -124,6 +124,7 @@ struct DrawPassCounts { std::uint64_t storeFlushes = 0; }; std::array DrawPassTotals(); +std::array DrawPassBreaks(); const char* IndirectDrawPathName(IndirectDrawPath path); // Draw packets that drew nothing, for the [draws] line: Nothing (an empty count, or no color writes diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index 4d29d617fb..72e07d8664 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -118,6 +118,10 @@ void SetHostImportDeferredSettles(bool enabled); std::uint64_t HostImportBudget(const VkPhysicalDeviceMemoryProperties& memory, std::uint64_t physicalBytes); void SetHostImportMaxBytes(std::uint64_t bytes); std::uint64_t HostImportLiveBytes(); +// A flip ends a frame: an import used in the current or the previous frame is never evicted for another one. +void NoteHostImportFrame(); +std::uint64_t HostImportFramesEnded(); +std::uint64_t HostImportEpoch(); std::uint64_t HostImportBudgetRemaining(const Context& context); struct HostImportSettleCounts { std::uint64_t submitted = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PassHazards.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PassHazards.hpp index 298ca9c49e..f389ccc716 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/PassHazards.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PassHazards.hpp @@ -5,6 +5,7 @@ #define VK_NO_PROTOTYPES #endif #include +#include #include #include #include @@ -27,6 +28,23 @@ struct PassAccess { enum class PassHazard : std::uint8_t { None, ReadAfterWrite, WriteAfterWrite, WriteAfterRead, Image, UnknownRead, UnknownWrite }; +enum class PassBreak : std::uint8_t { Continued, NewBatch, Command, Label, Samples, Forced, Segment, Key, ReadAfterWrite, WriteAfterWrite, WriteAfterRead, Image, UnknownRead, UnknownWrite, Count }; +constexpr std::size_t PassBreakCount = static_cast(PassBreak::Count); +constexpr std::array PassBreakNames{"continued", "new batch", "command", "label", "samples", "forced", "segment", "key", "read after write", "write after write", "write after read", "image", "unknown read", "unknown write"}; + +constexpr PassBreak PassBreakFor(PassHazard hazard) { + switch (hazard) { + case PassHazard::ReadAfterWrite: return PassBreak::ReadAfterWrite; + case PassHazard::WriteAfterWrite: return PassBreak::WriteAfterWrite; + case PassHazard::WriteAfterRead: return PassBreak::WriteAfterRead; + case PassHazard::Image: return PassBreak::Image; + case PassHazard::UnknownRead: return PassBreak::UnknownRead; + case PassHazard::UnknownWrite: return PassBreak::UnknownWrite; + case PassHazard::None: break; + } + return PassBreak::Key; +} + class GuestRangeSet { public: void Insert(std::uint64_t begin, std::uint64_t end); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index d246e5b4f1..067d251c70 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -115,7 +115,8 @@ private: // APS5_NO_PIPELINE_CACHE=1 builds one per draw as before. std::shared_ptr CachedPipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span shaders, VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); // `created` reports whether this call built the pipeline (false: served from the store or private). -std::shared_ptr CachedPipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, bool* created = nullptr); +std::shared_ptr CachedPipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, bool* created = nullptr, std::uint64_t* storeHash = nullptr); +std::shared_ptr TouchCachedPipeline(const Context& context, std::uint64_t hash, const std::weak_ptr& expected); // Cumulative pipeline store outcomes since start-up (the [pipecache] line reports 10 s windows). struct PipelineStoreCounters { std::uint64_t hits = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 826dc8cb7d..637a5e1ab4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -68,6 +68,7 @@ public: VkCommandBuffer commands = VK_NULL_HANDLE; bool continued = false; bool barrier = true; + PassBreak reason = PassBreak::Continued; }; DrawPassStart StartDrawPass(std::uint64_t key, bool forced, bool fenced, const PassAccess& access); void LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool fenced, const PassAccess& access, std::function afterPass = {}); @@ -673,6 +674,7 @@ private: std::uint64_t key = 0; std::uint32_t timing = NoTiming; std::function afterPass; + PassBreak closedBy = PassBreak::NewBatch; } renderPass; PassHazards passHazards; // A pass ended in this batch: Submit records the host-read barrier its draws left out. @@ -780,7 +782,7 @@ private: // form, toward the host only); returns whether the batch's host-read barrier was included. bool closeStoreRun(bool atSubmit = false); // Ends the render pass a draw left open (vkCmdEndRenderPass, the pass's trailing barrier). - void endOpenRenderPass(bool barrier = true); + void endOpenRenderPass(PassBreak by, bool barrier = true); // Records the queued key stores as one run (`forWriter`: before a command writing, reading or // labelling over one, not at Submit). void recordKeyStores(bool forWriter); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 150122e080..04f8a81b47 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -65,6 +65,7 @@ std::shared_ptr CachedStorageLayers(const Context& context, cons // would return); a true answer counts as a use for the cache's eviction order, as a lookup would. bool StorageImageCached(const Context& context, const StorageTexture* image); bool StorageImageServesKeys(const StorageTexture& image, std::uint64_t dccAddress); +std::shared_ptr RepeatStorageSurface(const Context& context, const GuestTextureResource& resource, const std::shared_ptr& stored); // Defined in Texture.cpp beside the pending-results registry, for the fast Revalidate below: whether // a storage image other than `except` has results pending in [address, address + bytes). diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index ad8b439410..190dd06e84 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -306,7 +306,7 @@ struct DrawKeyRange { std::uint32_t count; }; inline constexpr std::array DrawKeyRegisters{{ - {RegisterBank::Context, 0x000, 1}, {RegisterBank::Context, 0x002, 1}, {RegisterBank::Context, 0x005, 1}, {RegisterBank::Context, 0x007, 7}, {RegisterBank::Context, 0x010, 6}, {RegisterBank::Context, 0x01a, 5}, + {RegisterBank::Context, 0x000, 1}, {RegisterBank::Context, 0x002, 2}, {RegisterBank::Context, 0x005, 1}, {RegisterBank::Context, 0x007, 7}, {RegisterBank::Context, 0x010, 6}, {RegisterBank::Context, 0x01a, 5}, {RegisterBank::Context, 0x080, 4}, {RegisterBank::Context, 0x08c, 4}, {RegisterBank::Context, 0x090, 2}, {RegisterBank::Context, 0x094, 2}, {RegisterBank::Context, 0x0b4, 2}, {RegisterBank::Context, 0x105, 4}, {RegisterBank::Context, 0x10b, 3}, {RegisterBank::Context, 0x10f, 6}, // SPI_PS_INPUT_CNTL_0..31, SPI_PS_INPUT_ENA/ADDR, SPI_PS_IN_CONTROL .. SPI_BARYC_CNTL, SPI_SHADER_POS/Z/COL_FORMAT, // CB_BLEND0..7_CONTROL, GE_MAX_OUTPUT_PER_SUBGROUP. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/PerformanceOverlay.frag b/core/libs/prx/libSceAgcDriver/Graphics/shaders/PerformanceOverlay.frag index f9db234586..9bcbd62e64 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/shaders/PerformanceOverlay.frag +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/PerformanceOverlay.frag @@ -8,5 +8,5 @@ layout(location = 0) flat in uint paletteIndex; layout(location = 0) out vec4 color; void main() { - color = paletteIndex < 7u ? parameters.palette[paletteIndex] : parameters.palette[0]; + color = paletteIndex == 1u ? parameters.palette[1] : paletteIndex == 2u ? parameters.palette[2] : paletteIndex == 3u ? parameters.palette[3] : paletteIndex == 4u ? parameters.palette[4] : paletteIndex == 5u ? parameters.palette[5] : paletteIndex == 6u ? parameters.palette[6] : parameters.palette[0]; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp index 416b9d29e4..c8d36deaa3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -369,11 +369,10 @@ bool BufferPool::DeviceTiered(VkMemoryPropertyFlags properties) { return (properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0 && (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0 && DeviceTierEnabled(); } -std::size_t BufferPool::Capacity(std::size_t bytes, VkMemoryPropertyFlags properties) { +std::size_t BufferPool::Capacity(std::size_t bytes) { constexpr std::size_t smallest = 256; if (exactSizes()) return bytes; if (bytes < classLimit) return std::max(smallest, std::bit_ceil(bytes)); - if (!DeviceTiered(properties)) return bytes; const auto step = std::bit_floor(bytes) >> deviceClassBits; return (bytes + step - 1) / step * step; } @@ -393,7 +392,7 @@ BufferPool::Tier& BufferPool::tierFor(std::size_t capacity, VkMemoryPropertyFlag std::optional BufferPool::Take(std::size_t bytes, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; - const auto capacity = Capacity(bytes, properties); + const auto capacity = Capacity(bytes); std::lock_guard lock(mutex); ++clock; if (profile) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp index 1fb049628a..05da02fca1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DeferredDestruction.cpp @@ -1,5 +1,4 @@ #include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp" -#include #include #include #include @@ -25,7 +24,8 @@ struct Deferred { struct Registry { std::mutex mutex; std::unordered_map devices; - std::atomic resolver{nullptr}; + std::unordered_map resolvers; + PFN_vkGetDeviceProcAddr latest = nullptr; }; Registry& registry() { @@ -33,6 +33,13 @@ Registry& registry() { return state; } +PFN_vkGetDeviceProcAddr resolverFor(VkDevice device) { + auto& state = registry(); + std::lock_guard lock(state.mutex); + const auto found = state.resolvers.find(device); + return found == state.resolvers.end() ? state.latest : found->second; +} + void destroyAll(VkDevice device, const Deferred& deferred) noexcept { for (const auto buffer : deferred.buffers) deferred.destroyBuffer(device, buffer, nullptr); for (const auto memory : deferred.memory) deferred.freeMemory(device, memory, nullptr); @@ -56,18 +63,18 @@ bool defer(VkDevice device, THandle handle, std::vector Deferred::*list VKAPI_ATTR void VKAPI_CALL deferredDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* allocator) { if (buffer == VK_NULL_HANDLE || defer(device, buffer, &Deferred::buffers)) return; - reinterpret_cast(registry().resolver.load(std::memory_order_acquire)(device, "vkDestroyBuffer"))(device, buffer, allocator); + reinterpret_cast(resolverFor(device)(device, "vkDestroyBuffer"))(device, buffer, allocator); } VKAPI_ATTR void VKAPI_CALL deferredFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* allocator) { if (memory == VK_NULL_HANDLE || defer(device, memory, &Deferred::memory)) return; - reinterpret_cast(registry().resolver.load(std::memory_order_acquire)(device, "vkFreeMemory"))(device, memory, allocator); + reinterpret_cast(resolverFor(device)(device, "vkFreeMemory"))(device, memory, allocator); } VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL deferringDeviceProc(VkDevice device, const char* name) { if (std::strcmp(name, "vkDestroyBuffer") == 0) return reinterpret_cast(&deferredDestroyBuffer); if (std::strcmp(name, "vkFreeMemory") == 0) return reinterpret_cast(&deferredFreeMemory); - return registry().resolver.load(std::memory_order_acquire)(device, name); + return resolverFor(device)(device, name); } } @@ -80,10 +87,10 @@ PFN_vkGetDeviceProcAddr DeferDestructionToIdle(VkDevice device, PFN_vkGetDeviceP if (deferred.destroyBuffer == nullptr || deferred.freeMemory == nullptr) throw std::runtime_error("AGC graphics: missing Vulkan function: vkDestroyBuffer or vkFreeMemory"); auto& state = registry(); std::lock_guard lock(state.mutex); - const auto current = state.resolver.load(std::memory_order_acquire); - if (current != nullptr && current != resolver) throw std::runtime_error("AGC graphics: deferred destruction found two different device resolvers"); + if (!state.devices.empty() && state.latest != resolver) throw std::runtime_error("AGC graphics: deferred destruction found two different device resolvers"); if (!state.devices.emplace(device, std::move(deferred)).second) throw std::runtime_error("AGC graphics: deferred destruction was set up twice for one device"); - state.resolver.store(resolver, std::memory_order_release); + state.resolvers.insert_or_assign(device, resolver); + state.latest = resolver; return &deferringDeviceProc; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 6834e77f84..d8af46c18b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -1353,6 +1353,7 @@ public: const std::uint32_t layers; VkDeviceSize imageBytes = 0; std::uint64_t lastUse = 0; + std::uint64_t lastPresent = 0; VkDeviceSize HeldBytes() const { return imageBytes + (staging != nullptr ? staging->Size() : 0u); } private: @@ -1478,10 +1479,13 @@ void evictPlaneCopies(const std::tupleHeldBytes(); + const auto present = Recorder::Presents(); + const auto ceiling = budget + std::min(budget, std::numeric_limits::max() - budget); while (total > budget) { + const bool over = total > ceiling; auto oldest = copies.end(); for (auto it = copies.begin(); it != copies.end(); ++it) { - if (it->first == keep) continue; + if (it->first == keep || (!over && it->second->lastPresent + 1u >= present)) continue; if (oldest == copies.end() || it->second->lastUse < oldest->second->lastUse) oldest = it; } if (oldest == copies.end()) break; @@ -2218,6 +2222,7 @@ std::vector> FlushDepthPlanes(std::uint6 surface->ClearStale(); continue; } + GuestMemory::CollectWritesUncached(plane, static_cast(planeBytes)); surface->SyncFromMemory(resource); auto units = scope == DepthPlaneScope::Whole ? surface->staleUnits : surface->StaleUnitsIn(planeBytes, address, end); if (anyUnit(units)) { @@ -2435,6 +2440,12 @@ std::size_t DepthPlaneCopyCount() { return planeCopies().size(); } +VkDeviceSize DepthPlaneCopyBytes() { + VkDeviceSize total = 0; + for (const auto& entry : planeCopies()) total += entry.second->HeldBytes(); + return total; +} + namespace { thread_local bool depthCopySourceActive = false; @@ -2512,6 +2523,7 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::spanlayers < layers) copy = std::make_unique(context, base.extent, imageFormat, layers); copy->lastUse = ++planeCopyClock(); + copy->lastPresent = Recorder::Presents(); auto texture = copy->Refresh(slices, resource, components, cube || resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, layered && !cube ? resource.baseArray : 0u); evictPlaneCopies(copyKey); return texture; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 1422683ac0..cf991161d8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -567,6 +567,7 @@ struct DrawOutcome { // A recorded draw began a render pass of its own, or continued the previous draw's. bool passBegun = false; bool passContinued = false; + PassBreak passBreak = PassBreak::Continued; bool keyStoresFlushed = false; bool storesFlushed = false; bool addressDraw = false; @@ -643,6 +644,8 @@ struct DrawProfile { std::uint64_t passesBegun = 0; std::uint64_t passesContinued = 0; std::array passTotals{}; + std::array passBreaks{}; + std::array reportedPassBreaks{}; std::array reasons{}; // The CPU inside Graphics::Draw, split by outcome: a synchronous draw costs ten times a recorded // one, so one average would only show the synchronous population. @@ -713,6 +716,7 @@ void reportDraw(const std::array& us, const ShaderResources: if (outcome.recorded) ++(outcome.waited ? profile.waited : profile.recorded); if (outcome.completion) ++profile.completion; if (outcome.passBegun) ++profile.passesBegun; + if (outcome.passBegun) ++profile.passBreaks[static_cast(outcome.passBreak)]; if (outcome.passContinued) ++profile.passesContinued; if (outcome.recorded) { auto& totals = profile.passTotals[outcome.addressDraw ? 1 : 0]; @@ -742,8 +746,16 @@ void reportDraw(const std::array& us, const ShaderResources: const auto synchronous = profile.draws - profile.recorded - profile.waited; const auto average = [](double total, std::uint64_t count) { return count != 0 ? total / static_cast(count) : 0.0; }; char line[2048]; - int n = std::snprintf(line, sizeof(line), "[draws] %llu draws over 10 s (%llu recorded avg %.0f us, of them %llu with completion; %llu recorded then waited avg %.0f us; %llu synchronous avg %.0f us; render passes %llu begun, %llu draws continued one; waited or synchronous because:", static_cast(profile.draws), static_cast(profile.recorded), average(profile.recordedUs, profile.recorded), static_cast(profile.completion), static_cast(profile.waited), average(profile.waitedUs, profile.waited), static_cast(synchronous), average(profile.synchronousUs, synchronous), static_cast(profile.passesBegun), static_cast(profile.passesContinued)); + int n = std::snprintf(line, sizeof(line), "[draws] %llu draws over 10 s (%llu recorded avg %.0f us, of them %llu with completion; %llu recorded then waited avg %.0f us; %llu synchronous avg %.0f us; render passes %llu begun", static_cast(profile.draws), static_cast(profile.recorded), average(profile.recordedUs, profile.recorded), static_cast(profile.completion), static_cast(profile.waited), average(profile.waitedUs, profile.waited), static_cast(synchronous), average(profile.synchronousUs, synchronous), static_cast(profile.passesBegun)); const auto room = [&] { return n > 0 && static_cast(n) < sizeof(line); }; + bool listed = false; + for (std::size_t i = 1; i < PassBreakCount && room(); ++i) { + const auto count = profile.passBreaks[i] - profile.reportedPassBreaks[i]; + if (count == 0) continue; + n += std::snprintf(line + n, sizeof(line) - static_cast(n), "%s%s %llu", listed ? ", " : " (", PassBreakNames[i], static_cast(count)); + listed = true; + } + if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast(n), "%s, %llu draws continued one; waited or synchronous because:", listed ? ")" : "", static_cast(profile.passesContinued)); for (std::size_t i = SyncNone + 1; i < SyncCount && room(); ++i) { if (profile.reasons[i] != 0) n += std::snprintf(line + n, sizeof(line) - static_cast(n), " %s %llu", SyncReasonNames[i], static_cast(profile.reasons[i])); } @@ -792,6 +804,7 @@ void reportDraw(const std::array& us, const ShaderResources: profile.addressBuilds = 0; profile.draws = profile.recorded = profile.waited = profile.completion = 0; profile.passesBegun = profile.passesContinued = 0; + profile.reportedPassBreaks = profile.passBreaks; profile.reasons.fill(0); profile.recordedUs = profile.waitedUs = profile.synchronousUs = 0; profile.targetLookups = profile.slowLookups = 0; @@ -1125,6 +1138,12 @@ std::array DrawPassTotals() { return stats.passTotals; } +std::array DrawPassBreaks() { + auto& stats = Profile(); + std::lock_guard lock(stats.mutex); + return stats.passBreaks; +} + void CountDrawSkip(DrawSkip kind, double us) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile) return; @@ -2269,6 +2288,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam const bool continued = start.continued; outcome.passContinued = continued; outcome.passBegun = !continued; + outcome.passBreak = start.reason; const auto commands = start.commands; if (!continued) { recorder->PrepareSampleSlot(); @@ -2495,6 +2515,7 @@ struct PreparedDraw { PreparedDraw& operator=(const PreparedDraw&) = delete; std::shared_ptr* recipeOut = nullptr; const DrawTraceContext* trace = nullptr; + std::uint64_t shape = 0; std::vector stencilReferences; std::optional timing; DrawTimer timer; @@ -2527,6 +2548,137 @@ struct PreparedDraw { std::optional depthLocations; }; +bool ShapeMemoEnabled() { + static const bool enabled = std::getenv("APS5_NO_SHAPE_MEMO") == nullptr; + return enabled; +} + +bool VerifyShapeMemo() { + static const bool verify = std::getenv("APS5_VERIFY_SHAPE_MEMO") != nullptr; + return verify; +} + +void noteShapeMemoVerified(bool pipeline) { + static std::atomic targets{0}; + static std::atomic pipelines{0}; + static std::atomic last{0}; + (pipeline ? pipelines : targets).fetch_add(1, std::memory_order_relaxed); + const auto now = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + auto previous = last.load(std::memory_order_relaxed); + if (now - previous < 10 || !last.compare_exchange_strong(previous, now)) return; + std::fprintf(stderr, "[shape-memo] verified %llu targets and %llu pipelines\n", static_cast(targets.load()), static_cast(pipelines.load())); +} + +struct ShapeChoice { + VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + std::uint32_t fragmentOutputs = 0; + std::vector stages; + std::vector bindings; + std::vector attributes; + std::vector mainKey; + std::vector fragmentKey; + std::uint64_t hash = 0; + std::weak_ptr pipeline; +}; + +struct ShapeEntry { + VkDevice device = VK_NULL_HANDLE; + std::vector> targets; + std::vector choices; +}; + +struct ShapeMemoStorage {}; +constexpr std::size_t ShapeMemoEntries = 4096; +constexpr std::size_t ShapeMemoChoices = 4; + +std::unordered_map& shapeMemo() { + return HostThreadLocal, ShapeMemoStorage>(); +} + +const ShapeEntry* findShape(const Context& context, std::uint64_t shape) { + if (shape == 0 || !ShapeMemoEnabled()) return nullptr; + const auto& entries = shapeMemo(); + const auto found = entries.find(shape); + return found == entries.end() || found->second.device != context.device ? nullptr : &found->second; +} + +std::shared_ptr shapeTarget(const Context& context, std::uint64_t shape, std::size_t index) { + const auto* entry = findShape(context, shape); + return entry == nullptr || index >= entry->targets.size() ? nullptr : entry->targets[index].lock(); +} + +bool sameStages(std::span kept, std::span shaders) { + if (kept.size() != shaders.size() * 4u + 1u) return false; + std::size_t at = 0; + for (const auto& shader : shaders) { + const auto identity = shader.program->Identity(); + if (identity.Empty() || kept[at] != static_cast(shader.stage) || kept[at + 1] != identity.low || kept[at + 2] != identity.high || kept[at + 3] != shader.pushConstantOffset) return false; + at += 4; + } + return kept[at] == PushConstantStages(shaders); +} + +bool sameChoice(const ShapeChoice& choice, const VertexInputLayout& input, const PipelineSetLayouts& layouts, std::span shaders, VkImageLayout attachmentLayout, std::uint32_t fragmentOutputs) { + const auto sameBinding = [](const VkVertexInputBindingDescription& a, const VkVertexInputBindingDescription& b) { return a.binding == b.binding && a.stride == b.stride && a.inputRate == b.inputRate; }; + const auto sameAttribute = [](const VkVertexInputAttributeDescription& a, const VkVertexInputAttributeDescription& b) { return a.location == b.location && a.binding == b.binding && a.format == b.format && a.offset == b.offset; }; + return choice.attachmentLayout == attachmentLayout && choice.fragmentOutputs == fragmentOutputs && std::ranges::equal(choice.bindings, input.bindings, sameBinding) && std::ranges::equal(choice.attributes, input.attributes, sameAttribute) && std::ranges::equal(choice.mainKey, layouts.mainKey) && std::ranges::equal(choice.fragmentKey, layouts.fragmentKey) && sameStages(choice.stages, shaders); +} + +std::shared_ptr shapePipeline(const Context& context, std::uint64_t shape, const VertexInputLayout& input, const PipelineSetLayouts& layouts, std::span shaders, VkImageLayout attachmentLayout, std::uint32_t fragmentOutputs) { + const auto* entry = findShape(context, shape); + if (entry == nullptr) return nullptr; + for (const auto& choice : entry->choices) { + if (!sameChoice(choice, input, layouts, shaders, attachmentLayout, fragmentOutputs)) continue; + const auto hash = choice.hash; + const auto expected = choice.pipeline; + return TouchCachedPipeline(context, hash, expected); + } + return nullptr; +} + +void rememberShape(const Context& context, std::uint64_t shape, std::span targets, const std::shared_ptr* pipeline, std::uint64_t hash, const VertexInputLayout& input, const PipelineSetLayouts& layouts, std::span shaders, VkImageLayout attachmentLayout, std::uint32_t fragmentOutputs) { + if (shape == 0 || !ShapeMemoEnabled()) return; + auto& entries = shapeMemo(); + if (entries.size() >= ShapeMemoEntries && !entries.contains(shape)) entries.clear(); + auto& entry = entries[shape]; + if (entry.device != context.device) { + entry = {}; + entry.device = context.device; + } + entry.targets.resize(targets.size()); + for (std::size_t index = 0; index < targets.size(); ++index) { + const auto& resident = targets[index].resident; + if (resident == nullptr || targets[index].color.arrayLastSlice != 0) entry.targets[index].reset(); + else if (entry.targets[index].owner_before(resident) || resident.owner_before(entry.targets[index])) entry.targets[index] = resident; + } + if (pipeline == nullptr || hash == 0) return; + auto& choices = entry.choices; + const auto kept = std::ranges::find_if(choices, [&](const ShapeChoice& choice) { return sameChoice(choice, input, layouts, shaders, attachmentLayout, fragmentOutputs); }); + if (kept != choices.end()) { + kept->hash = hash; + kept->pipeline = *pipeline; + std::rotate(choices.begin(), kept, std::next(kept)); + return; + } + ShapeChoice choice; + choice.attachmentLayout = attachmentLayout; + choice.fragmentOutputs = fragmentOutputs; + for (const auto& shader : shaders) { + const auto identity = shader.program->Identity(); + if (identity.Empty()) return; + choice.stages.insert(choice.stages.end(), {static_cast(shader.stage), identity.low, identity.high, shader.pushConstantOffset}); + } + choice.stages.push_back(PushConstantStages(shaders)); + choice.bindings = input.bindings; + choice.attributes = input.attributes; + choice.mainKey.assign(layouts.mainKey.begin(), layouts.mainKey.end()); + choice.fragmentKey.assign(layouts.fragmentKey.begin(), layouts.fragmentKey.end()); + choice.hash = hash; + choice.pipeline = *pipeline; + if (choices.size() >= ShapeMemoChoices) choices.pop_back(); + choices.insert(choices.begin(), std::move(choice)); +} + bool prepareDraw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, PreparedDraw& prepared) { auto*& recipeOut = prepared.recipeOut; auto& stencilReferences = prepared.stencilReferences; @@ -2630,7 +2782,13 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara Require(resident->Attachable(), "storage format cannot be a color attachment"); return resident; } - auto resident = CachedStorageSurface(context, SurfaceForTarget(color)); + const auto surface = SurfaceForTarget(color); + auto resident = RepeatStorageSurface(context, surface, shapeTarget(context, prepared.shape, index)); + if (resident != nullptr && VerifyShapeMemo()) { + Require(CachedStorageSurface(context, surface) == resident, "APS5_VERIFY_SHAPE_MEMO: the shape memo's resident target differs from the storage cache's"); + noteShapeMemoVerified(false); + } + if (resident == nullptr) resident = CachedStorageSurface(context, surface); Require(resident->Attachable(), "storage format cannot be a color attachment"); Require(color.mipCount > 1 || color.depth > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout"); return resident; @@ -2890,7 +3048,22 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara stencilReplay.emplace_back(reference, std::move(selectedPipeline)); } auto& pipeline = prepared.pipeline; - pipeline = stencilReplay.empty() ? CachedPipeline(context, pipelineState, inputs.VertexLayout(), *resources, shaders, lean ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) : stencilReplay.front().second; + const auto attachmentLayout = lean ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + if (!stencilReplay.empty()) { + pipeline = stencilReplay.front().second; + } else { + const bool memoized = stencilReferences.empty() && std::none_of(targets.begin(), targets.end(), [](const TargetBinding& binding) { return binding.proxied || binding.multisampled; }); + const auto layouts = resources->SetLayouts(); + std::uint64_t storeHash = 0; + if (memoized) pipeline = shapePipeline(context, prepared.shape, inputs.VertexLayout(), layouts, shaders, attachmentLayout, inputs.fragmentOutputs); + if (pipeline != nullptr && VerifyShapeMemo()) { + Require(CachedPipeline(context, pipelineState, inputs.VertexLayout(), layouts, shaders, attachmentLayout) == pipeline, "APS5_VERIFY_SHAPE_MEMO: the shape memo's pipeline differs from the pipeline store's"); + noteShapeMemoVerified(true); + } + const bool repeated = pipeline != nullptr; + if (!repeated) pipeline = CachedPipeline(context, pipelineState, inputs.VertexLayout(), layouts, shaders, attachmentLayout, nullptr, &storeHash); + if (memoized) rememberShape(context, prepared.shape, targets, repeated ? nullptr : &pipeline, storeHash, inputs.VertexLayout(), layouts, shaders, attachmentLayout, inputs.fragmentOutputs); + } // Resident targets' views are stable while their storage image lives, so the framebuffer is // reused with the pipeline; a per-draw RenderTarget gets a framebuffer of its own. auto& owners = prepared.owners; @@ -3354,7 +3527,7 @@ void drawTessellationGeometry(const Context& context, const State& state, const Draw(context, meshed, captured, second, snapshots, nullptr, trace); } -void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipeOut, const DrawTraceContext* trace) { +void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipeOut, const DrawTraceContext* trace, std::uint64_t shape) { const DepthCopySourceScope depthCopySource(state.depthCopySource); if (state.stages.tessellationGeometry) { drawTessellationGeometry(context, state, draw, shaders, snapshots, trace); @@ -3364,6 +3537,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& PreparedDraw prepared(profile); prepared.recipeOut = recipeOut; prepared.trace = trace; + prepared.shape = shape; if (prepareDraw(context, state, draw, shaders, snapshots, prepared)) recordPreparedDraw(context, state, draw, shaders, prepared); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 00510dabcd..64de3d8c8d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -193,6 +193,7 @@ struct EvictingRefusal { std::uint64_t bytes; GuestAllocations::Mapped ranges; std::uint64_t epoch; + std::uint64_t frames; std::uint64_t liveBytes; std::uint64_t budget; std::uint64_t room; @@ -294,6 +295,9 @@ struct HostImports { #endif std::uint64_t useCounter = 0; std::uint64_t buildFloor = 0; + std::uint64_t frameFloor = 0; + std::uint64_t previousFrameFloor = 0; + std::uint64_t framesEnded = 0; std::array counts{}; VkPhysicalDevice reportedDevice = VK_NULL_HANDLE; std::chrono::steady_clock::time_point reportedAt{}; @@ -1611,12 +1615,18 @@ bool pointerHeld(const HostImports& state, const HostImport& entry) { constexpr std::uint64_t ProtectedUses = 256; +bool hasFrameHistory(const HostImports& state) { + return state.previousFrameFloor != 0; +} + bool evictable(const HostImports& state, const HostImport& entry) { - return !pointerHeld(state, entry) && effectiveUse(state, entry) + ProtectedUses < state.useCounter; + if (pointerHeld(state, entry)) return false; + if (!hasFrameHistory(state)) return effectiveUse(state, entry) + ProtectedUses < state.useCounter; + return effectiveUse(state, entry) < state.previousFrameFloor; } bool evictingRefusalCurrent(const HostImports& state, const EvictingRefusal& refusal, std::uint64_t budget) { - return refusal.epoch == state.epoch && refusal.liveBytes == state.liveBytes && budget <= refusal.budget && refusal.window == importWindow.load(std::memory_order_relaxed) && state.useCounter < refusal.ready && (state.buildFloor <= refusal.floor || state.useCounter < refusal.readyHeld); + return refusal.epoch == state.epoch && refusal.frames == state.framesEnded && refusal.liveBytes == state.liveBytes && budget <= refusal.budget && refusal.window == importWindow.load(std::memory_order_relaxed) && state.useCounter < refusal.ready && (state.buildFloor <= refusal.floor || state.useCounter < refusal.readyHeld); } void noteRetiredUnwatched(std::vector>& spans, const HostImport& entry, const GuestAllocations::Lease& lease) { @@ -1822,7 +1832,9 @@ const HostImport* peekImport(const HostImports& state, std::uint64_t begin, std: const HostImport* findImport(HostImports& state, std::uint64_t begin, std::uint64_t end); const HostImport* importRunAt(const Context& context, HostImports& state, ImportRun run, bool joined, std::uint64_t address, const GuestAllocations::Lease& lease, std::uint64_t end, bool evict, ImportRequester requester); -// The bytes retireLeastRecent could free outside [begin, end). +// The bytes retireLeastRecent could free outside [begin, end). Before two frames have ended the imports age by lookups as they +// did before frames existed. After that readyHeld is 0 when an import a build still holds is already two frames old: it becomes +// evictable as soon as that build's floor rises, which ends a remembered refusal. std::uint64_t evictableBytes(const HostImports& state, std::uint64_t begin, std::uint64_t end, std::uint64_t& ready, std::uint64_t& readyHeld) { std::uint64_t bytes = 0; for (const auto& [base, entry] : state.imports) { @@ -1831,8 +1843,10 @@ std::uint64_t evictableBytes(const HostImports& state, std::uint64_t begin, std: bytes += entry.pinnedBytes; continue; } - auto& horizon = pointerHeld(state, entry) ? readyHeld : ready; - horizon = std::min(horizon, effectiveUse(state, entry) + ProtectedUses + 1); + if (!hasFrameHistory(state)) { + auto& horizon = pointerHeld(state, entry) ? readyHeld : ready; + horizon = std::min(horizon, effectiveUse(state, entry) + ProtectedUses + 1); + } else if (pointerHeld(state, entry) && effectiveUse(state, entry) < state.previousFrameFloor) readyHeld = 0; } return bytes; } @@ -2060,7 +2074,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import std::fprintf(stderr, "[gpu] host import budget: %llu MiB live of %llu MiB (%s); %llu imports (%llu MiB) refused so far, %llu of them remembered, last 0x%llx+0x%llx by %s\n", static_cast(state.liveBytes >> 20u), static_cast(budget >> 20u), budgetSource(context, state, budget).c_str(), static_cast(refused), static_cast(refusedBytes >> 20u), static_cast(remembered), static_cast(base), static_cast(bytes), ImportRequesterName(requester)); } const auto room = budget + evictable > live ? budget + evictable - live : 0; - if (evict) state.evictingRefusals.insert_or_assign(std::pair{base, bytes}, EvictingRefusal{base, bytes, GuestAllocations::Mapped(run.ranges.begin(), run.ranges.end()), state.epoch, state.liveBytes, budget, room, importWindow.load(std::memory_order_relaxed), state.buildFloor, ready, readyHeld, joined}); + if (evict) state.evictingRefusals.insert_or_assign(std::pair{base, bytes}, EvictingRefusal{base, bytes, GuestAllocations::Mapped(run.ranges.begin(), run.ranges.end()), state.epoch, state.framesEnded, state.liveBytes, budget, room, importWindow.load(std::memory_order_relaxed), state.buildFloor, ready, readyHeld, joined}); if (stepDown && containing != run.ranges.end() && room >= (*containing)->bytes) return importAllocation(context, state, address, lease, requester, end, evict, std::min(bytes / 2, room)); if (containing != run.ranges.end() && run.ranges.size() > 1) remember((*containing)->address, (*containing)->bytes, GuestAllocations::Mapped{*containing}); ++state.budgetRefusals; @@ -2162,9 +2176,10 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import GuestArena::GuestArenaUnmapAlias_nid_postfix(std::exchange(entry.alias, nullptr)); #endif std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx failed at %s (%d); falling back to copies, requested by %s\n", static_cast(base), static_cast(bytes), step, static_cast(result), ImportRequesterName(requester)); + const bool exhausted = result == VK_ERROR_OUT_OF_HOST_MEMORY || result == VK_ERROR_OUT_OF_DEVICE_MEMORY; if (std::string_view(step) == "vkAllocateMemory") { state.allocationFailed = true; - if (++state.allocationFailures >= 3 || state.liveBytes + bytes + budget / 8 >= budget) { + if (exhausted && (++state.allocationFailures >= 3 || state.liveBytes + bytes + budget / 8 >= budget)) { state.ceiling = std::max(state.liveBytes, 1); std::fprintf(stderr, "[gpu] host import budget lowered to the %.1f MiB live after vkAllocateMemory failed %s\n", state.liveBytes / 1048576.0, state.allocationFailures >= 3 ? "3 times in a row" : "within an eighth of the budget"); } @@ -3963,6 +3978,26 @@ void SetHostImportMaxBytes(std::uint64_t bytes) { importMaxBytes.store(bytes, std::memory_order_relaxed); } +void NoteHostImportFrame() { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + state.previousFrameFloor = state.frameFloor; + state.frameFloor = state.useCounter + 1; + ++state.framesEnded; +} + +std::uint64_t HostImportEpoch() { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + return state.epoch; +} + +std::uint64_t HostImportFramesEnded() { + auto& state = Imports(); + std::lock_guard lock(state.mutex); + return state.framesEnded; +} + void SetHostImportBudget(std::uint64_t bytes) { budgetOverride.store(bytes, std::memory_order_relaxed); auto& state = Imports(); @@ -5381,6 +5416,7 @@ void GuestBufferMemory::UploadFinish(bool addressable) { if (space != nullptr) completeSpaceWrites(); uploaded = true; for (const auto& region : regions) static_cast(FlushDepthPlanes(region.begin, static_cast(region.end - region.begin))); + for (const auto& [begin, end] : writes) RequireNoResidentDepthRead_nid_no_patch(begin, static_cast(end - begin)); for (const auto& [begin, end] : writes) StorageTexture::FlushPending(begin, static_cast(end - begin), nullptr, "written buffer range"); if (context.hostImportAlignment != 0) { auto& state = Imports(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 7a7788c964..0f22b8c15b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -248,11 +248,7 @@ GuestTextureResource DecodeTextureResource(std::span words) case TextureDimension::kCube: Require(width == height, "guest cube texture descriptor is not square"); Require(baseArray <= depth, "guest cube texture descriptor has a base array past its last array slice"); - if (baseArray == depth) depth += 5u; - if (depth - baseArray + 1u < 6u) { - Require(false, "guest cube texture descriptor holds fewer than 6 array slices" + - describe() + " (base array " + std::to_string(baseArray) + ")"); - } + if (depth - baseArray < 5u) depth = baseArray + 5u; break; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp index 99cc95be63..2ebb1e9656 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp @@ -1172,7 +1172,8 @@ VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span pattern{}; std::memcpy(pattern.data(), texels.data(), texels.size()); VkClearColorValue clear{}; - Require(ClearColorForTexel(target.format, target.elementBytes, pattern, clear), "multisampled DCC fast clear has no exact host value"); + const bool repeated = std::ranges::all_of(pattern, [&](std::uint32_t word) { return word == pattern.front(); }); + Require(ClearColorForTexel(target.format, target.elementBytes, pattern, clear) || (repeated && MultisampledFillClearColor(target, pattern.front(), clear)), "multisampled DCC fast clear has no exact host value"); return clear; } @@ -1278,12 +1279,16 @@ bool sameStorageForm(const Context& context, VkFormat rendered, VkFormat request return storage != VK_FORMAT_UNDEFINED && storage == HostStorageFormat(context, requested); } +VkFormat sampledFormat(const Context& context, std::uint32_t guestFormat) { + return IsRepackedTextureFormat(guestFormat) ? ResolveTextureFormat(guestFormat) : SampledTextureFormat(context, guestFormat); +} + } std::shared_ptr MultisampleTargetTexture(const Context& context, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType) { std::lock_guard lock(targetsMutex()); discardRetiredTargets(); - const auto format = ResolveTextureFormat(resource.format); + const auto format = sampledFormat(context, resource.format); const MultisampleTarget* other = nullptr; for (const auto& target : targets()) { if (!target->active->load(std::memory_order_acquire)) continue; @@ -1306,7 +1311,7 @@ std::shared_ptr MultisampleTargetTexture(const Context& context, const std::shared_ptr MultisampleTargetFirstSample(const Context& context, const GuestTextureResource& resource, VkComponentMapping components) { std::lock_guard lock(targetsMutex()); discardRetiredTargets(); - const auto format = ResolveTextureFormat(resource.format); + const auto format = sampledFormat(context, resource.format); const MultisampleTarget* other = nullptr; for (const auto& target : targets()) { if (!target->active->load(std::memory_order_acquire)) continue; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 353e5a7d1e..c0779ee61c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -1228,9 +1228,10 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st return CachedPipeline(context, state, vertexInput, resources.SetLayouts(), shaders, attachmentLayout); } -std::shared_ptr CachedPipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout, bool* created) { +std::shared_ptr CachedPipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout, bool* created, std::uint64_t* storeHash) { static const bool disabled = std::getenv("APS5_NO_PIPELINE_CACHE") != nullptr; if (created != nullptr) *created = false; + if (storeHash != nullptr) *storeHash = 0; if (disabled) return std::make_shared(context, state, vertexInput, resources, shaders, attachmentLayout); auto& store = Pipelines(); struct PipelineKeyStorage {}; @@ -1270,6 +1271,7 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st if (auto pipeline = lookup(collision)) { ++store.hits; ++store.totals.hits; + if (storeHash != nullptr) *storeHash = hash; return pipeline; } if (collision) { @@ -1290,7 +1292,10 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st { std::lock_guard lock(store.mutex); bool collision = false; - if (auto raced = lookup(collision)) return raced; + if (auto raced = lookup(collision)) { + if (storeHash != nullptr) *storeHash = hash; + return raced; + } if (collision) { ++store.uncached; ++store.totals.uncached; @@ -1325,12 +1330,27 @@ std::shared_ptr CachedPipeline(const Context& context, const State& st ++store.evicted; ++store.totals.evicted; } + if (storeHash != nullptr && store.index.contains(hash)) *storeHash = hash; } if (created != nullptr) *created = true; if (PipelineJournalEnabled() && !PipelineJournalReplaying() && state.tessellationCapture == nullptr) RecordGraphicsPipeline(MakeGraphicsPipelineRecipe(state, vertexInput, resources, shaders, attachmentLayout), shaders); return pipeline; } +std::shared_ptr TouchCachedPipeline(const Context& context, std::uint64_t hash, const std::weak_ptr& expected) { + auto& store = Pipelines(); + std::lock_guard lock(store.mutex); + reportPipelines(store); + const auto found = store.index.find(hash); + if (found == store.index.end()) return nullptr; + const auto it = found->second; + if (it->pipeline.owner_before(expected) || expected.owner_before(it->pipeline) || !alive(*it, context)) return nullptr; + store.entries.splice(store.entries.end(), store.entries, it); + ++store.hits; + ++store.totals.hits; + return it->pipeline; +} + void BuildPipelineStoreKey(std::vector& key, const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout) { pipelineKey(key, context, state, vertexInput, resources, shaders, attachmentLayout); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp index 28df53956c..ab8802ea4d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +73,12 @@ PipelineCache::PipelineCache(const Context& context, const VkPhysicalDevicePrope result = create(context.device, &info, nullptr, &cache); } Check(result, "vkCreatePipelineCache"); - if (!path.empty()) saver = std::thread([this] { run(); }); + try { + if (!path.empty()) saver = std::thread([this] { run(); }); + } catch (...) { + context.Function("vkDestroyPipelineCache")(context.device, cache, nullptr); + throw; + } } PipelineCache::~PipelineCache() { @@ -179,7 +185,7 @@ void PipelineCache::load(std::vector& initialData) { std::fprintf(stderr, "[pipeline-cache] loaded %.1f KiB from %s\n", static_cast(initialData.size()) / 1024.0, path.string().c_str()); } -void PipelineCache::save(bool final, bool waitForPipelines) { +void PipelineCache::save(bool final, bool waitForPipelines) try { std::lock_guard pathLock(pathMutex(path)); const auto getData = context.Function("vkGetPipelineCacheData"); const auto snapshot = [&] { return waitForPipelines ? synchronization.LockForSnapshot() : synchronization.TryLockForSnapshot(); }; @@ -200,7 +206,6 @@ void PipelineCache::save(bool final, bool waitForPipelines) { auto disk = fileUnchanged ? File{} : readFile(); if (disk.state == FileState::Unreadable) { std::fprintf(stderr, "[pipeline-cache] cannot read %s; not saving over it\n", path.string().c_str()); - settle(readHeader()); return; } if (disk.state == FileState::Invalid) std::fprintf(stderr, "[pipeline-cache] replacing %s: %s\n", path.string().c_str(), disk.invalidReason); @@ -271,6 +276,8 @@ void PipelineCache::save(bool final, bool waitForPipelines) { } settle(header); if (final || profiling()) std::fprintf(stderr, "[pipeline-cache] saved %.1f KiB to %s in %.1f ms%s%s\n", static_cast(size) / 1024.0, path.string().c_str(), std::chrono::duration(std::chrono::steady_clock::now() - started).count(), mergedWithFile ? " (merged with the file)" : "", final ? " (teardown)" : ""); +} catch (const std::bad_alloc&) { + std::fputs("[pipeline-cache] save skipped: host memory exhausted\n", stderr); } void PipelineCache::SaveNow() { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 04e22b2b1d..8f077856a2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1440,7 +1440,7 @@ VkCommandBuffer Recorder::Commands(VkAccessFlags* coveredAccess) { // Work recorded after a draw's render pass or an inline store run must see their writes: the // pass's end and the run's trailing barrier go in first (a per-batch run waits for Submit, or // for a caller whose ranges overlap a queued store: FlushStores). - if (open->renderPass.open) endOpenRenderPass(); + if (open->renderPass.open) endOpenRenderPass(PassBreak::Command); if (open->run.open && !LabelRunsPerBatch()) closeStoreRun(); if (coveredAccess != nullptr) *coveredAccess = open->coveredAccess; open->coveredAccess = 0; @@ -1455,20 +1455,22 @@ Recorder::DrawPassStart Recorder::StartDrawPass(std::uint64_t key, bool forced, ensureOpen(); auto& pass = open->renderPass; if (!pass.open || forced || (countingSamples && segmentMode() && !segmentSlotReady(*open))) { + const auto reason = !pass.open ? pass.closedBy : forced ? PassBreak::Forced : PassBreak::Segment; const auto commands = Commands(); open->passHazards.Clear(); open->passHazards.BeginPass(); - return {commands, false, true}; + return {commands, false, true, reason}; } const auto hazard = open->passHazards.Check(access, pass.key == key); - if (hazard == PassHazard::None && pass.key == key) return {open->commands, true, false}; + if (hazard == PassHazard::None && pass.key == key) return {open->commands, true, false, PassBreak::Continued}; + const auto reason = PassBreakFor(hazard); const bool merge = MergeBarriers() && !fenced && !pass.fenced && !pass.afterPass; const bool elide = merge && hazard == PassHazard::None; - endOpenRenderPass(!merge); + endOpenRenderPass(reason, !merge); const auto commands = Commands(); if (!elide) open->passHazards.Clear(); open->passHazards.BeginPass(); - return {commands, false, !elide}; + return {commands, false, !elide, reason}; } void Recorder::LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool fenced, const PassAccess& access, std::function afterPass) { @@ -1488,7 +1490,7 @@ void Recorder::SealPassWriteOnly() { if (open != nullptr) open->passHazards.SealWriteOnly(); } -void Recorder::endOpenRenderPass(bool barrier) { +void Recorder::endOpenRenderPass(PassBreak by, bool barrier) { auto& pass = open->renderPass; if (open->segmentActive) endSegment(*open); EndRenderPass(context, open->commands); @@ -1503,6 +1505,7 @@ void Recorder::endOpenRenderPass(bool barrier) { open->coveredAccess = barrier ? VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT : 0; open->hostReadOwed = true; pass = {}; + pass.closedBy = by; } void Recorder::QueueKeyStore(VkBuffer buffer, VkDeviceSize first, VkDeviceSize last, std::shared_ptr seed, std::uint64_t begin, std::uint64_t end) { @@ -1643,7 +1646,7 @@ void Recorder::RecordStore(VkBuffer buffer, VkDeviceSize offset, std::spankeyStores.empty()) recordKeyStores(true); - if (open->renderPass.open) endOpenRenderPass(); + if (open->renderPass.open) endOpenRenderPass(PassBreak::Label); open->coveredAccess = 0; // Two transfers to the same bytes have no order of their own within the run. const auto overlapsRecorded = [&] { return std::any_of(run.recorded.begin(), run.recorded.end(), [&](const auto& store) { return std::get<0>(store) == buffer && offset < std::get<2>(store) && std::get<1>(store) < end; }); }; @@ -1716,7 +1719,7 @@ bool Recorder::closeStoreRun(bool atSubmit) { // 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(); + if (open->renderPass.open) endOpenRenderPass(PassBreak::Label); const auto commands = open->commands; run.timing = beginTiming(ClassKey(CommandClass::LabelRun)); if (BarrierValidate()) { @@ -2119,7 +2122,7 @@ void Recorder::CountSamples() { gpuSampleCounter(); countingSamples = true; if (segmentMode() || open == nullptr || open->samples != VK_NULL_HANDLE) return; - if (open->renderPass.open) endOpenRenderPass(); + if (open->renderPass.open) endOpenRenderPass(PassBreak::Samples); beginSamples(*open); } @@ -3630,7 +3633,7 @@ void Recorder::Submit() { if (open == nullptr) return; GuestMemory::AssertGpuLockHeld("Recorder::Submit"); if (!open->keyStores.empty()) recordKeyStores(false); - if (open->renderPass.open) endOpenRenderPass(); + if (open->renderPass.open) endOpenRenderPass(PassBreak::NewBatch); const bool segments = segmentMode(); if (!segments) { endSamples(*open); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index ea37fed7ef..e75cb79f82 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -6,11 +6,21 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include #include +#ifdef __APPLE__ +#include +#endif namespace AgcDriver::Graphics { namespace { +#ifdef __APPLE__ +void ClearPooledTail(void* mapping, std::size_t size, VkDeviceSize mappedBytes, VkMemoryPropertyFlags properties) { + if (mapping == nullptr || (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0 || mappedBytes <= size) return; + std::memset(static_cast(mapping) + size, 0, static_cast(mappedBytes - size)); +} + +#endif VkDeviceSize ClassAllocationBytes(const VkMemoryRequirements& requirements, std::size_t capacity) { const auto alignment = std::max(requirements.alignment, 1); return std::max(requirements.size, (static_cast(capacity) + alignment - 1) / alignment * alignment); @@ -38,7 +48,7 @@ VkBuffer BindExactBuffer(const Context& context, std::size_t size, VkBufferUsage } -Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) : context(context), size(size), capacity(BufferPool::Capacity(size, properties)), usage(BufferPool::Usage(usage, properties)), properties(properties) { +Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) : context(context), size(size), capacity(BufferPool::Capacity(size)), usage(BufferPool::Usage(usage, properties)), properties(properties) { Require(size != 0, "zero-sized GPU buffer"); const bool addressable = (usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0; Require(!addressable || context.bufferDeviceAddress, "buffer device address is not enabled"); @@ -52,6 +62,9 @@ Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usag capacity = allocation->bytes; offset = allocation->offset; slab = allocation->slab; +#ifdef __APPLE__ + ClearPooledTail(mapping, size, std::min(capacity, allocationBytes), properties); +#endif if (allocation->bufferBytes != size) { context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); buffer = VK_NULL_HANDLE; @@ -93,6 +106,9 @@ Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usag Check(context.Function("vkBindBufferMemory")(context.device, buffer, memory, offset), "vkBindBufferMemory slab"); initializeAddress(usage); mapping = slot.mapping; +#ifdef __APPLE__ + ClearPooledTail(mapping, size, capacity, properties); +#endif ready = true; return; } @@ -144,7 +160,7 @@ void Buffer::Invalidate() { Check(context.Function("vkInvalidateMappedMemoryRanges")(context.device, 1, &range), "vkInvalidateMappedMemoryRanges"); } -DeviceBuffer::DeviceBuffer(const Context& context, std::size_t size, VkBufferUsageFlags usage) : context(context), size(size), capacity(BufferPool::Capacity(size, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)), usage(BufferPool::Usage(usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { +DeviceBuffer::DeviceBuffer(const Context& context, std::size_t size, VkBufferUsageFlags usage) : context(context), size(size), capacity(BufferPool::Capacity(size)), usage(BufferPool::Usage(usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { Require(size != 0, "zero-sized device buffer"); cache = GetBufferPool(context); if (const auto allocation = cache->Take(size, this->usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 30ea580803..08278405d1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -6,6 +6,9 @@ #include "prx/libc/include/HostThreadLocal.hpp" #include #include +#include +#include +#include #include #include #include @@ -27,6 +30,9 @@ constexpr std::uint32_t spiPsInputAddr = 0x1B4; constexpr std::uint32_t spiPsInControl = 0x1B6; constexpr std::uint32_t spiBarycCntl = 0x1B8; constexpr std::uint32_t dbShaderControl = 0x203; +constexpr std::uint32_t dbRenderOverride = 0x003; +constexpr std::uint32_t paScVportZmin0 = 0x0B4; +constexpr std::uint32_t paScVportZmax0 = 0x0B5; constexpr std::uint32_t spiShaderColFormat = 0x1C5; constexpr std::uint32_t defaultPixelInputs = 0x2u; @@ -143,6 +149,20 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con } const bool pixelKillEnable = ((shaderControl >> 6u) & 0x1u) != 0; const bool depthExportEnable = (shaderControl & 0x1u) != 0; + float depthExportMin = 0.0f; + float depthExportMax = 1.0f; + if (depthExportEnable) { + if ((read(context, dbRenderOverride, RegisterBank::Context) & 0x10000u) != 0) { + depthExportMin = -std::numeric_limits::infinity(); + depthExportMax = std::numeric_limits::infinity(); + } else { + depthExportMin = std::bit_cast(read(context, paScVportZmin0, RegisterBank::Context)); + depthExportMax = std::bit_cast(read(context, paScVportZmax0, RegisterBank::Context)); + if (!std::isfinite(depthExportMin) || !std::isfinite(depthExportMax) || depthExportMin > depthExportMax) { + throw std::runtime_error("AGC graphics: invalid viewport depth clamp bounds for depth export"); + } + } + } const bool sampleMaskExportEnable = ((shaderControl >> 8u) & 0x1u) != 0; const bool stencilOperationValue = !nullProgram && StencilOperationValueReplaces(context); const bool stencilRefExportEnable = ((shaderControl >> 1u) & 0x1u) != 0 || stencilOperationValue; @@ -176,6 +196,8 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con .linearCentroid = loaded(PixelInput::LinearCentroid), .pixelKillEnable = pixelKillEnable, .depthExportEnable = depthExportEnable, + .depthExportMin = depthExportMin, + .depthExportMax = depthExportMax, .sampleMaskExportEnable = sampleMaskExportEnable, .earlyZ = PixelEarlyZ(shaderControl), .executeOnNoop = ((shaderControl >> 10u) & 0x1u) != 0, diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 5ce3b19c54..8430b38d30 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1545,6 +1545,32 @@ bool StorageImageServesKeys(const StorageTexture& image, std::uint64_t dccAddres return !MetadataMoved(image, resource); } +std::shared_ptr RepeatStorageSurface(const Context& context, const GuestTextureResource& requested, const std::shared_ptr& stored) { + static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; + if (disabled || stored == nullptr) return nullptr; + const auto viewed = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers); + Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented"); + const auto viewedBytes = DescribeSurface(viewed).guestBytes; + RequireNoMultisampleTarget(viewed.baseAddress, static_cast(viewedBytes), "a storage image access"); + if (DepthSurfaceStorage(context, {}, viewed, 0) != nullptr) return nullptr; + if (DepthPlanesStale()) static_cast(FlushDepthPlanes(viewed.baseAddress, static_cast(viewedBytes), DepthPlaneScope::Overlapped)); + if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes(); + const bool profile = LookupOutcomes::Profiled(); + const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; + const auto resource = StorageSurface(context, viewed); + auto& cache = StorageTextures(); + std::lock_guard lock(cache.mutex); + const CacheHold held(storageCacheHeld); + if (resource.mipCount > AllocatedLevels(resource)) return nullptr; + const auto it = findStorageByImage(cache, context.device, stored.get()); + if (it == cache.entries.end() || MetadataMoved(*it->texture, resource)) return nullptr; + it->texture->Refresh(); + cache.entries.splice(cache.entries.begin(), cache.entries, it); + TextureCounts().storageHits.fetch_add(1, std::memory_order_relaxed); + if (profile) LookupOutcomes::Add(LookupOutcomes::StorageHit, start); + return it->texture; +} + namespace { const char* roleName(ShaderRecompiler::DescriptorRole role) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 6e16b45ea1..c4fb6bad61 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -437,8 +437,8 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint DepthTarget decodeDepthPlane(const Registers& cx) { const auto viewRegister = find(cx, 0x002); const auto view = viewRegister == cx.end() ? 0u : viewRegister->second; - const auto viewMip = (view >> 26u) & 0xfu; const auto maxMip = (read(cx, 0x010) >> 16u) & 0xfu; + const auto viewMip = maxMip == 0 ? 0u : (view >> 26u) & 0xfu; const auto zFormat = read(cx, 0x010) & 3u; const auto stencilRegister = find(cx, 0x011); const auto stencilInfo = stencilRegister == cx.end() ? 0u : stencilRegister->second; @@ -466,7 +466,6 @@ DepthTarget decodeDepthPlane(const Registers& cx) { } const auto size = read(cx, 0x007); depth.extent = {(size & 0x3fffu) + 1u, ((size >> 16u) & 0x3fffu) + 1u}; - if (maxMip == 0 && viewMip != 0) Require(false, "depth view mip " + std::to_string(viewMip) + " on a surface without mips"); if (maxMip != 0) { char message[320]; std::snprintf(message, sizeof(message), "depth view mip %u of %u at %ux%u (DB_Z_INFO 0x%08x, DB_DEPTH_VIEW 0x%08x)", viewMip, maxMip + 1u, depth.extent.width, depth.extent.height, read(cx, 0x010), view); @@ -1431,7 +1430,9 @@ void ApplyDepthRemap(const Context& context, State& state) { const auto& viewport = state.viewport; const bool inRange = viewport.minDepth >= 0 && viewport.minDepth <= 1 && viewport.maxDepth >= 0 && viewport.maxDepth <= 1; if (context.depthRangeUnrestricted || inRange) return; - Require(state.stages.path == ShaderPath::Vertex && !state.stages.mesh && !state.stages.tessellation, "viewport depth outside [0, 1] is remapped on the vertex path only"); + const bool vertex = state.stages.path == ShaderPath::Vertex && !state.stages.tessellation; + const bool tessellation = state.stages.path == ShaderPath::Tessellation && state.stages.tessellation.has_value(); + Require((vertex || tessellation) && !state.stages.mesh, "viewport depth outside [0, 1] is remapped on the vertex and tessellation paths only"); Require(context.depthClamp, "viewport depth outside [0, 1] needs depth clamping to stand in for the console's clamp"); const float clampMin = std::clamp(state.depthClampMin, 0.0f, 1.0f); const float clampMax = std::clamp(state.depthClampMax, 0.0f, 1.0f); @@ -2332,7 +2333,7 @@ std::optional DecodeColorMetadataPass(const QueueState& queue Require(target.samples == 1, "CB metadata pass over a multisampled color target, whose texels are on the host, is unsupported (only a CB resolve is modeled)"); } Require((read(cx, 0x31c + slot * 0xfu) & 0x10000000u) == 0 || target.dccAddress != 0, "CB metadata pass over a mipmapped DCC color target, whose keys are not modeled"); - if (target.cmaskAddress != 0 || target.dccAddress != 0) requireCovered(target); + if (target.dccAddress != 0 || (target.cmaskAddress != 0 && pass.mode != ColorMetadataPass::Mode::EliminateFastClear)) requireCovered(target); pass.targets.push_back(target); } return pass; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index cac265e81d..53ad0992b4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -21,6 +21,7 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include +#include #include #include #include @@ -354,9 +355,11 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex const auto& mips = geometry.mips; const auto arrayLayers = geometry.layers; const auto elementBytes = BytesPerElement(descriptor.format); +#ifdef APS5_DEBUG_LOG AGC_LOG_DEFERRED(stdout, "Texture address=0x%llx %ux%u mips=%u layers=%u dim=%d tile=%d format=%u vk=%d element=%u", static_cast(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, arrayLayers, static_cast(descriptor.dimension), static_cast(descriptor.tileMode), descriptor.format, static_cast(vkFormat), elementBytes); for (const auto& mip : mips) AGC_LOG_DEFERRED(stdout, " mip %ux%u tiled=0x%llx+0x%llx linear=0x%llx+0x%llx blocksPerRow=%u pitch=%u tail=%d", mip.width, mip.height, static_cast(mip.tiledOffset), static_cast(mip.tiledSize), static_cast(mip.linearOffset), static_cast(mip.linearSize), mip.blocksPerRow, mip.pitchBytes, mip.tail ? 1 : 0); +#endif const auto guestBytes = geometry.guestBytes; Require(snapshot.size() == guestBytes, "texture snapshot size mismatch"); @@ -642,7 +645,9 @@ Texture::Texture(const Context& context, const std::shared_ptr& copyLevels = descriptor.mipCount; copyLayers = geometry.imageLayers; copyElementBytes = BytesPerElement(descriptor.format); +#ifdef APS5_DEBUG_LOG AGC_LOG_DEFERRED(stdout, "Texture address=0x%llx %ux%u mips=%u layers=%u repacked copy of storage image (guest format %u -> vk %d)", static_cast(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, copyLayers, descriptor.format, static_cast(format)); +#endif VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; imageInfo.flags = descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u; @@ -701,7 +706,9 @@ Texture::Texture(const Context& context, const std::shared_ptr& copyLevels = descriptor.mipCount; copyLayers = geometry.imageLayers; copyElementBytes = BytesPerElement(descriptor.format); +#ifdef APS5_DEBUG_LOG AGC_LOG_DEFERRED(stdout, "Texture address=0x%llx %ux%u mips=%u layers=%u block copy of storage image (guest format %u -> vk %d)", static_cast(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, copyLayers, source->Descriptor().format, static_cast(blockFormat)); +#endif VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -759,7 +766,9 @@ Texture::Texture(const Context& context, const std::shared_ptr& copyLevels = descriptor.mipCount; copyLayers = geometry.imageLayers; copyElementBytes = BytesPerElement(descriptor.format); +#ifdef APS5_DEBUG_LOG AGC_LOG_DEFERRED(stdout, "Texture address=0x%llx %ux%u mips=%u layers=%u depth copy of storage image (vk=%d -> %d)", static_cast(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, copyLayers, static_cast(colorFormat), static_cast(depthFormat)); +#endif const bool depth1D = descriptor.dimension == TextureDimension::k1D || descriptor.dimension == TextureDimension::k1DArray; VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; @@ -4502,11 +4511,78 @@ bool StorageDumpSelects(const GuestTextureResource& descriptor) { return selector.width != 0 && descriptor.width == selector.width && descriptor.height == selector.height && descriptor.depthOrLastArray + 1 == selector.layers && (selector.anyFormat || descriptor.format == selector.format); } +bool StorageDumpReady() { + const char* gate = std::getenv("APS5_DUMP_STORAGE_GATE"); + std::error_code error; + return gate == nullptr || std::filesystem::exists(gate, error); +} + } void StorageTexture::FlushDumpSelected(const char* reason) { const auto& selector = StorageDumpSelection(); if (selector.address == 0 && selector.width == 0) return; + if (!StorageDumpReady()) return; + if (std::getenv("APS5_DUMP_STORAGE_GATE") != nullptr) { + std::lock_guard gpu(GuestMemory::GpuMutex()); + static std::vector captured; + std::vector> selected; + { + auto& live = Live(); + std::lock_guard lock(live.mutex); + for (auto* texture : live.textures) { + if (selected.size() + captured.size() >= 8) break; + if (!StorageDumpSelects(texture->descriptor) || texture->sliceLinearBytes * texture->arrayLayers > (8u << 20u)) continue; + if (std::find(captured.begin(), captured.end(), texture->descriptor.baseAddress) != captured.end()) continue; + if (auto alive = texture->weak_from_this().lock()) selected.push_back(std::move(alive)); + } + } + if (!selected.empty()) { + if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->Recording()) recorder->Submit(); + for (const auto& texture : selected) { + const auto& context = texture->context; + Check(context.Function("vkDeviceWaitIdle")(context.device), "private image readback wait"); + Buffer host(context, static_cast(texture->sliceLinearBytes * texture->arrayLayers), VK_BUFFER_USAGE_TRANSFER_DST_BIT); + CommandBatch batch(context); + const auto commands = batch.Handle(); + VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = texture->image; + barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, texture->descriptor.mipCount, 0, texture->geometry.imageLayers}; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + const auto regions = texture->CopyRegions(); + context.Function("vkCmdCopyImageToBuffer")(commands, texture->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, host.Handle(), static_cast(regions.size()), regions.data()); + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + const auto hostBarrier = WholeBufferBarrier(host.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1, &hostBarrier, 1, &barrier); + batch.SubmitAndWait(); + char name[80]; + std::snprintf(name, sizeof(name), "storage_direct_%llx_%zu.raw", static_cast(texture->descriptor.baseAddress), captured.size()); + std::FILE* file = std::fopen(name, "wb"); + Require(file != nullptr, "cannot open private image readback file"); + const auto& mip = texture->mips[0]; + const std::uint32_t header[3] = {mip.pitchBytes / static_cast(BytesPerElement(texture->descriptor.format)), mip.height, static_cast(texture->storageFormat)}; + bool written = std::fwrite(header, sizeof(header), 1, file) == 1; + for (std::uint32_t layer = 0; layer < texture->arrayLayers; ++layer) { + if (!texture->geometry.HasLayer(0, layer)) continue; + written = std::fwrite(host.Bytes().data() + texture->geometry.LinearLayerOffset(layer) + mip.linearOffset, 1, static_cast(mip.linearSize), file) == mip.linearSize && written; + } + const bool closed = std::fclose(file) == 0; + Require(written && closed, "cannot complete private image readback file"); + captured.push_back(texture->descriptor.baseAddress); + std::fprintf(stderr, "[storage-dump] direct=%s bytes=%llu\n", name, static_cast(mip.linearSize * texture->arrayLayers)); + } + } + return; + } std::vector> ranges; { auto& pending = Pending(); @@ -6287,7 +6363,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { detiler.Dispatch(commands, descriptor.tileMode, elementBytes, linear.Handle(), geometry.LinearLayerOffset(layer) + mip.linearOffset, tiled.Handle(), geometry.GuestLayerOffset(layer) + mip.tiledOffset, mip, true, layer, geometry.thick, {.pipeBankXor = descriptor.pipeBankXor}); } } - const bool dumpSelected = StorageDumpSelects(descriptor); + const bool dumpSelected = StorageDumpSelects(descriptor) && StorageDumpReady(); static int dumps = 0; std::unique_ptr dump; if (dumpSelected && dumps < 8) { @@ -6306,9 +6382,9 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 1, &toCopy, 1, &backToGeneral); CopyBuffer(context, commands, tiled.Handle(), 0, host.Handle(), 0, original.size()); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); - APS5_LOG_CHARS_OUT("StorageTexture writeback submit"); + APS5_LOG_CHARS_OUT_DEBUG("StorageTexture writeback submit"); batch.SubmitAndWait(); - APS5_LOG_CHARS_OUT("StorageTexture writeback done"); + APS5_LOG_CHARS_OUT_DEBUG("StorageTexture writeback done"); std::uint64_t storedBytes = 0; for (const auto& [from, to] : stores) storedBytes += to - from; countStorageWriteBack(drawn.rect ? storedBytes : guestBytes, false, drawn.drawn); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp index cd5212cf8f..e1c4285c92 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp @@ -378,12 +378,14 @@ std::size_t publishUnits(const std::shared_ptr& shadow, std::uint64_ std::lock_guard lock(registry.mutex); std::vector changed, cpu; changedUnits(*shadow, first, last, changed, cpu); + std::uint32_t ended = 0; for (auto unit = first; unit <= last; ++unit) { const auto k = static_cast(unit - first); if (!selected[k] || !shadow->Live(unit)) continue; if (changed[k] == GuestMemory::BlockWritten) { // Newer bytes reached the import since the retile: the slab's are dead. shadow->generation[unit] = 0; + ++ended; (cpu[k] != 0 ? Stats().staleDroppedCpu : Stats().staleDroppedDriver).fetch_add(1, std::memory_order_relaxed); continue; } @@ -391,6 +393,7 @@ std::size_t publishUnits(const std::shared_ptr& shadow, std::uint64_ const auto& slab = shadow->slabs[slabIndex]; if (slab == nullptr) { shadow->generation[unit] = 0; + ++ended; continue; } const auto [begin, end] = shadow->UnitRange(unit); @@ -405,11 +408,12 @@ std::size_t publishUnits(const std::shared_ptr& shadow, std::uint64_ ranges.emplace_back(begin, end); } shadow->published[unit] = 1; + ++ended; slab->lastUse = Recorder::Presents(); bytes += end - begin; ++units; } - shadow->Recount(); + shadow->liveUnits -= ended; } if (units == 0) return 0; Stats().published[static_cast(reason)].fetch_add(1, std::memory_order_relaxed); @@ -800,12 +804,12 @@ void MarkShadowed(const HostImport& import, std::span range ++lost; continue; } + if (!shadow->Live(unit)) ++shadow->liveUnits; shadow->generation[unit] = generation; shadow->published[unit] = 0; current->lastUse = present; } } - shadow->Recount(); } if (lost != 0) { Stats().slabLost.fetch_add(lost, std::memory_order_relaxed); diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp index 5f9dff1e4c..628aef3851 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp" #include #include @@ -10,7 +11,7 @@ extern "C" { int APS5_VABI sceAgcDriverSubmitAcb(uint32_t queue, const Packet* packet) { - if (queue < 0x20 || queue >= 0x58) { + if (queue < 0x20 || queue >= AgcDriver::DriverDetail::SubmissionQueueEnd) { throw std::runtime_error(std::string(__func__) + ": unsupported compute queue"); } AgcDriver::Submit(packet, queue); @@ -19,7 +20,7 @@ int APS5_VABI sceAgcDriverSubmitAcb(uint32_t queue, const Packet* packet) { int APS5_VABI sceAgcDriverSubmitMultiAcbs(uint32_t queue, uint32_t* const* acbs, const uint32_t* sizes_in_dwords, uint32_t count) { if (count == 0) return 0; - if (queue < 0x20 || queue >= 0x58) { + if (queue < 0x20 || queue >= AgcDriver::DriverDetail::UnmeasuredSubmissionQueueEnd) { throw std::runtime_error(std::string(__func__) + ": unsupported compute queue"); } return AgcDriver::SubmitMany(acbs, sizes_in_dwords, count, queue); diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp index 7b326f7f9d..a2c4534fdc 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp" #include #include @@ -15,7 +16,7 @@ extern "C" { int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet) { if (!queue_context || !packet) return EINVAL; const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); - if (queue != 0 && (queue < 0x20 || queue >= 0x58)) { + if (queue != 0 && (queue < 0x20 || queue >= AgcDriver::DriverDetail::UnmeasuredSubmissionQueueEnd)) { throw std::runtime_error(std::string(__func__) + ": unsupported queue"); } AgcDriver::Submit(packet, queue); @@ -26,7 +27,7 @@ int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_ 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)) { + if (queue != 0 && (queue < 0x20 || queue >= AgcDriver::DriverDetail::UnmeasuredSubmissionQueueEnd)) { 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/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 55a7cfe408..530932720e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -686,6 +686,8 @@ void importCoverageHintBudgetTests(const Context& context, const BdaTestAccess& Require(first != nullptr && first->base == a && first->bytes == ranges.bytes, "coverage budget fixture did not import its first range"); const auto firstBuffer = first->buffer; const auto firstSerial = HostImportSerial(ranges.importing, a, 16, false); + NoteHostImportFrame(); + NoteHostImportFrame(); for (int use = 0; use < 300; ++use) Require(HostImportFor(ranges.importing, d, 16) != nullptr, "coverage budget fixture did not import its hot range"); const auto hotSerial = HostImportSerial(ranges.importing, d, 16, false); Require(firstSerial != 0 && hotSerial != 0 && HostImportLiveBytes() == 2 * ranges.bytes, "coverage budget fixture has unexpected import ownership or usage"); @@ -733,6 +735,8 @@ void failedImportJoinProbeBudgetTests(const Context& context, const BdaTestAcces const auto* first = HostImportFor(ranges.importing, a, 16); Require(first != nullptr && first->base == a && first->bytes == ranges.bytes, "failed join budget fixture did not import its first range"); const auto firstSerial = HostImportSerial(ranges.importing, a, 16, false); + NoteHostImportFrame(); + NoteHostImportFrame(); for (int use = 0; use < 300; ++use) Require(HostImportFor(ranges.importing, d, 16) != nullptr, "failed join budget fixture did not import its hot range"); const auto hotSerial = HostImportSerial(ranges.importing, d, 16, false); Require(firstSerial != 0 && hotSerial != 0 && HostImportLiveBytes() == 2 * ranges.bytes, "failed join budget fixture has unexpected import ownership or usage"); @@ -1157,6 +1161,8 @@ void importAtMapOverBudgetTests(const Context& context) { leased.Upload(false); leased.WriteBack(); } + NoteHostImportFrame(); + NoteHostImportFrame(); const auto live = HostImportLiveBytes(); SetHostImportBudget(live + bytes - 1); change(mapped, true); @@ -1176,6 +1182,57 @@ void importAtMapOverBudgetTests(const Context& context) { for (auto* block : {kept, used, mapped}) ::operator delete(block, std::align_val_t{bytes}); } +// Three ranges under a budget for two, each used by a build of its own in every frame, with more lookups between the +// uses than a frame's worth of imports. The two imported first keep their imports and the third is refused at every +// frame, instead of evicting one of the others (which then evicts the next, every frame). A range unused for two frames +// is evicted for the third. +void importEvictionFramesTests(const Context& context) { + constexpr std::size_t bytes = 65536; + auto importing = context; + importing.hostImportAlignment = bytes; + std::array blocks{}; + std::array at{}; + const auto change = [&](void* block, bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, bytes, true, false, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + for (std::size_t index = 0; index < blocks.size(); ++index) { + blocks[index] = ::operator new(bytes, std::align_val_t{bytes}); + at[index] = reinterpret_cast(blocks[index]); + change(blocks[index], true); + } + SetHostImportBudget(HostImportLiveBytes() + 2 * bytes); + const auto use = [&](std::size_t index) { + GuestBufferMemory leased(importing); + leased.AddReadable(at[index], 32); + leased.Upload(false); + leased.WriteBack(); + for (int lookup = 0; lookup < 300; ++lookup) static_cast(HostImportFor(importing, at[index], 16)); + return HostImportSerial(importing, at[index], 16, false); + }; + NoteHostImportFrame(); + NoteHostImportFrame(); + std::array serials{use(0), use(1)}; + Require(serials[0] != 0 && serials[1] != 0, "two ranges under a budget for two were not imported"); + const auto epoch = HostImportEpoch(); + NoteHostImportFrame(); + Require(HostImportEpoch() == epoch, "a frame end with no import change moved the imports epoch"); + for (int frame = 0; frame < 10; ++frame) { + Require(use(2) == 0, "a range over the budget evicted an import used in the same frame (frame " + std::to_string(frame) + ")"); + NoteHostImportFrame(); + Require(use(0) == serials[0] && use(1) == serials[1], "an import used in every frame was imported again (frame " + std::to_string(frame) + ")"); + } + NoteHostImportFrame(); + Require(use(1) == serials[1] && use(2) == 0, "a range over the budget evicted an import used in the previous frame"); + NoteHostImportFrame(); + Require(use(1) == serials[1] && use(2) != 0 && !HostImportCovers(importing, at[0], 16), "an import unused for two frames was not evicted for a range over the budget"); + SetHostImportBudget(0); + for (auto* block : blocks) change(block, false); + for (auto* block : blocks) ::operator delete(block, std::align_val_t{bytes}); +} + void importRequesterTests(const Context& context, const BdaTestAccess& access) { ClearHostImports(context.device); constexpr std::size_t piece = 65536; @@ -1669,6 +1726,8 @@ void evictingRefusalMemoryTests(const Context& context) { } before = counted(); Require(HostImportFor(importing, address(filled + 2), 16) == nullptr && counted().remembered == before.remembered + 1, "a remembered evicting refusal ended when a build raised the floor while no import could age out of its protection yet"); + NoteHostImportFrame(); + NoteHostImportFrame(); for (int use = 0; use < 200; ++use) Require(HostImportFor(importing, address(filled + 1), 16) != nullptr, "a live import was not found"); before = counted(); Require(HostImportFor(importing, address(filled + 2), 16) != nullptr && counted().remembered == before.remembered, "an evicting request was answered from its remembered refusal after the imports aged out of their protection"); @@ -1705,6 +1764,8 @@ void evictingRefusalAgingTests(const Context& context) { static_cast(HostImportFor(importing, address(filled), 16)); auto before = counted(); Require(HostImportFor(importing, address(filled), 16) == nullptr && counted().remembered == before.remembered + 1, "a refusal while the imports were recent but no longer held by a build was not remembered"); + NoteHostImportFrame(); + NoteHostImportFrame(); for (int use = 0; use < 200; ++use) Require(HostImportFor(importing, address(filled - 1), 16) != nullptr, "a live import was not found"); before = counted(); Require(HostImportFor(importing, address(filled), 16) != nullptr && counted().remembered == before.remembered, "an evicting request was answered from its remembered refusal after imports no build held aged out of their protection"); @@ -1868,6 +1929,42 @@ void importDeviceBudgetTests() { Require(HostImportBudgetWith(5888 * MiB, false, 0, true, 0) != 0, "a host heap report that leaves imports no room made the default import budget 0, which the refusal memory reads as no budget"); } +void importAllocationErrorTests(const Context& context, VkResult failure, bool nearBudget) { + constexpr std::size_t piece = 65536; + constexpr std::size_t count = 8; + auto* block = static_cast(::operator new(2 * count * piece, std::align_val_t{piece})); + auto importing = context; + importing.hostImportAlignment = piece; + ImportAllocationProbe probe(importing); + SetHostImportWindow(0); + ClearHostImports(importing.device); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) mutation.Add(block + 2 * index * piece, piece, true, true, true); + } + SetHostImportBudget((nearBudget ? 2 : 64) * piece); + std::size_t next = 0; + const auto lookup = [&] { return HostImportFor(importing, reinterpret_cast(block + 2 * next++ * piece), 16) != nullptr; }; + const auto counted = [] { return HostImportCounters().requesters[static_cast(ImportRequester::Access)]; }; + Require(lookup(), "the allocation error test's first import was not made"); + probe.failure = failure; + const auto before = counted(); + for (int attempt = 0; attempt < 3; ++attempt) Require(!lookup(), "an import with an injected allocation failure was made"); + const auto after = counted(); + probe.failure = VK_SUCCESS; + const bool exhausted = failure == VK_ERROR_OUT_OF_DEVICE_MEMORY || failure == VK_ERROR_OUT_OF_HOST_MEMORY; + const auto failed = exhausted && nearBudget ? 1u : 3u; + Require(after.failed - before.failed == failed && after.refused - before.refused == 3 - failed, "allocation error " + std::to_string(failure) + " changed the import failure/refusal counts"); + Require(lookup() != exhausted, "allocation error " + std::to_string(failure) + " incorrectly changed the host import budget"); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) mutation.Remove(block + 2 * index * piece); + } + ClearHostImports(importing.device); + SetHostImportBudget(0); + ::operator delete(block, std::align_val_t{piece}); +} + void importCeilingTests(const Context& context, const BdaTestAccess& access, bool nearBudget) { constexpr std::size_t piece = 65536; constexpr std::size_t count = 24; @@ -1931,7 +2028,7 @@ void VKAPI_PTR reportHostHeap(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties } } -void importReportFloorTests(const Context& context, const BdaTestAccess& access, bool setBudget) { +void importReportFloorTests(const Context& context, bool setBudget, VkResult failure) { constexpr std::size_t piece = 65536; constexpr std::size_t count = 24; constexpr std::uint64_t GiB = std::uint64_t{1} << 30u; @@ -1939,6 +2036,7 @@ void importReportFloorTests(const Context& context, const BdaTestAccess& access, auto* block = static_cast(::operator new(2 * count * piece, std::align_val_t{piece})); auto importing = context; importing.hostImportAlignment = piece; + ImportAllocationProbe probe(importing); importing.memory.memoryHeapCount = 2; importing.memory.memoryHeaps[0] = {8 * GiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; importing.memory.memoryHeaps[1] = {16 * GiB, 0}; @@ -1957,11 +2055,10 @@ void importReportFloorTests(const Context& context, const BdaTestAccess& access, const auto lookup = [&] { return HostImportFor(importing, reinterpret_cast(block + 2 * next++ * piece), 16) != nullptr; }; const std::string budget = setBudget ? "a set import budget" : "the default import budget"; for (int made = 0; made < 3; ++made) Require(lookup(), budget + " was lowered below " + std::to_string(made + 1) + " imports by a host heap report that leaves imports no room, before any vkAllocateMemory failure"); - access.limitImports(0); - Require(!lookup(), "an import past the mock's import limit was made"); - access.limitImports(std::nullopt); - if (setBudget) Require(lookup(), "a set import budget took the host heap report's limit after a vkAllocateMemory failure far below it"); - else Require(!lookup(), "the default import budget did not take the host heap report's limit after a vkAllocateMemory failure"); + probe.failure = failure; + Require(!lookup(), "an import with an injected allocation failure was made"); + probe.failure = VK_SUCCESS; + Require(lookup() == setBudget, budget + " incorrectly applied the host heap report after allocation error " + std::to_string(failure)); SetHostImportBudget(0); { GuestAllocations::Mutation mutation; @@ -3556,7 +3653,7 @@ void RunHeapMirrorRunUnwatchTests(const Context& context, const BdaTestAccess& a std::cout << "an address-based build refreshes the unwatched heap of a run in full and its watched neighbour by its writes\n"; } -void RunAddressCopyCapTests(const Context& context, const BdaTestAccess& access, bool remembered) { +void RunAddressCopyCapTests(const Context& context, const BdaTestAccess& access, bool remembered, int frames) { constexpr std::size_t block = 65536; constexpr std::size_t heapBytes = 32 * block; constexpr std::size_t idleBytes = 16 * block; @@ -3651,6 +3748,7 @@ void RunAddressCopyCapTests(const Context& context, const BdaTestAccess& access, SetAddressCopyLimit(0); unrelated(true); } + for (int frame = 0; frame < frames; ++frame) NoteHostImportFrame(); for (int use = 0; use < 300; ++use) Require(HostImportFor(importing, busy, 64) != nullptr, "the busy range lost its import"); if (!remembered) unwatch(); const auto refused = builds(); @@ -4288,10 +4386,17 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importDeviceBudgetTests(); importCeilingTests(context, access, false); importCeilingTests(context, access, true); - importReportFloorTests(context, access, true); - importReportFloorTests(context, access, false); + for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_UNKNOWN}) { + importAllocationErrorTests(context, failure, false); + importAllocationErrorTests(context, failure, true); + } + for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_UNKNOWN}) { + importReportFloorTests(context, true, failure); + importReportFloorTests(context, false, failure); + } importReportRefusalMemoryTests(context, access); sparseImportedHeapTests(context, access); + importEvictionFramesTests(context); heapMirrorTests(context, access); partialHeapImportBudgetTests(context, access); importedHeapMirrorTests(context, access); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp index 7479c1f4b1..3ea90d15c6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp @@ -21,10 +21,11 @@ void RunHeapMirrorUnwatchTests(const AgcDriver::Graphics::Context& context, cons void RunSparseHeapMirrorUnwatchTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access); void RunSparseHeapMirrorBudgetTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access); void RunHeapMirrorRunUnwatchTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access); -void RunAddressCopyCapTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access, bool remembered); +void RunAddressCopyCapTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access, bool remembered, int frames); void RunGuestAllocationTests(); void RunGuestLeaseWaitTests(); void RunUnmappedGapTests(); +void RunVanishedPageReadTests(); void RunColorTargetLayoutTests(); void RunLiveStackAccessTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp index 62ce062d8c..fcdf6ea91e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp @@ -414,7 +414,7 @@ void Budget() { Expect(MemoryOf(probe.Handle()) != first, "the least recently used device buffer survived the eviction"); } -void AddressAndHostUnchanged() { +void AddressAndHostClasses() { mock = MockDevice{}; auto context = mockContext(); VkDeviceMemory scratch = VK_NULL_HANDLE; @@ -426,17 +426,23 @@ void AddressAndHostUnchanged() { Expect(MemoryOf(addressed.Handle()) != scratch && addressed.DeviceAddress() != 0, "an addressable device-local buffer took a scratch buffer without a device address"); Buffer shadow(context, 2 * MiB - 256, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); Expect(MemoryOf(shadow.Handle()) == scratch, "a device-local staging buffer did not share the scratch buffers' class"); - VkBuffer host = VK_NULL_HANDLE; + VkDeviceMemory host = VK_NULL_HANDLE; { Buffer copy(context, 10 * MiB + 4096, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, HostVisible); - host = copy.Handle(); - Expect(mock.sizes.at(host) == 10 * MiB + 4096, "a large host buffer was rounded to a class"); - Expect(mock.usages.at(host) == VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "a host buffer was created with more usages than asked"); + host = MemoryOf(copy.Handle()); + Expect(mock.sizes.at(copy.Handle()) == 10 * MiB + 4096, "a 10 MiB + 4 KiB host buffer was created with " + std::to_string(mock.sizes.at(copy.Handle())) + " bytes, not the requested size"); + Expect(ClassOf(copy.Handle()) == 11 * MiB, "a 10 MiB + 4 KiB host buffer has " + std::to_string(ClassOf(copy.Handle())) + " bytes of memory, not its 11 MiB class"); + Expect(mock.usages.at(copy.Handle()) == VK_BUFFER_USAGE_TRANSFER_SRC_BIT, "a host buffer was created with more usages than asked"); + Expect(copy.Bytes().size() == 10 * MiB + 4096, "a host buffer maps its class instead of the requested size"); } - Buffer other(context, 10 * MiB + 8192, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, HostVisible); - Expect(other.Handle() != host, "a large host buffer of another exact size was reused"); - Buffer same(context, 10 * MiB + 4096, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, HostVisible); - Expect(same.Handle() == host, "a large host buffer of the same exact size was not reused"); + const auto made = mock.allocations; + { + Buffer other(context, 10 * MiB + 8192, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, HostVisible); + Expect(MemoryOf(other.Handle()) == host && mock.allocations == made, "a retained 11 MiB host buffer did not serve another size in its class"); + Expect(mock.sizes.at(other.Handle()) == 10 * MiB + 8192 && other.Bytes().size() == 10 * MiB + 8192, "a reused host buffer kept the size of the earlier one"); + } + Buffer larger(context, 11 * MiB + 4096, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, HostVisible); + Expect(MemoryOf(larger.Handle()) != host && mock.allocations == made + 1, "a host buffer was served by a retained buffer of a smaller class"); } void SmallDeviceClasses() { @@ -1198,7 +1204,7 @@ int main(int argc, char** argv) { KeptUntilFence(); SmallDeviceRetention(); Budget(); - AddressAndHostUnchanged(); + AddressAndHostClasses(); SmallDeviceClasses(); SlabMetadataFailure(); SlabMapFailure(); diff --git a/core/libs/prx/libSceAgcDriver/tests/ComputePipelineInspectRun.cmake b/core/libs/prx/libSceAgcDriver/tests/ComputePipelineInspectRun.cmake new file mode 100644 index 0000000000..61a2b8d0c6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/ComputePipelineInspectRun.cmake @@ -0,0 +1,84 @@ +if(NOT DEFINED REPLAY OR NOT DEFINED OUTPUT_ROOT) + message(FATAL_ERROR "Missing replay tool or output root") +endif() +string(RANDOM LENGTH 12 ALPHABET 0123456789abcdef runId) +set(runDirectory "${OUTPUT_ROOT}/compute-inspect-${runId}") +file(MAKE_DIRECTORY "${runDirectory}") +if(NOT DEFINED ENV{ANYPS5_REQUIRE_VULKAN} AND DEFINED VULKAN_PROBE) + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env "APS5_PIPELINE_STATS=0" "${VULKAN_PROBE}" + WORKING_DIRECTORY "${runDirectory}" + RESULT_VARIABLE probeResult OUTPUT_VARIABLE probeOutput ERROR_VARIABLE probeError TIMEOUT 20 + ) + file(WRITE "${runDirectory}/vulkan-probe.log" "${probeOutput}\n${probeError}") + if(probeResult STREQUAL "77") + message(STATUS "compute pipeline inspection: skipped, no usable Vulkan device") + return() + endif() +endif() +set(report "${runDirectory}/inspection.json") +execute_process( + COMMAND "${CMAKE_COMMAND}" -E env "APS5_PIPELINE_STATS=1" "${REPLAY}" --fixture --inspect --output "${report}" + WORKING_DIRECTORY "${runDirectory}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error TIMEOUT 20 +) +file(WRITE "${runDirectory}/inspection.log" "${output}\n${error}") +message("${error}") +if(error MATCHES "APS5_PIPELINE_STATS requires VK_KHR_pipeline_executable_properties") + message(STATUS "compute pipeline inspection: skipped, the device reports no executable statistics") + return() +endif() +if(NOT result STREQUAL "0" OR NOT EXISTS "${report}") + message(FATAL_ERROR "Inspection failed: ${result}\n${output}\n${error}") +endif() +file(READ "${report}" inspection) +string(JSON mode GET "${inspection}" mode) +string(JSON statistics GET "${inspection}" pipeline_statistics) +string(JSON timingType TYPE "${inspection}" milliseconds) +string(JSON flags GET "${inspection}" replay_pipeline_flags) +string(JSON dispatches GET "${inspection}" dispatch_count) +math(EXPR statisticsFlag "${flags} & 64") +if(NOT mode STREQUAL "inspection" OR NOT statistics OR NOT timingType STREQUAL "NULL" OR statisticsFlag EQUAL 0 OR NOT dispatches EQUAL 0) + message(FATAL_ERROR "Inspection report mixes counters and benchmark data: ${inspection}") +endif() +if(NOT error MATCHES "\\[pipeline-stats\\] executable=0 stages=") + message(FATAL_ERROR "Inspection did not query an executable: ${error}") +endif() +set(normalReport "${runDirectory}/normal.json") +execute_process( + COMMAND "${CMAKE_COMMAND}" -E env "APS5_PIPELINE_STATS=0" "${REPLAY}" --fixture --output "${normalReport}" + WORKING_DIRECTORY "${runDirectory}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error TIMEOUT 20 +) +file(WRITE "${runDirectory}/normal.log" "${output}\n${error}") +message("${error}") +if(NOT result STREQUAL "0" OR NOT EXISTS "${normalReport}") + message(FATAL_ERROR "Normal replay failed: ${result}\n${output}\n${error}") +endif() +file(READ "${normalReport}" normal) +string(JSON mode GET "${normal}" mode) +string(JSON statistics GET "${normal}" pipeline_statistics) +string(JSON timingType TYPE "${normal}" milliseconds) +string(JSON originalIdentity GET "${inspection}" capture_identity) +string(JSON normalIdentity GET "${normal}" capture_identity) +string(JSON originalFlags GET "${inspection}" pipeline_flags) +string(JSON normalFlags GET "${normal}" pipeline_flags) +if(NOT mode STREQUAL "benchmark" OR statistics OR NOT timingType STREQUAL "OBJECT" OR NOT originalIdentity STREQUAL normalIdentity OR NOT originalFlags EQUAL normalFlags) + message(FATAL_ERROR "Replay mode changed the input identity or benchmark report") +endif() +function(expectFailure name statistics expected) + execute_process( + COMMAND "${CMAKE_COMMAND}" -E env "APS5_PIPELINE_STATS=${statistics}" "${REPLAY}" ${ARGN} + WORKING_DIRECTORY "${runDirectory}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error TIMEOUT 10 + ) + file(WRITE "${runDirectory}/${name}.log" "${output}\n${error}") + if(result STREQUAL "0" OR NOT error MATCHES "${expected}") + message(FATAL_ERROR "${name} was not rejected explicitly: ${result}\n${output}\n${error}") + endif() +endfunction() +expectFailure(benchmark-guard 1 "unset APS5_PIPELINE_STATS before benchmarking" --fixture) +expectFailure(missing-statistics 0 "requires APS5_PIPELINE_STATS=1" --fixture --inspect) +expectFailure(repeated-mode 1 "unknown or repeated argument" --fixture --inspect --inspect) +expectFailure(fixture-options 1 "cannot be combined with replay options" --write-fixture "${runDirectory}/fixture" --inspect) +message(STATUS "compute pipeline inspection: counters queried, input identity retained, timings separated and benchmark guards preserved") diff --git a/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp b/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp index d7bac76983..5e2fc66ef1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp @@ -210,6 +210,8 @@ enum class QuadMask { Nested, SeparateHalves, RealOnly, + RealPartial, + RealDivergent, Partial, WrongHalf, Divergent, @@ -225,7 +227,7 @@ bool quadProjection(std::uint32_t wave, QuadMask shape, bool expected, IrShaderS IrBuilder builder(program); builder.SetInsertionPoint(block); auto& lane = builder.Emit(IrOpcode::LaneId, IrType::U32, {}); - if (shape == QuadMask::Divergent) { + if (shape == QuadMask::Divergent || shape == QuadMask::RealDivergent) { auto& taken = program.CreateBlock(); auto& other = program.CreateBlock(); program.BlockOrder().push_back(&taken); @@ -245,17 +247,18 @@ bool quadProjection(std::uint32_t wave, QuadMask shape, bool expected, IrShaderS return std::array{&builder.CompositeExtract(mask, 0u), &builder.CompositeExtract(mask, 1u)}; }; auto& helper = builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::HelperInvocation)), &builder.Constant(0u)}); - const auto real = ballot(builder.IEqual(helper, builder.Constant(0u))); + auto& realCondition = builder.IEqual(helper, builder.Constant(0u)); + const auto real = ballot(realCondition); const auto quads = expand(real); const auto host = expand(ballot(builder.ConstantBool(true))); auto words = quads; - if (shape == QuadMask::Intersected || shape == QuadMask::Nested || shape == QuadMask::SeparateHalves || shape == QuadMask::RealOnly) { + if (shape == QuadMask::Intersected || shape == QuadMask::Nested || shape == QuadMask::SeparateHalves || shape == QuadMask::RealOnly || shape == QuadMask::RealPartial || shape == QuadMask::RealDivergent) { const auto highHost = shape == QuadMask::SeparateHalves ? expand(ballot(builder.ConstantBool(true))) : host; - const auto source = shape == QuadMask::Nested || shape == QuadMask::RealOnly ? real : quads; + const auto source = shape == QuadMask::Nested || shape == QuadMask::RealOnly || shape == QuadMask::RealPartial || shape == QuadMask::RealDivergent ? real : quads; words = {&builder.BitwiseAnd(*source[0], *host[0]), &builder.BitwiseAnd(*source[1], *highHost[1])}; if (shape == QuadMask::Nested) words = expand(words); } - if (shape == QuadMask::Partial) { + if (shape == QuadMask::Partial || shape == QuadMask::RealPartial) { words = {&builder.BitwiseAnd(*words[0], builder.Constant(0x0000ffffu)), &builder.Constant(0u)}; } if (shape == QuadMask::WrongHalf) words[1] = words[0]; @@ -266,7 +269,8 @@ bool quadProjection(std::uint32_t wave, QuadMask shape, bool expected, IrShaderS static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); ConstantFolder{}.Fold(program); const auto* result = user.Argument(0)->Resolve(); - return expected ? result->HasImmediate() && result->ImmediateBool() : !result->HasImmediate(); + if (shape == QuadMask::RealOnly) return result == &realCondition; + return expected ? result->HasImmediate() && result->ImmediateBool() : result->Opcode() == IrOpcode::INotEqual32; } bool run(const char* name, bool passed) { @@ -284,8 +288,10 @@ int main() { passed &= run("intersecting whole-quad entry masks preserves pixel helpers", quadProjection(wave, QuadMask::Intersected, true)); passed &= run("a nested whole-quad entry mask covers pixel helpers", quadProjection(wave, QuadMask::Nested, true)); passed &= run("independent low and high whole-quad masks cover pixel helpers", quadProjection(wave, QuadMask::SeparateHalves, true)); - passed &= run("an unexpanded entry mask must keep the helper bit test", quadProjection(wave, QuadMask::RealOnly, false)); + passed &= run("an unexpanded entry mask must reuse the original helper test", quadProjection(wave, QuadMask::RealOnly, false)); passed &= run("a partial whole-quad mask must keep the lane bit test", quadProjection(wave, QuadMask::Partial, false)); + passed &= run("a partial real-lane mask must keep the lane bit test", quadProjection(wave, QuadMask::RealPartial, false)); + passed &= run("a divergent real-lane ballot must keep the lane bit test", quadProjection(wave, QuadMask::RealDivergent, false)); passed &= run("a divergent whole-quad ballot must keep the lane bit test", quadProjection(wave, QuadMask::Divergent, false)); passed &= run("whole-quad entry proofs do not apply to other stages", quadProjection(wave, QuadMask::Intersected, false, IrShaderStage::Vertex)); } diff --git a/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp b/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp index 7873086b80..c366f09d2a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DeferredDestruction.cpp @@ -51,6 +51,16 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) { return nullptr; } +VKAPI_ATTR VkResult VKAPI_CALL reloadedQueueWaitIdle(VkQueue queue) { + events.push_back(Name("reloaded idle ", queue)); + return VK_SUCCESS; +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL reloadedProc(VkDevice device, const char* name) { + if (std::strcmp(name, "vkQueueWaitIdle") == 0) return reinterpret_cast(&reloadedQueueWaitIdle); + return mockProc(device, name); +} + Context ContextFor(VkDevice device, PFN_vkGetDeviceProcAddr proc) { Context context{}; context.device = device; @@ -115,12 +125,36 @@ void OtherDevicesDestroyAtOnce() { DestroyDeferredObjects(device); } +PFN_vkGetDeviceProcAddr TrySetUp(VkDevice device, PFN_vkGetDeviceProcAddr resolver) { + try { + return DeferDestructionToIdle(device, resolver); + } catch (const std::exception&) { + return nullptr; + } +} + +void ALaterDeviceUsesItsOwnResolver() { + events.clear(); + const auto first = Handle(0x103); + const auto firstProc = DeferDestructionToIdle(first, mockProc); + DestroyDeferredObjects(first); + const auto second = Handle(0x104); + const auto proc = TrySetUp(second, reloadedProc); + Expect(proc != nullptr, "a device set up after the last one went could not use a resolver from a reloaded Vulkan loader"); + if (proc == nullptr) return; + Expect(ContextFor(second, proc).Function("vkQueueWaitIdle") == &reloadedQueueWaitIdle, "a later device did not pass through to its own resolver"); + Expect(reinterpret_cast(firstProc(first, "vkQueueWaitIdle")) == &mockQueueWaitIdle, "an earlier device's wrapper passed through to a later device's resolver"); + Expect(TrySetUp(Handle(0x105), mockProc) == nullptr, "two live devices with different resolvers were accepted"); + DestroyDeferredObjects(second); +} + } int main() { try { DeferredUntilTheQueueIsIdle(); OtherDevicesDestroyAtOnce(); + ALaterDeviceUsesItsOwnResolver(); } catch (const std::exception& error) { std::fprintf(stderr, "FAIL: %s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp b/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp index 8452a2ae1a..257d5b4987 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DepthRangeRemap.cpp @@ -75,11 +75,13 @@ ModuleFacts inspect(const ShaderRecompiler::RecompileResult& result) { } constexpr std::array PositionExport{0xf80008cfu, 0u, 0xbf810000u}; +constexpr std::array DomainPositionExport{0xf8000941u, 0u, 0xf80008cfu, 0x06050605u, 0xbf810000u}; -ShaderRecompiler::RecompileRequest baseRequest(bool remap) { +ShaderRecompiler::RecompileRequest baseRequest(bool remap, ShaderRecompiler::ShaderStage stage = ShaderRecompiler::ShaderStage::Vertex) { using namespace ShaderRecompiler; RecompileRequest request{}; - request.shader = {ShaderStage::Vertex, 0x10000u, PositionExport, 0, {}}; + const auto code = stage == ShaderStage::TessellationEvaluation ? std::span(DomainPositionExport) : std::span(PositionExport); + request.shader = {stage, 0x10000u, code, 0, {}}; request.context.waveSize = 64; ShaderVertexStageInfo vertex{}; vertex.depthRemapMode = 2u; @@ -87,9 +89,13 @@ ShaderRecompiler::RecompileRequest baseRequest(bool remap) { request.target.vulkanVersion = 0x00401000u; request.target.spirvVersion = 0x00010300u; request.target.subgroupSize = 64; - static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityClipDistance}; + static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityClipDistance, spv::CapabilityTessellation}; request.target.supportedCapabilities = capabilities; request.target.depthRangeRemap = remap; + if (stage == ShaderStage::TessellationEvaluation) { + request.target.tessellation = TessellationTargetLimits{32u, 128u, 128u, 128u, 4096u, 128u, 128u}; + request.graphics = GraphicsCompileContext{0u, {}, std::nullopt, TessellationConfiguration{3u, 3u, 1u, 2u, 2u}, {0u, 3u, 0u, 1u}}; + } request.layout.pushConstantSizeBytes = ShaderRecompiler::DepthRemapPushOffsetBytes; request.useCache = false; return request; @@ -99,9 +105,9 @@ void require(bool condition, const char* what) { if (!condition) throw std::runtime_error(what); } -void preparedTransformTests() { +void preparedTransformTests(ShaderRecompiler::ShaderStage stage) { using namespace ShaderRecompiler; - auto request = baseRequest(true); + auto request = baseRequest(true, stage); request.useCache = true; const auto handle = PrepareShader(request); const auto materialize = [&](std::uint32_t mode) { @@ -120,15 +126,15 @@ void preparedTransformTests() { require(!PreparedShaderInvocation::TryCreate(other, handle).has_value(), "a prepared vertex shader matched a request of another depth remap mode"); } -void pushedTransformTests() { +void pushedTransformTests(ShaderRecompiler::ShaderStage stage) { using namespace ShaderRecompiler; - const auto remapped = Recompile(baseRequest(true)); - const auto plain = Recompile(baseRequest(false)); + const auto remapped = Recompile(baseRequest(true, stage)); + const auto plain = Recompile(baseRequest(false, stage)); const auto facts = inspect(remapped); require(!facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 1u) && !facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 2u), "the depth remap scale or offset is a specialization constant"); require(facts.pushConstantVariable && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u) && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u + 1u), "the remapped vertex shader does not read the scale and offset from the push block"); require(!inspect(plain).pushConstantVariable, "a vertex shader without a depth remap gained a push constant block"); - auto crowded = baseRequest(true); + auto crowded = baseRequest(true, stage); crowded.layout.pushConstantSizeBytes = DepthRemapPushOffsetBytes + 4u; bool refused = false; try { @@ -139,18 +145,41 @@ void pushedTransformTests() { require(refused, "a remapped vertex shader with push data over the depth remap words was accepted"); } +void clipDistanceCapacityTests(ShaderRecompiler::ShaderStage stage) { + using namespace ShaderRecompiler; + static constexpr std::array vertex{0xf80008cfu, 0u, 0xf80008dfu, 0u, 0xf80008efu, 0u, 0xbf810000u}; + static constexpr std::array domain{0xf8000941u, 0u, 0xf80008cfu, 0x05050505u, 0xf80008dfu, 0x05050505u, 0xf80008efu, 0x05050505u, 0xbf810000u}; + auto request = baseRequest(true, stage); + request.shader.code = stage == ShaderStage::TessellationEvaluation ? std::span(domain) : std::span(vertex); + request.context.vertex->paClVsOutCntl = 0xc0003fu; + require(inspect(Recompile(request)).clipDistanceLength == 8u, "the remap did not preserve six guest clip distances beside its two planes"); + for (const auto control : {0xc0007fu, 0xc0403fu, 0xc000ffu}) { + request.context.vertex->paClVsOutCntl = control; + bool refused = false; + try { + static_cast(Recompile(request)); + } catch (const std::runtime_error& error) { + refused = std::string(error.what()).find("two free clip-distance components") != std::string::npos; + } + require(refused, "a remap that exceeds the combined clip and cull distance capacity was accepted"); + } +} + } int main() { using namespace ShaderRecompiler; try { - const auto remapped = inspect(Recompile(baseRequest(true))); - const auto plain = inspect(Recompile(baseRequest(false))); - require(remapped.clipDistanceBuiltIn && remapped.clipDistanceLength == plain.clipDistanceLength + 2u, "the remap did not add its two clip distances"); - require(remapped.multiplies >= plain.multiplies + 2u, "the remap does not scale the clip z and offset it by w"); - require(!plain.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !remapped.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 3u), "unexpected specialization constants"); - preparedTransformTests(); - pushedTransformTests(); + for (const auto stage : {ShaderStage::Vertex, ShaderStage::TessellationEvaluation}) { + const auto remapped = inspect(Recompile(baseRequest(true, stage))); + const auto plain = inspect(Recompile(baseRequest(false, stage))); + require(remapped.clipDistanceBuiltIn && remapped.clipDistanceLength == plain.clipDistanceLength + 2u, "the remap did not add its two clip distances"); + require(remapped.multiplies >= plain.multiplies + 2u, "the remap does not scale the clip z and offset it by w"); + require(!plain.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !remapped.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 3u), "unexpected specialization constants"); + preparedTransformTests(stage); + pushedTransformTests(stage); + clipDistanceCapacityTests(stage); + } std::cout << "Depth range remap tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp index cd876ad472..47bf9f8ca4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include #include #include @@ -158,6 +159,21 @@ VKAPI_ATTR VkResult VKAPI_CALL mapMemory(VkDevice, VkDeviceMemory, VkDeviceSize, return VK_ERROR_MEMORY_MAP_FAILED; } VKAPI_ATTR void VKAPI_CALL unmapMemory(VkDevice, VkDeviceMemory) {} +VKAPI_ATTR VkResult VKAPI_CALL createDescriptorSetLayout(VkDevice, const VkDescriptorSetLayoutCreateInfo*, const VkAllocationCallbacks*, VkDescriptorSetLayout* layout) { + *layout = newHandle(); + return VK_SUCCESS; +} +VKAPI_ATTR VkResult VKAPI_CALL createPipelineLayout(VkDevice, const VkPipelineLayoutCreateInfo*, const VkAllocationCallbacks*, VkPipelineLayout* layout) { + *layout = newHandle(); + return VK_SUCCESS; +} +VKAPI_ATTR VkResult VKAPI_CALL createShaderModule(VkDevice, const VkShaderModuleCreateInfo*, const VkAllocationCallbacks*, VkShaderModule* module) { + *module = newHandle(); + return VK_SUCCESS; +} +VKAPI_ATTR void VKAPI_CALL destroyDescriptorSetLayout(VkDevice, VkDescriptorSetLayout, const VkAllocationCallbacks*) {} +VKAPI_ATTR void VKAPI_CALL destroyPipelineLayout(VkDevice, VkPipelineLayout, const VkAllocationCallbacks*) {} +VKAPI_ATTR void VKAPI_CALL destroyShaderModule(VkDevice, VkShaderModule, const VkAllocationCallbacks*) {} VKAPI_ATTR void VKAPI_CALL formatProperties(VkPhysicalDevice, VkFormat, VkFormatProperties* properties) { *properties = {}; @@ -216,7 +232,13 @@ PFN_vkVoidFunction VKAPI_CALL deviceProc(VkDevice, const char* name) { {"vkBindBufferMemory", reinterpret_cast(bindBufferMemory)}, {"vkDestroyBuffer", reinterpret_cast(destroyBuffer)}, {"vkMapMemory", reinterpret_cast(mapMemory)}, - {"vkUnmapMemory", reinterpret_cast(unmapMemory)} + {"vkUnmapMemory", reinterpret_cast(unmapMemory)}, + {"vkCreateDescriptorSetLayout", reinterpret_cast(createDescriptorSetLayout)}, + {"vkCreatePipelineLayout", reinterpret_cast(createPipelineLayout)}, + {"vkCreateShaderModule", reinterpret_cast(createShaderModule)}, + {"vkDestroyDescriptorSetLayout", reinterpret_cast(destroyDescriptorSetLayout)}, + {"vkDestroyPipelineLayout", reinterpret_cast(destroyPipelineLayout)}, + {"vkDestroyShaderModule", reinterpret_cast(destroyShaderModule)} }; const auto it = table.find(name); return it == table.end() ? nullptr : it->second; @@ -467,6 +489,8 @@ void TestReadOnlyStalePlaneFlush(const Context& context) { *properties = {}; properties->optimalTilingFeatures = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT; }; + TextureDetiler detiler(storageContext); + storageContext.detiler = &detiler; DepthTarget target{address, 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0}; target.depthSwizzle = XorSwizzleMode(TextureTileMode::kZ64KBX); Require(DepthSurfaceView(storageContext, target, true) != VK_NULL_HANDLE, "a read-only depth plane must render"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Float32FlushControl.cpp b/core/libs/prx/libSceAgcDriver/tests/Float32FlushControl.cpp index f51d28a2bf..55dc9f5ba9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Float32FlushControl.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Float32FlushControl.cpp @@ -17,7 +17,8 @@ constexpr Case Cases[]{ {VK_DRIVER_ID_MESA_RADV, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, true, true}, {VK_DRIVER_ID_MESA_RADV, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY, true, true}, {VK_DRIVER_ID_MESA_RADV, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE, false, false}, - {VK_DRIVER_ID_NVIDIA_PROPRIETARY, VK_FALSE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, false, false}, + {VK_DRIVER_ID_NVIDIA_PROPRIETARY, VK_FALSE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, true, false}, + {VK_DRIVER_ID_NVIDIA_PROPRIETARY, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, true, true}, {VK_DRIVER_ID_AMD_PROPRIETARY, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY, true, true}, {static_cast(0), VK_FALSE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE, false, false}, }; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 90c946f214..21accfeb6d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -31,6 +31,7 @@ #include "prx/libc/include/GuestHeap.hpp" #include "ImageTableAbi.hpp" #include "SpirvBackend/SpirvSpecialization.hpp" +#include #include #ifdef _WIN32 #include @@ -103,6 +104,7 @@ AgcDriver::QueueState makeState() { queue.context[0x114] = 0; queue.context[0xb4] = 0; queue.context[0xb5] = std::bit_cast(1.0f); + queue.context[0x3] = 0; queue.shader[0x008] = 0x100; queue.shader[0x009] = 0; return queue; @@ -313,6 +315,9 @@ void retainedPixelStageTests() { Require(!PixelProgramSkipped(queue), "an enabled high render target lost its pixel program"); queue.context[0x8e] = queue.context[0x8f] = 0; } + queue.context[0x3] = 0; + queue.context[0xb4] = std::bit_cast(0.0f); + queue.context[0xb5] = std::bit_cast(1.0f); queue.context[0x1b3] = queue.context[0x1b4] = 2; for (const auto [format, control] : {std::pair{1u, 1u}, std::pair{2u, 2u}, std::pair{9u, 0x100u}, std::pair{9u, 0x103u}}) { queue.context[0x1c4] = format; @@ -322,6 +327,25 @@ void retainedPixelStageTests() { const auto pixel = DecodePixelStageInfo(queue.context, ExportMappings(state), PixelProgramSkipped(queue)); Require(pixel.depthExportEnable == ((control & 1u) != 0) && pixel.stencilRefExportEnable == ((control & 2u) != 0) && pixel.sampleMaskExportEnable == ((control & 0x100u) != 0), "decoding lost an enabled pixel export"); } + queue.context[0x1c4] = 1; + queue.context[0x203] = 1; + queue.context[0xb4] = std::bit_cast(0.25f); + queue.context[0xb5] = std::bit_cast(0.75f); + const auto clampState = DecodeState(queue); + const auto clamped = DecodePixelStageInfo(queue.context, ExportMappings(clampState)); + Require(clamped.depthExportEnable && clamped.depthExportMin == 0.25f && clamped.depthExportMax == 0.75f, "PA_SC_VPORT_ZMIN_0/ZMAX_0 did not become the exported depth clamp"); + queue.context[0x3] = 0x10000; + const auto unclamped = DecodePixelStageInfo(queue.context, ExportMappings(clampState)); + Require(unclamped.depthExportEnable && unclamped.depthExportMin == -std::numeric_limits::infinity() && unclamped.depthExportMax == std::numeric_limits::infinity(), "DB_RENDER_OVERRIDE.DISABLE_VIEWPORT_CLAMP did not leave the exported depth unclamped"); + queue.context[0x3] = 0; + queue.context[0xb4] = std::bit_cast(0.9f); + expectFailure([&] { static_cast(DecodePixelStageInfo(queue.context, ExportMappings(clampState))); }, "invalid viewport depth clamp bounds"); + queue.context[0xb4] = std::bit_cast(std::numeric_limits::quiet_NaN()); + expectFailure([&] { static_cast(DecodePixelStageInfo(queue.context, ExportMappings(clampState))); }, "invalid viewport depth clamp bounds"); + queue.context[0x1c4] = 0; + queue.context[0x203] = 0x10; + const auto notExporting = DecodePixelStageInfo(queue.context, ExportMappings(clampState)); + Require(!notExporting.depthExportEnable && notExporting.depthExportMin == 0.0f && notExporting.depthExportMax == 1.0f, "a draw without depth export took the viewport depth clamp"); queue.context[0x203] = 0; for (const auto alpha : {0xaa00u, 0xaa01u}) { queue.context[0x2dc] = alpha; @@ -1898,7 +1922,8 @@ void DisabledColorTests() { const auto thirdSlice = AgcDriver::Graphics::DecodeState(queue); Require(thirdSlice.depth && thirdSlice.depth->firstSlice == 3 && thirdSlice.depth->address == depthOnly.depth->address + 3u * AgcDriver::Graphics::DepthSliceBytes(depthOnly.depth->extent, 4u), "a depth view of slice 3 lost its slice or its Z offset"); queue.context[0x002] = 1u << 26u; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "on a surface without mips"); + const auto singleLevelMip = AgcDriver::Graphics::DecodeState(queue); + Require(singleLevelMip.depth && singleLevelMip.depth->address == depthOnly.depth->address && singleLevelMip.depth->extent.width == depthOnly.depth->extent.width && singleLevelMip.depth->extent.height == depthOnly.depth->extent.height, "a depth view mip on a single-level surface did not select its first level"); queue.context[0x002] = 0; { using AgcDriver::Graphics::ComputeElementMipLayout; @@ -3174,6 +3199,9 @@ void metadataPassTests() { queue.context[0x324] = 0; pass = DecodeColorMetadataPass(queue); Require(pass.has_value() && pass->targets.size() == 1 && pass->targets[0].dccAddress == keysAddress, "the metadata pass lost the target's DCC keys"); + auto partial = queue; + partial.context[0x10f] = std::bit_cast(16.0f); + expectFailure([&] { DecodeColorMetadataPass(partial); }, "over part of a color target"); auto mipmapped = queue; mipmapped.context[0x3b0] |= 1u << 28u; expectFailure([&] { DecodeColorMetadataPass(mipmapped); }, "mipmapped DCC"); @@ -3397,6 +3425,11 @@ void cmaskTests() { queue.context[0x202] = 0xcc0020; auto pass = AgcDriver::Graphics::DecodeColorMetadataPass(queue); Require(pass.has_value() && pass->targets.size() == 1 && pass->targets[0].cmaskAddress == cmaskAddress, "the metadata pass lost the target's CMASK"); + auto partial = queue; + partial.context[0x10f] = std::bit_cast(16.0f); + Require(AgcDriver::Graphics::DecodeColorMetadataPass(partial).has_value(), "a fast-clear eliminate of a CMASK target without DCC keys over part of it was refused"); + partial.context[0x202] = 0xcc0060; + expectFailure([&] { AgcDriver::Graphics::DecodeColorMetadataPass(partial); }, "over part of a color target"); const AgcDriver::Graphics::Context context{}; const auto texels = [&](std::uint32_t value) { for (std::size_t offset = 0; offset < colorMemory.size(); offset += 4) { @@ -4331,6 +4364,31 @@ void multisampleDccStateTests() { target.cmaskFastClear = true; expectFailure([&] { static_cast(AgcDriver::Graphics::MultisampledDccClear(target, dcc, cmask)); }, "multisampled DCC CMASK compression or register clear"); } + for (const auto samples : {2u, 4u, 8u}) { + for (const auto format : {VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB}) { + for (const auto key : {0x00u, 0x40u, 0x80u, 0xc0u}) { + for (const auto alphaOnMsb : {false, true}) { + AgcDriver::Graphics::ColorTarget target{}; + target.extent = {64, 64}; + target.format = format; + target.tileMode = AgcDriver::Graphics::ColorTileMode::RenderTarget; + target.elementBytes = 4; + target.samples = samples; + target.dccAddress = 0x200000u; + target.dccPipeAligned = true; + target.dccAlphaOnMsb = alphaOnMsb; + const std::vector dcc(AgcDriver::Graphics::MultisampledDccBytes(target), static_cast(key)); + const auto clear = AgcDriver::Graphics::MultisampledDccClear(target, dcc, {}); + const auto alpha = alphaOnMsb ? 3u : format == VK_FORMAT_B8G8R8A8_SRGB ? 2u : 0u; + for (std::uint32_t channel = 0; channel < 4; ++channel) { + const bool one = channel == alpha ? key == 0x40u || key == 0xc0u : key == 0x80u || key == 0xc0u; + Require(clear.float32[channel] == static_cast(one), "an sRGB multisampled DCC endpoint did not produce its exact linear clear value"); + } + } + } + } + } + } void multisampledDepthTests() { @@ -4719,6 +4777,17 @@ void DepthRemapTests() { floating.depthWrite = true; AgcDriver::Graphics::ApplyDepthRemap(remapping, floating); Require(floating.depthRemap.mode == 1 && floating.viewport.minDepth == 0 && floating.viewport.maxDepth == 1 && floating.depthClamp, "a written float depth target refused clipped clamp bounds"); + auto tessellated = AgcDriver::Graphics::DecodeState(queue); + tessellated.stages.path = AgcDriver::Graphics::ShaderPath::Tessellation; + tessellated.stages.tessellation = ShaderRecompiler::TessellationConfiguration{3u, 3u, 1u, 2u, 2u}; + auto nativeTessellation = tessellated; + AgcDriver::Graphics::ApplyDepthRemap(unrestricted, nativeTessellation); + Require(nativeTessellation.depthRemap.mode == 0 && nativeTessellation.viewport.minDepth == -0.5f && nativeTessellation.viewport.maxDepth == 1.5f, "an unrestricted tessellation draw was remapped"); + AgcDriver::Graphics::ApplyDepthRemap(remapping, tessellated); + Require(tessellated.depthRemap.mode == 1 && tessellated.depthRemap.scale == 2.0f && tessellated.depthRemap.offset == -0.5f && tessellated.depthClamp, "a tessellation draw did not remap its depth range"); + auto combined = nativeTessellation; + combined.stages.mesh = ShaderRecompiler::MeshConfiguration{}; + expectFailure([&] { AgcDriver::Graphics::ApplyDepthRemap(remapping, combined); }, "vertex and tessellation paths only"); } void DepthBoundsEmulationTests() { @@ -8448,7 +8517,7 @@ void barycentricEmulationTests() { for (std::uint32_t location = 0; location < 11u; ++location) crowded.push_back({location, location, false, true}); expectFailure([&] { static_cast(LayoutBarycentricEmulation(crowded, {true, false, false})); }, "exceed 32 locations"); - const auto compile = [&](std::span code, bool barycentric, std::uint32_t secondInput = 0xffffffffu) { + const auto compile = [&](std::span code, bool barycentric, std::uint32_t secondInput = 0xffffffffu, bool builtins = false) { auto queue = makeState(); queue.context[0x1b3] = 0x2u; queue.context[0x1b4] = 0x2u; @@ -8463,6 +8532,7 @@ void barycentricEmulationTests() { request.target.spirvVersion = 0x00010300u; request.target.subgroupSize = 64; request.target.fragmentShaderBarycentricEnabled = barycentric; + request.target.fragmentBarycentricBuiltins = builtins; request.layout.pushConstantSizeBytes = 128; request.useCache = false; return Recompile(request); @@ -8510,6 +8580,15 @@ void barycentricEmulationTests() { static constexpr std::array sharedSlot{0xc8080000u, 0xc8090001u, 0xc80e0402u, 0xf800180fu, 0x03030202u, 0xbf810000u}; const auto shared = compile(sharedSlot, false, 0x400u); Require(shared.barycentricEmulation.active && !barycentricCapability(shared), "inputs sharing a slot with different interpolation did not emulate per-vertex parameters"); + static constexpr std::array interpolatesAndReadsIj{0xc8080000u, 0xc8090001u, 0xf800180fu, 0x02020100u, 0xbf810000u}; + for (const auto& code : {std::span(readsIj), std::span(interpolatesAndReadsIj)}) { + const auto builtin = compile(code, false, 0xffffffffu, true); + const auto builtinDecorations = decorations(builtin, spv::DecorationBuiltIn); + Require(!builtin.barycentricEmulation.active && barycentricCapability(builtin) && std::find(builtinDecorations.begin(), builtinDecorations.end(), static_cast(spv::BuiltInBaryCoordKHR)) != builtinDecorations.end() && decorations(builtin, spv::DecorationPerVertexKHR).empty(), "reading the I/J VGPRs with barycentric built-ins did not use BaryCoordKHR"); + Require(std::ranges::none_of(builtin.fragmentParameters, [](const FragmentParameter& parameter) { return parameter.perVertex; }), "barycentric built-ins made a parameter per-vertex"); + } + Require(compile(interpolatesAndReadsIj, false).barycentricEmulation.active, "reading the I/J VGPRs next to v_interp_p1/p2 without the extension did not emulate barycentrics"); + Require(compile(readsVertex, false, 0xffffffffu, true).barycentricEmulation.active, "v_interp_mov of P10 with only barycentric built-ins did not emulate per-vertex parameters"); fragment = compile(readsVertex, false); Require(fragment.barycentricEmulation.active && !fragment.barycentricEmulation.smooth && fragment.fragmentParameters.size() == 1u && fragment.fragmentParameters[0].perVertex, "v_interp_mov of P10 did not emulate per-vertex parameters"); Require(decorations(fragment, spv::DecorationLocation) == std::vector{0u} && decorations(fragment, spv::DecorationPerVertexKHR).empty() && !decorations(fragment, spv::DecorationFlat).empty() && !barycentricCapability(fragment), "the emulated per-vertex parameter was not a flat array at the first free location"); @@ -11873,6 +11952,57 @@ void pipelineStoreGrowthTests(bool fixed) { Require(mock.live == 0, "pipeline store growth tests leaked Vulkan objects"); } +void pipelineStoreTouchTests() { + using namespace AgcDriver::Graphics; + mock = MockVulkan{}; + auto context = mockContext(); + context.device = makeHandle(); + context.graphicsPipelineLibrary = true; + context.pipelineLinkTimeOptimization = false; + context.limits.framebufferNoAttachmentsSampleCounts = VK_SAMPLE_COUNT_1_BIT; + ShaderRecompiler::RecompileResult vertex, fragment; + vertex.spirv = makeModule({}); + fragment.spirv = makeModule({.fragment = true}); + vertex.variantId = 401; + fragment.variantId = 402; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, PipelinePushSlotBytes}}}; + std::array mainKey{51}, fragmentKey{52}; + const PipelineSetLayouts resources{makeHandle(), makeHandle(), true, mainKey, fragmentKey}; + const auto layout = [](std::uint32_t configuration) { + VertexInputLayout input; + input.bindings.push_back({0, 4u * (configuration + 1u), VK_VERTEX_INPUT_RATE_VERTEX}); + return input; + }; + std::uint64_t hash = 0; + std::weak_ptr touched; + std::weak_ptr other; + { + auto first = CachedPipeline(context, State{}, layout(0), resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, nullptr, &hash); + Require(hash != 0, "a stored pipeline reported no store hash"); + std::uint64_t secondHash = 0; + auto second = CachedPipeline(context, State{}, layout(1), resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, nullptr, &secondHash); + Require(secondHash != 0 && secondHash != hash, "two configurations reported the same store hash"); + touched = first; + other = second; + const auto hits = PipelineStoreTotals().hits; + Require(TouchCachedPipeline(context, hash, touched) == first && PipelineStoreTotals().hits == hits + 1, "touching a stored pipeline did not return it as a hit"); + Require(TouchCachedPipeline(context, hash, other) == nullptr, "touching a hash with another pipeline returned the stored one"); + Require(TouchCachedPipeline(context, hash, std::weak_ptr{}) == nullptr, "touching a hash without a pipeline returned the stored one"); + } + for (std::uint32_t configuration = 2; configuration < PipelineStoreTotals().bound; ++configuration) static_cast(CachedPipeline(context, State{}, layout(configuration), resources, shaders)); + Require(TouchCachedPipeline(context, hash, touched) != nullptr, "the first pipeline left a store that was not yet full"); + bool created = false; + static_cast(CachedPipeline(context, State{}, layout(PipelineStoreTotals().bound), resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, &created)); + Require(created, "the configuration that fills the store was already stored"); + static_cast(CachedPipeline(context, State{}, layout(0), resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, &created)); + Require(!created, "a touched pipeline was evicted as the least recently used one"); + static_cast(CachedPipeline(context, State{}, layout(1), resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, &created)); + Require(created, "the least recently used pipeline stayed while a touched one was older"); + ClearCachedPipelines(context.device); + Require(TouchCachedPipeline(context, hash, touched) == nullptr, "touching a pipeline of a cleared store returned it"); + Require(mock.live == 0, "pipeline store touch tests leaked Vulkan objects"); +} + // A journal longer than the store's starting bound: 1032 recorded pipelines replayed into an empty // store all stay resident (the replay grows the bound instead of dropping what does not fit). void pipelineStoreReplayGrowthTests() { @@ -12388,6 +12518,11 @@ int main(int argc, char** argv) { std::cout << "pipeline store bound follows the evicted pipelines that come back\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--pipeline-store-touch") { + pipelineStoreTouchTests(); + std::cout << "a touched pipeline is returned as a hit and stays recently used\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--pipeline-store-lazy-buffer-pool") { pipelineStoreLazyBufferPoolTests(); std::cout << "cached pipelines survive lazy buffer pool creation\n"; @@ -12502,7 +12637,7 @@ int main(int argc, char** argv) { #else setenv("APS5_ADDRESS_COPY_MAX_MIB", "1", 1); #endif - { + for (const int frames : {0, 1, 2}) { mock = MockVulkan{}; auto context = mockContext(); context.bufferDeviceAddress = true; @@ -12517,9 +12652,9 @@ int main(int argc, char** argv) { }, {}, {} - }, std::string_view(argv[1]) == "--address-copy-cap-remembered"); + }, std::string_view(argv[1]) == "--address-copy-cap-remembered", frames); + Require(mock.live == 0, "the address copy cap test leaked Vulkan objects"); } - Require(mock.live == 0, "the address copy cap test leaked Vulkan objects"); return 0; } if (argc == 2 && std::string_view(argv[1]) == "--address-space-teardown") { @@ -12799,6 +12934,7 @@ int main(int argc, char** argv) { Require(mock.live == 0, "BDA resources leaked Vulkan objects"); RunGuestAllocationTests(); RunUnmappedGapTests(); + RunVanishedPageReadTests(); RunColorTargetLayoutTests(); RunLiveStackAccessTests(); RunTextureFormatTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index 89d83afd29..6a1deede07 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -328,3 +329,34 @@ void RunUnmappedGapTests() { GuestArena::GuestArenaRelease_nid_postfix(spare, page); #endif } + +void RunVanishedPageReadTests() { +#ifdef _WIN32 + namespace GuestMemory = AgcDriver::GuestMemory; + Require(GuestArena::GuestArenaAvailable_nid_postfix(), "released-page read fixture needs the guest arena"); + constexpr std::size_t page = 4096; + constexpr std::uint32_t ReadWriteProtection = 0x04; + auto* block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * page, page)); + const auto address = reinterpret_cast(block); + GuestArena::GuestArenaCommit_nid_postfix(block, page, ReadWriteProtection, page); + GuestArena::GuestArenaCommit_nid_postfix(block + page, page, ReadWriteProtection, page); + std::memset(block, 0x5a, 2 * page); + Require(GuestMemory::Accessible(block, 2 * page), "committed arena test pages are not readable"); + GuestArena::GuestArenaReset_nid_postfix(block + page, page); + std::array read{}; + read.fill(std::byte{0xff}); + GuestMemory::ReadCommitted(address, read); + const auto filled = [](std::span bytes, std::byte value) { return std::all_of(bytes.begin(), bytes.end(), [&](std::byte byte) { return byte == value; }); }; + Require(filled(std::span(read).first(page), std::byte{0x5a}) && filled(std::span(read).subspan(page), std::byte{0}), "a committed read of a page released after it was checked did not read it as uncommitted"); + std::array expected{}; + std::fill(expected.begin(), expected.begin() + page, std::byte{0x5a}); + std::fill(expected.begin() + page, expected.end(), std::byte{0x77}); + Require(GuestMemory::EqualsCommitted(address, expected), "a committed compare of a page released after it was checked did not skip it"); + expected[page - 1] = std::byte{0}; + Require(!GuestMemory::EqualsCommitted(address, expected), "a committed compare missed a difference before a page released after it was checked"); + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(address, 2 * page); + GuestArena::GuestArenaReset_nid_postfix(block, page); + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(address, 2 * page); + GuestArena::GuestArenaRelease_nid_postfix(block, 2 * page); +#endif +} diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestMemoryLifetimeSupport.hpp b/core/libs/prx/libSceAgcDriver/tests/GuestMemoryLifetimeSupport.hpp index 863b8ca67f..2b2866adc8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestMemoryLifetimeSupport.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestMemoryLifetimeSupport.hpp @@ -17,6 +17,8 @@ static inline bool GuestMemoryLifetimeHasHostImports(VkDevice device) { } #ifdef _WIN32 constexpr const char* libraryName = "vulkan-1.dll"; +#elif defined(__APPLE__) + constexpr const char* libraryName = "libvulkan.1.dylib"; #else constexpr const char* libraryName = "libvulkan.so.1"; #endif diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index ab7db93b7a..22eb2c0eb2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -371,9 +371,14 @@ void RunGuestTextureResourceTests() { result = DecodeTextureResource(pack(cubeExtraSlice)); Require(result.baseArray == 0 && result.depthOrLastArray == 6, "a cube descriptor with a slice past its last whole cube lost a slice"); Require(DescribeSurface(result).imageLayers == 7, "a cube allocation with a slice past its last whole cube omitted it"); - Fields cubePartial = cube; - cubePartial.depth = 4; - rejectFields(cubePartial, "fewer than 6 array slices"); + for (const auto [first, last] : {std::pair{0u, 4u}, std::pair{0u, 1u}, std::pair{2u, 5u}, std::pair{6u, 10u}}) { + auto shortCube = cube; + shortCube.baseArray = first; + shortCube.depth = last; + result = DecodeTextureResource(pack(shortCube)); + Require(result.baseArray == first && result.depthOrLastArray == first + 5u, "a cube descriptor with fewer than six slices did not expose six faces from its base slice"); + Require(DescribeSurface(result).imageLayers == first + 6u, "a cube allocation with fewer than six slices omitted faces"); + } Fields multisampled = base; multisampled.typeRaw = 14; diff --git a/core/libs/prx/libSceAgcDriver/tests/PipelineCacheFailures.cpp b/core/libs/prx/libSceAgcDriver/tests/PipelineCacheFailures.cpp new file mode 100644 index 0000000000..7e13f9881c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/PipelineCacheFailures.cpp @@ -0,0 +1,238 @@ +#include "prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp" +#include "AllocationFailure.hpp" +#include "ShaderCacheDirectory.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AgcDriver::Graphics; +using namespace std::chrono_literals; + +namespace { + +void Check(bool value, const char* reason) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", reason); + std::fflush(stderr); + std::_Exit(1); +} + +std::mutex mutex; +std::condition_variable changed; +std::map> caches; +std::uintptr_t next = 1; +std::atomic armed{false}; +std::atomic failStartup{false}; +std::atomic queries{0}; +std::atomic faults{0}; +std::size_t initialBytes = 64; + +std::vector Payload() { + std::vector bytes(initialBytes); + const std::array header{32, VK_PIPELINE_CACHE_HEADER_VERSION_ONE, 13, 42}; + std::memcpy(bytes.data(), header.data(), sizeof(header)); + return bytes; +} + +VKAPI_ATTR VkResult VKAPI_CALL Create(VkDevice, const VkPipelineCacheCreateInfo* info, const VkAllocationCallbacks*, VkPipelineCache* output) { + std::lock_guard lock(mutex); + *output = reinterpret_cast(next++); + if (info->initialDataSize != 0) { + const auto* bytes = static_cast(info->pInitialData); + caches[*output] = std::vector(bytes, bytes + info->initialDataSize); + } else caches[*output] = Payload(); + if (failStartup.exchange(false)) { + ++faults; + AgcDriver::Tests::SetAllocationFailureCountdown(0); + } + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL Destroy(VkDevice, VkPipelineCache cache, const VkAllocationCallbacks*) { + std::lock_guard lock(mutex); + Check(caches.erase(cache) == 1, "destroyed an unknown cache"); + changed.notify_all(); +} + +VKAPI_ATTR VkResult VKAPI_CALL Merge(VkDevice, VkPipelineCache destination, std::uint32_t count, const VkPipelineCache* sources) { + std::lock_guard lock(mutex); + Check(count == 1, "expected one merge source"); + caches.at(destination) = caches.at(sources[0]); + return VK_SUCCESS; +} + +VKAPI_ATTR VkResult VKAPI_CALL Data(VkDevice, VkPipelineCache cache, std::size_t* size, void* output) { + std::lock_guard lock(mutex); + const auto& bytes = caches.at(cache); + if (output != nullptr) { + Check(*size >= bytes.size(), "cache snapshot buffer is too small"); + std::memcpy(output, bytes.data(), bytes.size()); + } + *size = bytes.size(); + if (output == nullptr && armed && ++queries == 2) { + armed = false; + ++faults; + changed.notify_all(); + AgcDriver::Tests::SetAllocationFailureCountdown(0); + } + return VK_SUCCESS; +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL Resolve(VkDevice, const char* name) { + if (std::strcmp(name, "vkCreatePipelineCache") == 0) return reinterpret_cast(Create); + if (std::strcmp(name, "vkDestroyPipelineCache") == 0) return reinterpret_cast(Destroy); + if (std::strcmp(name, "vkGetPipelineCacheData") == 0) return reinterpret_cast(Data); + if (std::strcmp(name, "vkMergePipelineCaches") == 0) return reinterpret_cast(Merge); + return nullptr; +} + +std::vector Read(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + Check(static_cast(file), "saved cache file is missing"); + return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; +} + +void Grow(VkPipelineCache cache, std::size_t bytes = 128) { + std::lock_guard lock(mutex); + caches.at(cache).resize(bytes, std::byte{9}); +} + +} + +int main(int argc, char** argv) { + Check(argc == 2, "expected startup, now, busy, destructor, worker or read-retry"); + const std::string_view mode(argv[1]); + Check(mode == "startup" || mode == "now" || mode == "busy" || mode == "destructor" || mode == "worker" || mode == "read-retry", "unknown mode"); +#ifndef _WIN32 + if (mode == "read-retry") return 77; +#endif + const auto root = std::filesystem::temp_directory_path() / ("aps5-cache-allocation-" + std::to_string(std::random_device{}())); + Check(std::filesystem::create_directory(root), "test cache directory already exists"); + struct Cleanup { + std::filesystem::path path; + ~Cleanup() { std::error_code error; std::filesystem::remove_all(path, error); } + } cleanup{root}; +#ifdef _WIN32 + _putenv_s("ANYPS5_NO_SHADER_CACHE", "0"); + _wputenv_s(L"ANYPS5_SHADER_CACHE_DIR", root.wstring().c_str()); +#else + setenv("ANYPS5_NO_SHADER_CACHE", "0", 1); + setenv("ANYPS5_SHADER_CACHE_DIR", root.string().c_str(), 1); +#endif + Context context{}; + context.device = reinterpret_cast(std::uintptr_t{1}); + context.deviceProc = Resolve; + VkPhysicalDeviceProperties properties{}; + properties.vendorID = 13; + properties.deviceID = 42; + if (mode == "startup") { + failStartup = true; + bool failed = false; + try { + PipelineCache attempt(context, properties); + } catch (const std::bad_alloc&) { + failed = true; + } + Check(failed && faults == 1, "saver startup allocation failure was not propagated"); + Check(caches.empty(), "failed saver startup leaked the Vulkan pipeline cache"); + { + PipelineCache retry(context, properties); + Check(retry.Handle() != VK_NULL_HANDLE, "cache creation could not retry after failed saver startup"); + } + Check(caches.empty(), "successful retry leaked a Vulkan pipeline cache"); + std::puts("startup: propagated the allocation failure, released the cache and allowed recreation"); + return 0; + } + if (mode == "read-retry") initialBytes = 32768; + auto cache = std::make_unique(context, properties); + cache->SaveNow(); + std::filesystem::path file; + for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) if (entry.path().extension() == ".bin") file = entry.path(); + Check(!file.empty(), "initial save created no cache file"); +#ifdef _WIN32 + if (mode == "read-retry") { + { + PipelineCache writer(context, properties); + Grow(writer.Handle(), 40960); + writer.SaveNow(); + } + const auto before = Read(file); + Check(before.size() == 24 + 40960, "the second writer did not update the cache file"); + Grow(cache->Handle(), 65536); + std::error_code error; + const auto native = ShaderRecompiler::NativeCachePath_nid_no_patch(file, error); + Check(!error, "could not resolve the test cache path"); + struct TailLock { + HANDLE handle = INVALID_HANDLE_VALUE; + OVERLAPPED region{}; + void Release() { + if (handle == INVALID_HANDLE_VALUE) return; + Check(UnlockFileEx(handle, 0, 1, 0, ®ion) != 0, "could not unlock the cache body"); + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + } + ~TailLock() { Release(); } + } locked; + locked.handle = CreateFileW(native.c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + Check(locked.handle != INVALID_HANDLE_VALUE, "could not open the cache body lock handle"); + locked.region.Offset = 16384; + Check(LockFileEx(locked.handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &locked.region) != 0, + "could not lock the cache body"); + { + std::ifstream header(file, std::ios::binary); + std::array bytes{}; + Check(static_cast(header.read(bytes.data(), bytes.size())), "the body lock unexpectedly blocked the header"); + } + std::vector unreadable; + Check(!ShaderRecompiler::ReadWholeFile(file, unreadable), "the body lock did not block a complete cache read"); + cache->SaveNow(); + locked.Release(); + Check(Read(file) == before, "a failed cache read overwrote the other writer's file"); + cache->SaveNow(); + Check(Read(file).size() == 24 + 65536, "a transient cache read failure prevented the later save"); + cache.reset(); + Check(caches.empty(), "cache read retry leaked Vulkan objects"); + std::puts("read-retry: kept the unreadable cache file and saved new pipelines after it became readable"); + return 0; + } +#endif + const auto original = Read(file); + Grow(cache->Handle()); + queries = 0; + armed = true; + if (mode == "destructor") cache.reset(); + else if (mode == "worker") { + std::unique_lock lock(mutex); + Check(changed.wait_for(lock, 14s, [] { return faults == 1; }), "background save did not reach the allocation failure"); + } else if (mode == "busy") cache->SaveUnlessBusy(); + else cache->SaveNow(); + Check(faults == 1 && !armed, "the snapshot allocation was not exercised"); + Check(Read(file) == original, "a failed snapshot replaced the previous cache file"); + { + std::unique_lock lock(mutex); + if (mode == "worker") Check(changed.wait_for(lock, 2s, [] { return caches.size() == 1; }), "background snapshot did not release its temporary cache"); + Check(caches.size() == (cache ? 1u : 0u), "failed snapshot leaked its temporary merged cache"); + } + if (!cache) { + cache = std::make_unique(context, properties); + Grow(cache->Handle()); + } + cache->SaveNow(); + Check(Read(file).size() == 24 + 128 && Read(file) != original, "a failed snapshot prevented a later successful save"); + cache.reset(); + Check(caches.empty(), "pipeline cache allocation test leaked Vulkan objects"); + std::printf("%.*s: allocation failure kept the old file, released temporary caches and allowed a retry\n", static_cast(mode.size()), mode.data()); +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 79ed61e7fc..352b5f5086 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -109,6 +109,19 @@ void testNopPad() { AgcDriver::Pm4::Validate(largest, 0); } +void testWorkloadNopPayloadsIgnored() { + AgcDriver::QueueState state; + for (const auto& packet : { + makePacket(0x10, {1, 0x18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), + makePacket(0x10, {1, 33, 0xffffffffu, 0xfffffffdu, 0, 0, 0, 0, 0, 0, 0}), + makePacket(0x10, {1, 0, 0, 0, 0, 0, 0, 0}), + }) execute(state, packet); + check(state.markers.empty() && state.indexBase == 0 && !state.indexBufferSize && state.indexType == 0 && + state.instanceCount == 1 && state.predication.operation == 0 && state.predication.address == 0 && + state.shader.begin() == state.shader.end() && !state.savedContext && state.constantRam.empty(), + "a workload NOP payload reached queue state"); +} + void testRegisters() { AgcDriver::QueueState state; execute(state, makePacket(0x79, {0x242, 4})); @@ -2095,6 +2108,7 @@ int main(int argc, char** argv) { } testCatalog(); testNopPad(); + testWorkloadNopPayloadsIgnored(); testRewindPayloadBits(); testWriteChangedKeepsUntouchedBytes(); testWriteChangedStoresExactlyTheChangedRuns(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index acec23f12a..6bcfea05b4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -81,6 +81,7 @@ #include #include #include +#include #include namespace { @@ -9326,6 +9327,109 @@ void multisampleGuestAccessTests(const Device& device, Recorder& recorder) { std::cout << "Multisampled target guest access tests passed\n"; } +void repeatStorageGuardTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if ((base.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0 || !device.NullDescriptors()) { + std::cout << "4x color targets or nullDescriptor unavailable: repeated storage surface guard not tested\n"; + return; + } + constexpr std::uint32_t side = 256; + constexpr std::uint32_t targetSide = 64; + constexpr std::size_t surfaceBytes = side * side * 4; + constexpr std::size_t targetBytes = 65536u; + constexpr std::size_t bytes = surfaceBytes + 2u * targetBytes; +#ifdef _WIN32 + void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + void* block = std::aligned_alloc(65536, bytes); +#endif + Require(block != nullptr, "cannot allocate the repeated storage surface block"); + std::memset(block, 0, bytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + ~Unregister() { + ClearMultisampleTargets(context.device); + ClearCachedTextures(context.device); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + } unregister{base, block}; + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + context.nullDescriptors = true; + TextureCache cache(context); + context.textureCache = &cache; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = side; + resource.height = side; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kR64KBX; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + Require(DescribeSurface(resource).guestBytes == surfaceBytes, "the repeated storage surface has an unexpected size"); + const auto guard = [&](std::uint64_t at) { + ColorTarget target{}; + target.address = at; + target.surfaceAddress = at; + target.extent = {targetSide, targetSide}; + target.surfaceExtent = target.extent; + target.format = VK_FORMAT_R8G8B8A8_UNORM; + target.bytes = targetBytes; + target.samples = 4; + std::lock_guard gpu(GpuMutex()); + Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "the guarding multisampled target has no view"); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + }; + const auto clearTargets = [&] { + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + ClearMultisampleTargets(context.device); + }; + const auto refusal = [](const std::function& access) { + try { + access(); + } catch (const std::exception& error) { + return std::string(error.what()); + } + return std::string(); + }; + const auto stored = CachedStorageSurface(context, resource); + Require(stored != nullptr, "the storage surface has no image"); + Require(RepeatStorageSurface(context, resource, stored) == stored && CachedStorageSurface(context, resource) == stored, "without a guarded target the repeat path and the lookup returned different images"); + guard(address + surfaceBytes); + Require(RepeatStorageSurface(context, resource, stored) == stored && CachedStorageSurface(context, resource) == stored, "a guarded target that starts where the surface ends changed the repeat path's or the lookup's image"); + clearTargets(); + guard(address + targetBytes); + const auto repeated = refusal([&] { static_cast(RepeatStorageSurface(context, resource, stored)); }); + const auto looked = refusal([&] { static_cast(CachedStorageSurface(context, resource)); }); + Require(!looked.empty() && looked.find("a storage image access") != std::string::npos && looked.find("multisampled color target") != std::string::npos, "the lookup did not refuse a surface a guarded target overlaps after its first byte: " + looked); + Require(repeated == looked, "the repeat path decided differently from the lookup for a guarded target after the surface's first byte: " + repeated); + clearTargets(); + std::cout << "Repeated storage surface guard tests passed\n"; +} + void oneFragmentFmaskGuardTests(const Device& device) { const auto& base = device.GetContext(); if (!device.NullDescriptors()) { @@ -9906,8 +10010,8 @@ void singleCubeTests(const Device& device, Recorder& recorder) { context.detiler = &detiler; SampleProgram program(context, recorder, SAMPLE_ARRAY_SPV); const VkComponentMapping identity{}; - for (const auto first : {0u, 5u, 6u}) { - std::array words{0x1000u, (56u << 20u) | (3u << 30u), 15u | (63u << 14u), 0xb0000facu, first | (first << 16u), 0, 0, 0}; + for (const auto [first, last] : {std::pair{0u, 0u}, std::pair{5u, 5u}, std::pair{6u, 6u}, std::pair{0u, 4u}, std::pair{2u, 5u}}) { + std::array words{0x1000u, (56u << 20u) | (3u << 30u), 15u | (63u << 14u), 0xb0000facu, last | (first << 16u), 0, 0, 0}; auto resource = DecodeTextureResource(words); const auto geometry = DescribeSurface(resource); std::vector memory(static_cast(geometry.guestBytes) + 256); @@ -12127,6 +12231,10 @@ int main(int argc, char** argv) { std::cout << "Copied draw input tests passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--repeat-storage-guard-only") { + repeatStorageGuardTests(device, recorder); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--multisample-only") { multisampleGuestAccessTests(device, recorder); oneFragmentFmaskGuardTests(device); @@ -12191,6 +12299,7 @@ int main(int argc, char** argv) { bdaStagingTests(device, recorder); depthSurfaceProofTests(device, recorder); multisampleGuestAccessTests(device, recorder); + repeatStorageGuardTests(device, recorder); oneFragmentFmaskGuardTests(device); tiledResolveTests(device, recorder); fmaskDecompressMsaaTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index d30a761388..a420eade76 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -351,6 +351,7 @@ CompiledVariant sampleVariant() { image.flatLineCompatible = false; image.flatVolumeLineCompatible = false; image.dimensionQueryOnly = false; + image.sampledOnly = false; image.foldTexelOffsetsOnly = true; image.remappedDepth1D = true; image.optionalDescriptor = true; @@ -526,6 +527,62 @@ std::vector packBytes(const std::vector published; + require(ReadWholeFile(path, published) && published == packBytes({{key, EncodeEntry(key, variant)}}), "editing packed SPIR-V modified the cache file"); + second = {}; +#ifdef _WIN32 + require(VirtualQuery(words, ®ion, sizeof(region)) == sizeof(region) && region.State == MEM_FREE, "the final packed variant did not release its mapping"); +#endif + const auto offset = sizeof(PackHeader) + sizeof(PackRecord); + published.insert(published.begin() + offset, std::byte{}); + PackHeader header; + PackRecord record; + std::memcpy(&header, published.data(), sizeof(header)); + std::memcpy(&record, published.data() + sizeof(header), sizeof(record)); + ++header.fileBytes; + ++record.offset; + std::memcpy(published.data() + sizeof(header), &record, sizeof(record)); + header.indexHash = ShaderRecompiler::HashBytes(std::span(published).subspan(sizeof(header), sizeof(record))); + std::memcpy(published.data(), &header, sizeof(header)); + require(WriteFileAtomically(path, published), "cannot publish an unaligned packed entry"); + { + CachePack pack(path); + require(pack.Valid(), "an unaligned packed entry was rejected"); + require(pack.Load(key, first, timings) == LoadStatus::Loaded && pack.Load(key, second, timings) == LoadStatus::Loaded, "an unaligned packed entry did not load"); + require(first.artifact.spirv.Words().data() != second.artifact.spirv.Words().data(), "an unaligned packed module did not use aligned owned storage"); + } + requireSameVariant(variant, first, "unaligned packed variant outlives its pack"); + requireSameVariant(variant, second, "second unaligned packed variant outlives its pack"); +} + void refreshIndexHash(std::vector& pack, std::size_t entries) { using namespace ShaderRecompiler::ShaderDiskCache; const auto index = std::span(pack).subspan(sizeof(PackHeader), entries * sizeof(PackRecord)); @@ -780,6 +837,7 @@ void verifyKeySensitivity() { changes("exact robust loads", [](SampleRequest& sample) { sample.request.target.exactRobustLoads = true; }); changes("wave LDS memory barriers", [](SampleRequest& sample) { sample.request.target.waveLdsMemoryBarriers = true; }); changes("device-scope LDS memory barriers", [](SampleRequest& sample) { sample.request.target.deviceScopeLdsMemoryBarriers = true; }); + changes("LDS lock fences", [](SampleRequest& sample) { sample.request.target.ldsLockFences = true; }); changes("the workgroup size limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupSize[2] = 128; }); changes("the invocation limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupInvocations = 512; }); changes("the shared memory limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupSharedMemoryBytes = 32768; }); @@ -2541,6 +2599,13 @@ int main(int argc, char** argv) { std::cout << "housekeeping version " << directory.filename().string() << '\n'; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--prebuilt-pack-ownership") { + const auto directory = std::filesystem::temp_directory_path() / ("aps5-pack-ownership-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(directory); + verifyPrebuiltPackOwnership(directory); + std::filesystem::remove_all(directory); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--resource-plan-codec") { verifyResultRoundTrip(); verifyEntryRoundTrip(); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index a47dc40213..3efcc4e97a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -30,6 +30,7 @@ #include "BdaAbi.hpp" #include "ImageTableAbi.hpp" #include "ShaderDiskCache.hpp" +#include #include #include #include @@ -1234,6 +1235,475 @@ void verifyTypeZeroImageMaterialization() { atomic ? "TYPE=0 or 1 atomic image descriptor was accepted without hardware evidence" : "TYPE=0 or 1 writable image descriptor was accepted without hardware evidence"); } } + + std::vector code{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf4000400u, 0xfa000050u, 0xf4080a00u, 0xfa000080u, 0xbf8cc07fu, 0xf42c0814u, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850008u}; + for (std::uint32_t dword = 0; dword < 8u; dword++) { + code.push_back(0xbe800300u | ((8u + dword) << 16u) | (32u + dword)); + } + code.insert(code.end(), {0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u}); + constexpr std::uint32_t Format8888UNorm = 56; + constexpr std::uint32_t Type2D = 9; + struct alignas(256) Texture { std::array bytes{}; }; + static Texture textures[2]; + const auto makeImage = [&](const Texture& texture, std::uint32_t extent) { + const auto base = static_cast(reinterpret_cast(texture.bytes.data())); + return std::array{static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (extent << 30u), extent << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; + }; + const auto oldT = makeImage(textures[0], 3u); + const auto newT = makeImage(textures[1], 7u); + std::array output{}; + const auto outputAddress = static_cast(reinterpret_cast(output.data())); + alignas(4096) static std::array srt{}; + static const auto srtAddress = static_cast(reinterpret_cast(srt.data())); + const std::array outputV{static_cast(outputAddress), static_cast((outputAddress >> 32u) & 0xffffu), 16u, 0xfacu}; + const std::array userData{static_cast(srtAddress), static_cast(srtAddress >> 32u)}; + const std::array capabilities{29u, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess}; + const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x23001u, code, 0, {}}; + request.context.waveSize = 32u; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.bdaAbiVersion = BdaAbi::Version; + request.target.fragmentShaderBarycentricEnabled = false; + request.layout.pushConstantSizeBytes = 128; + + static std::uint64_t syncBegin = 0; + static std::uint64_t syncEnd = 0; + static std::uint64_t scalarSyncWord = 0; + static std::uint64_t waits = 0; + static std::uint64_t rawFirstWords = 0; + static std::uint64_t scalarSyncHits = 0; + static bool spanAwareSync = true; + static bool flushed = false; + static std::array replacementT{}; + + struct FlushReset { + ~FlushReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } flushReset; + + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + if (!flushed && address < syncEnd && syncBegin < address + bytes) { + flushed = true; + ++waits; + std::memcpy(srt.data(), replacementT.data(), sizeof(replacementT)); + } + }); + + const auto pending = +[](std::uint64_t address, std::size_t bytes, std::span) -> AgcDriver::ShaderMemory::PendingWrite { + using Pending = AgcDriver::ShaderMemory::PendingWrite; + if (bytes >= 4096u) { + return address <= srtAddress && srtAddress < address + bytes ? Pending::Sync : Pending::None; + } + if (scalarSyncWord != 0u && address < scalarSyncWord + sizeof(std::uint32_t) && scalarSyncWord < address + bytes) { + ++scalarSyncHits; + return Pending::Sync; + } + if (flushed) return Pending::None; + if (spanAwareSync) { + if (address < syncEnd && syncBegin < address + bytes) return Pending::Sync; + } else { + if (address >= syncBegin && address < syncEnd) return Pending::Sync; + } + if (address >= srtAddress && address + bytes <= srtAddress + 32u) { + if (bytes == sizeof(std::uint32_t) && address < syncBegin) ++rawFirstWords; + return Pending::Raw; + } + return Pending::None; + }; + const auto hookWaits = +[]() -> std::uint64_t { return waits; }; + + for (const int mode : {0, 1, 2}) { + const bool allRaw = mode == 0; + const bool primaryBarrier = mode == 1; + srt.fill(0u); + std::copy(oldT.begin(), oldT.end(), srt.begin()); + std::copy(outputV.begin(), outputV.end(), srt.begin() + 16); + srt[20] = 1u; + syncBegin = allRaw ? srtAddress + 32u : srtAddress + 12u; + syncEnd = srtAddress + 32u; + scalarSyncWord = srtAddress + 84u; + waits = 0u; + rawFirstWords = 0u; + scalarSyncHits = 0u; + spanAwareSync = primaryBarrier; + flushed = false; + replacementT = newT; + + AgcDriver::ShaderMemory memory({}, pending, nullptr, hookWaits); + const auto capture = memory.Capture(request); + require(scalarSyncHits == 0u, "descriptor pendingWrite barrier: a 4-byte scalar read widened its pendingWrite query into a neighbouring dword"); + const bool holdsNew = std::ranges::any_of(capture->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(newT.begin(), newT.end(), value.dwords.begin()); + }); + const bool holdsOld = std::ranges::any_of(capture->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(oldT.begin(), oldT.end(), value.dwords.begin()); + }); + if (allRaw) { + require(!flushed && waits == 0u && rawFirstWords == 8u && holdsOld && !holdsNew, + "descriptor pendingWrite barrier: an all-Raw T# did not return the complete pre-sync T#"); + } else { + require(flushed && waits == 1u, "descriptor pendingWrite barrier: the pending GPU write on the second half of T# was not flushed"); + if (primaryBarrier) { + require(rawFirstWords == 0u, "descriptor pendingWrite primary barrier: leading T# dwords took PendingWrite::Raw despite a pending Sync in the 32-byte descriptor span"); + } else { + require(rawFirstWords > 0u, "descriptor pendingWrite secondary barrier test setup did not exercise initial Raw dword reads before the GPU wait"); + } + require(holdsNew && !holdsOld, + primaryBarrier ? "descriptor pendingWrite primary barrier: capture returned a torn or stale T# instead of the complete post-sync T#" + : "descriptor pendingWrite secondary barrier: cached Raw dwords were not invalidated and re-read after the GPU wait"); + } + } + + static Texture extraTexture; + const auto thirdT = makeImage(extraTexture, 11u); + const auto makeTableV = [](const void* data, std::uint32_t stride, std::uint32_t numRecords) { + const auto base = static_cast(reinterpret_cast(data)); + return std::array{ + static_cast(base), + static_cast((base >> 32u) & 0xffffu) | (stride << 16u), + numRecords, + stride == 0u ? 0x31016facu : 0x01016facu, + }; + }; + const std::vector wholeTableCode{ + 0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, + 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x8f108510u, 0xf42c0502u, 0x20000000u, + 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u, + }; + auto wideTableCode = wholeTableCode; + std::replace(wideTableCode.begin(), wideTableCode.end(), 0x8f108510u, 0x9310b010u); + const std::vector indirectTableCode{ + 0xf4080200u, 0xfa000000u, 0xf4080700u, 0xfa000010u, 0xf4080800u, 0xfa000020u, + 0xf4080100u, 0xfa000030u, 0x7e200500u, 0x8f108210u, 0xf4200444u, 0x20000000u, + 0x8711ff11u, 0x000000ffu, 0x8f128411u, 0xf4240b02u, 0x24000008u, 0xf4040b96u, + 0xfa000010u, 0xf40c0917u, 0xfa000020u, 0xf09c0f08u, 0x01090000u, 0xe0700000u, + 0x80070000u, 0xbf810000u, + }; + alignas(4096) static std::array tableSrt{}; + static std::array, 3> tableMaterials{}; + static std::array tableOutput{}; + const auto tableSrtAddr = static_cast(reinterpret_cast(tableSrt.data())); + const std::array tableUserData{static_cast(tableSrtAddr), static_cast(tableSrtAddr >> 32u)}; + const auto inPageSrtAddr = srtAddress + 512u * sizeof(std::uint32_t); + const std::array inPageUserData{static_cast(inPageSrtAddr), static_cast(inPageSrtAddr >> 32u)}; + const auto fillWholeSrt = [&](std::uint32_t* dst, const void* heap, std::uint32_t stride, std::uint32_t numRecords) { + const auto heapV = makeTableV(heap, stride, numRecords); + const auto matV = makeTableV(tableMaterials.data(), 16u, 3u); + const auto outV = makeTableV(tableOutput.data(), 0u, 16u); + std::fill_n(dst, 20u, 0u); + std::copy(heapV.begin(), heapV.end(), dst); + std::copy(matV.begin(), matV.end(), dst + 8u); + std::copy(outV.begin(), outV.end(), dst + 12u); + }; + RecompileRequest tableReq = request; + tableReq.shader = {ShaderStage::Compute, 0x23010u, wholeTableCode, 0, {}}; + tableReq.context.waveSize = 64u; + tableReq.context.userData = tableUserData; + tableReq.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + RecompileRequest wideTableReq = tableReq; + wideTableReq.shader = {ShaderStage::Compute, 0x23018u, wideTableCode, 0, {}}; + wideTableReq.context.userData = inPageUserData; + + static std::uint32_t phase = 0; + static std::array pendingT0{}; + static std::array pendingT1{}; + static std::array pendingT2{}; + static std::uint64_t record0Addr = 0; + static std::uint64_t record1Addr = 0; + static std::uint64_t record2Addr = 0; + + static std::uint32_t forcedHookCalls = 0; + static std::uint32_t srtRootReads = 0; + static std::uint64_t lastPendingAddr = 0; + static bool gateSecondWaitOnSrtRoot = false; + + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + if (phase == 1u && !flushed && address < syncEnd && syncBegin < address + bytes) { + flushed = true; + ++waits; + std::memcpy(reinterpret_cast(record0Addr), pendingT0.data(), sizeof(pendingT0)); + if (record1Addr != 0u) std::memcpy(reinterpret_cast(record1Addr), pendingT1.data(), sizeof(pendingT1)); + } else if (phase == 2u || phase == 3u) { + if (waits == 0u && address < record1Addr + 32u && record1Addr + 12u < address + bytes) { + ++waits; + std::memcpy(reinterpret_cast(record1Addr), pendingT1.data(), sizeof(pendingT1)); + } else if ((waits == 1u || (phase == 3u && waits >= 1u)) && address < record2Addr + 32u && record2Addr + 12u < address + bytes) { + ++waits; + std::memcpy(reinterpret_cast(record0Addr), pendingT0.data(), sizeof(pendingT0)); + std::memcpy(reinterpret_cast(record2Addr), pendingT2.data(), sizeof(pendingT2)); + } else if (phase == 2u && waits == 2u && address < record0Addr + 32u && record0Addr < address + bytes) { + ++forcedHookCalls; + } + } else if (phase == 5u) { + ++waits; + } + }); + + const auto multiPending = +[](std::uint64_t address, std::size_t bytes, std::span known) -> AgcDriver::ShaderMemory::PendingWrite { + using Pending = AgcDriver::ShaderMemory::PendingWrite; + if (bytes >= 4096u) { + return address <= srtAddress && srtAddress < address + bytes ? Pending::Sync : Pending::None; + } + if (address != lastPendingAddr) { + lastPendingAddr = address; + if (address == srtAddress + 512u * sizeof(std::uint32_t) + 48u) { + ++srtRootReads; + } + } + if (phase == 0u || phase == 5u) return Pending::Raw; + if (phase == 4u) { + if (address >= record0Addr && address + bytes <= record0Addr + 32u) { + if (known.size() == bytes) { + std::memcpy(known.data(), reinterpret_cast(pendingT0.data()) + (address - record0Addr), bytes); + } + return Pending::KnownValue; + } + return Pending::Raw; + } + if (phase == 1u) { + if (flushed) return Pending::None; + if (spanAwareSync) { + if (address < syncEnd && syncBegin < address + bytes) return Pending::Sync; + } else { + if (address >= syncBegin && address < syncEnd) return Pending::Sync; + } + return Pending::Raw; + } + if (waits == 0u) { + if (address >= record1Addr + 12u && address < record1Addr + 32u && bytes == sizeof(std::uint32_t)) return Pending::Sync; + return Pending::Raw; + } + if (waits == 1u || (phase == 3u && waits >= 1u)) { + if (address >= record1Addr && address + bytes <= record1Addr + 32u) return Pending::None; + if ((!gateSecondWaitOnSrtRoot || srtRootReads >= 2u) && address >= record2Addr + 12u && address < record2Addr + 32u && bytes == sizeof(std::uint32_t)) return Pending::Sync; + return Pending::Raw; + } + if (phase == 2u && waits == 2u) { + if (gateSecondWaitOnSrtRoot && + ((srtRootReads == 3u && address == srtAddress + 512u * sizeof(std::uint32_t) + 56u) || + (srtRootReads == 4u && address == srtAddress + 512u * sizeof(std::uint32_t) + 60u))) { + return Pending::Sync; + } + if ((gateSecondWaitOnSrtRoot && address >= srtAddress + 512u * sizeof(std::uint32_t) + 48u && address < srtAddress + 512u * sizeof(std::uint32_t) + 64u) || + (address < record0Addr + 32u && record0Addr < address + bytes)) { + return Pending::Raw; + } + } + return Pending::None; + }; + + srt.fill(0u); + std::copy(oldT.begin(), oldT.end(), srt.begin()); + std::copy(outputV.begin(), outputV.end(), srt.begin() + 16); + srt[20] = 1u; + phase = 0u; + waits = 0u; + flushed = false; + record0Addr = srtAddress; + record1Addr = 0u; + pendingT0 = thirdT; + { + AgcDriver::ShaderMemory knownTransitionMemory({}, multiPending, nullptr, hookWaits); + const auto rawCapture = knownTransitionMemory.Capture(request); + require(!flushed && waits == 0u && std::ranges::any_of(rawCapture->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(oldT.begin(), oldT.end(), value.dwords.begin()); + }), "raw-to-known descriptor transition: initial Raw capture did not cache oldT"); + phase = 4u; + const auto knownCapture = knownTransitionMemory.Capture(request); + require(!flushed && waits == 0u && std::ranges::any_of(knownCapture->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(thirdT.begin(), thirdT.end(), value.dwords.begin()); + }), "raw-to-known descriptor transition: cached Raw descriptor was not invalidated and replaced by KnownValue"); + } + + for (std::uint32_t record = 0; record < 128u; ++record) { + std::copy(oldT.begin(), oldT.end(), srt.begin() + record * 8u); + } + fillWholeSrt(tableSrt.data(), srt.data(), 32u, 128u); + phase = 0u; + waits = 0u; + flushed = false; + record0Addr = srtAddress; + record1Addr = 0u; + pendingT0 = thirdT; + { + AgcDriver::ShaderMemory fullPageMemory({}, multiPending, nullptr, hookWaits); + const auto initialCapture = fullPageMemory.Capture(tableReq); + const auto& initialCol = initialCapture->snapshot.tables.tables.at(0); + require(initialCol.keys == 128u && initialCol.words.at(initialCol.codes.at(0)).dwords == oldT, + "full raw page readWords test: initial raw capture did not populate the table with oldT"); + phase = 4u; + const auto knownCapture = fullPageMemory.Capture(tableReq); + const auto& knownCol = knownCapture->snapshot.tables.tables.at(0); + require(!flushed && waits == 0u && knownCol.words.at(knownCol.codes.at(0)).dwords == thirdT, + "full raw page readWords: cached Raw descriptor words did not consume KnownValue replacement"); + phase = 1u; + pendingT0 = newT; + spanAwareSync = true; + syncBegin = srtAddress + 12u; + syncEnd = srtAddress + 32u; + const auto updatedCapture = fullPageMemory.Capture(tableReq); + const auto& updatedCol = updatedCapture->snapshot.tables.tables.at(0); + require(flushed && waits == 1u && updatedCol.words.at(updatedCol.codes.at(0)).dwords == newT, + "full raw page readWords: readWords() bypassed pendingWrite policy on a fully cached wordwise page"); + } + + for (const bool spanSync : {false, true}) { + std::fill(srt.begin(), srt.end(), 0u); + std::copy(oldT.begin(), oldT.end(), srt.begin()); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 12u); + fillWholeSrt(srt.data() + 512u, srt.data(), 48u, 2u); + phase = 1u; + waits = 0u; + flushed = false; + spanAwareSync = spanSync; + record0Addr = srtAddress; + record1Addr = srtAddress + 48u; + syncBegin = record1Addr + 12u; + syncEnd = record1Addr + 32u; + pendingT0 = newT; + pendingT1 = thirdT; + AgcDriver::ShaderMemory multiRunMemory({}, multiPending, nullptr, hookWaits); + const auto multiRunCapture = multiRunMemory.Capture(wideTableReq); + const auto& col = multiRunCapture->snapshot.tables.tables.at(0); + require(flushed && waits == 1u && col.keys == 2u, "multi-run table wait: second run did not flush"); + require(col.words.at(col.codes.at(0)).dwords == newT && col.words.at(col.codes.at(1)).dwords == thirdT, + "multi-run table wait: a GPU wait on a later run did not rebuild descriptors from an earlier run"); + } + + { + alignas(4096) static std::array indRoot{}; + alignas(4096) static std::array indRecords{}; + alignas(4096) static std::array, 2> indLinks{}; + static std::array indKeys{}; + const auto ptrAddr = [](const void* p) { return static_cast(reinterpret_cast(p)); }; + const auto storePtr = [](std::uint32_t* at, std::uint64_t pointer) { + at[0] = static_cast(pointer); + at[1] = static_cast(pointer >> 32u); + }; + std::fill(srt.begin(), srt.end(), 0u); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 8u); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 24u); + storePtr(indRecords.data() + 2u, ptrAddr(indLinks[0].data())); + storePtr(indRecords.data() + 6u, ptrAddr(indLinks[1].data())); + storePtr(indLinks[0].data() + 4u, srtAddress); + storePtr(indLinks[1].data() + 4u, srtAddress + 64u); + const auto keysV = makeTableV(indKeys.data(), 0u, static_cast(sizeof(indKeys))); + const auto outV = makeTableV(tableOutput.data(), 0u, static_cast(sizeof(tableOutput))); + const auto recsV = makeTableV(indRecords.data(), 0u, static_cast(sizeof(indRecords))); + indRoot.fill(0u); + std::copy(keysV.begin(), keysV.end(), indRoot.begin()); + std::copy(outV.begin(), outV.end(), indRoot.begin() + 4u); + std::copy(recsV.begin(), recsV.end(), indRoot.begin() + 12u); + const std::array indUserData{static_cast(ptrAddr(indRoot.data())), static_cast(ptrAddr(indRoot.data()) >> 32u)}; + RecompileRequest indReq = tableReq; + indReq.shader = {ShaderStage::Compute, 0x23020u, indirectTableCode, 0, {}}; + indReq.context.userData = indUserData; + phase = 1u; + waits = 0u; + flushed = false; + spanAwareSync = false; + record0Addr = srtAddress + 32u; + record1Addr = srtAddress + 96u; + syncBegin = record1Addr + 12u; + syncEnd = record1Addr + 32u; + pendingT0 = newT; + pendingT1 = thirdT; + AgcDriver::ShaderMemory indirectMemory({}, multiPending, nullptr, hookWaits); + const auto indCapture = indirectMemory.Capture(indReq); + const auto& col = indCapture->snapshot.tables.tables.at(0); + require(flushed && waits == 1u && col.keys == 2u, "indirect table wait: second record did not flush"); + require(col.words.at(col.codes.at(0)).dwords == newT && col.words.at(col.codes.at(1)).dwords == thirdT, + "indirect table wait: a GPU wait on a later indirect record did not rebuild an earlier record"); + } + + const auto postForcedSrtAddr = srtAddress + 256u * sizeof(std::uint32_t); + const std::array postForcedUserData{static_cast(postForcedSrtAddr), static_cast(postForcedSrtAddr >> 32u)}; + RecompileRequest postForcedReq = request; + postForcedReq.context.userData = postForcedUserData; + + for (const int mode : {0, 1, 2}) { + const bool stridedRuns = mode != 0; + gateSecondWaitOnSrtRoot = mode == 2; + const std::uint32_t stride = stridedRuns ? 48u : 32u; + const std::size_t stepWords = stride / sizeof(std::uint32_t); + std::fill(srt.begin(), srt.end(), 0u); + std::copy(oldT.begin(), oldT.end(), srt.begin()); + std::copy(oldT.begin(), oldT.end(), srt.begin() + stepWords); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 2u * stepWords); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 256u); + std::copy(outputV.begin(), outputV.end(), srt.begin() + 256u + 16u); + srt[256u + 20u] = 1u; + fillWholeSrt(srt.data() + 512u, srt.data(), stride, 3u); + RecompileRequest twoWaitReq = stridedRuns ? wideTableReq : tableReq; + twoWaitReq.context.userData = inPageUserData; + phase = 2u; + waits = 0u; + forcedHookCalls = 0u; + srtRootReads = 0u; + lastPendingAddr = 0u; + flushed = false; + record0Addr = srtAddress; + record1Addr = srtAddress + stride; + record2Addr = srtAddress + 2u * stride; + pendingT0 = newT; + pendingT1 = thirdT; + pendingT2 = newT; + AgcDriver::ShaderMemory twoWaitMemory({}, multiPending, nullptr, hookWaits); + const auto twoWaitCapture = twoWaitMemory.Capture(twoWaitReq); + const auto& col = twoWaitCapture->snapshot.tables.tables.at(0); + require(waits == 2u && forcedHookCalls == 15u && (!gateSecondWaitOnSrtRoot || srtRootReads == 5u) && col.keys == 3u, + "multi-wait readWords: forced third attempt did not execute or nested forceSync scope was not preserved"); + require(col.words.at(col.codes.at(0)).dwords == newT && col.words.at(col.codes.at(1)).dwords == thirdT && col.words.at(col.codes.at(2)).dwords == newT, + "multi-wait readWords: retry pass accepted an unstable epoch after a second invalidation"); + phase = 5u; + waits = 0u; + forcedHookCalls = 0u; + const auto normalAfterForced = twoWaitMemory.Capture(postForcedReq); + const auto cachedAfterForced = twoWaitMemory.Capture(postForcedReq); + require(waits == 0u && forcedHookCalls == 0u && std::ranges::any_of(normalAfterForced->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(oldT.begin(), oldT.end(), value.dwords.begin()); + }) && std::ranges::any_of(cachedAfterForced->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(oldT.begin(), oldT.end(), value.dwords.begin()); + }), "forced pass scope: a normal pass over Raw words after a forced third attempt waited on the hook or bumped rawInvalidations"); + + std::fill(srt.begin(), srt.end(), 0u); + std::copy(oldT.begin(), oldT.end(), srt.begin()); + std::copy(oldT.begin(), oldT.end(), srt.begin() + stepWords); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 2u * stepWords); + std::copy(oldT.begin(), oldT.end(), srt.begin() + 256u); + std::copy(outputV.begin(), outputV.end(), srt.begin() + 256u + 16u); + srt[256u + 20u] = 1u; + fillWholeSrt(srt.data() + 512u, srt.data(), stride, 3u); + phase = 3u; + waits = 0u; + forcedHookCalls = 0u; + srtRootReads = 0u; + lastPendingAddr = 0u; + flushed = false; + AgcDriver::ShaderMemory unstableMemory({}, multiPending, nullptr, hookWaits); + bool refused = false; + try { + static_cast(unstableMemory.Capture(twoWaitReq)); + } catch (const std::runtime_error&) { + refused = true; + } + require(refused && waits >= 3u, "unstable epoch refusal: capture succeeded despite invalidation on every retry attempt"); + phase = 5u; + waits = 0u; + forcedHookCalls = 0u; + const auto normalAfterUnstable = unstableMemory.Capture(postForcedReq); + require(waits == 0u && forcedHookCalls == 0u && std::ranges::any_of(normalAfterUnstable->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(oldT.begin(), oldT.end(), value.dwords.begin()); + }), "forced pass exception scope: a normal pass over Raw words after a throwing forced attempt waited on the hook or bumped rawInvalidations"); + } + gateSecondWaitOnSrtRoot = false; } void verifyOutOfRangeConstantReads() { @@ -3454,6 +3924,38 @@ void verifyImageTableMaskedOffset() { require(codes.size() == 4u && codes[0] == codes[3] && codes[2] == Abi::NullCode && codes[1] != codes[0] && (codes[1] & Abi::PoisonFlag) == 0u, "image table masked offset: the whole table map is wrong"); } +void verifyImageTableFirstLaneScale() { + using namespace ShaderRecompiler; + namespace Abi = ImageTableAbi; + static TableTexture textures[2]; + alignas(4096) static TableGuest guest; + auto& heap = guest.heap; + std::fill(heap.begin(), heap.end(), std::array{}); + heap[0] = TableT(textures[0].bytes.data(), TableFormat8888UNorm, TableType2D); + heap[3] = TableT(textures[1].bytes.data(), TableFormat32UInt, TableType2D); + heap[9] = heap[0]; + FillTableSrt(guest, heap.data(), 32u, 12u); + const auto userData = TableUserData(guest); + const auto scaled = [](std::initializer_list offset) { + std::vector code(TableWholeCode); + const auto first = std::find(code.begin(), code.end(), 0x7e200500u); + require(first != code.end() && first[1] == 0x8f108510u, "image table first-lane scale: the program has no v_readfirstlane_b32 and s_lshl_b32"); + code.insert(code.erase(first, first + 2), offset); + return code; + }; + const auto code = scaled({0x34020085u, 0xd7460001u, 0x04050301u, 0x7e200501u}); + const auto request = TableRequest(code, userData); + const auto plan = GetResourcePlan(request); + require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u && plan->info.images[0].tableOperation == TableOperation::Supported, "image table first-lane scale: the T# was not planned as a table"); + const auto& column = plan->descriptorSources.at(plan->info.images[0].source).tableColumn; + require(column.has_value() && column->stride == 96u && column->addend == 0u && column->offset == 0u && column->dwordCount == 8u && column->unkeyed, "image table first-lane scale: the column is wrong"); + const auto compiled = CompileTable(request); + const auto& snapshot = compiled.capture->snapshot.tables.tables.at(0); + require(snapshot.records == 4u && snapshot.keys == 4u && snapshot.fault == 0u, "image table first-lane scale: the snapshot does not hold the four 96-byte records"); + const auto codes = TableCodes(TableMap(*compiled.result), 0); + require(codes.size() == 4u && codes[0] == codes[3] && codes[2] == Abi::NullCode && codes[1] != codes[0] && (codes[0] & Abi::PoisonFlag) == 0u && (codes[1] & Abi::PoisonFlag) == 0u, "image table first-lane scale: the table map is wrong"); +} + void verifyImageTableRegistration() { using namespace ShaderRecompiler; namespace Abi = ImageTableAbi; @@ -5740,7 +6242,10 @@ void verifyPixelRequestSerialization() { minimal.context.pixel->layerExported = true; minimal.context.pixel->ditheredAlphaToCoverage = true; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQS8AAAA=", "new requests did not use serialization version 47"); + require(requestPrefix(encoded, 8u) == "NVNQQTEAAAA=", "new requests did not use serialization version 49"); + auto withBarycentric = minimal; + withBarycentric.target.fragmentBarycentricBuiltins = true; + require(serializer.Deserialize(serializer.Serialize(withBarycentric)).request.target.fragmentBarycentricBuiltins && !serializer.Deserialize(encoded).request.target.fragmentBarycentricBuiltins, "barycentric built-ins were lost in serialization"); const std::array fallbacks{0x40u, 0x1ac4u}; auto withFallbacks = minimal; withFallbacks.context.linkedCallFallbacks = fallbacks; @@ -5858,7 +6363,7 @@ void verifyPixelRequestSerialization() { for (std::size_t bytes = 0; bytes < 45u; ++bytes) { expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted"); } - for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQTAAAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQTIAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } static constexpr std::string_view version18Request{ @@ -5930,6 +6435,132 @@ std::string encodeBase64(const std::vector& bytes) { return text; } +void verifyDepthExportClamp() { + using namespace ShaderRecompiler; + static constexpr std::array code{0xf8001881u, 0x00000000u, 0xbf810000u}; + ShaderPixelStageInfo pixel{}; + pixel.inputAddr = PixelInputBit(PixelInput::PerspectiveCenter); + pixel.hasPerspectiveCenterVgpr = true; + pixel.depthExportEnable = true; + pixel.depthExportMin = 0.25f; + pixel.depthExportMax = 0.75f; + RecompileRequest request{}; + request.shader = {ShaderStage::Fragment, 0x30000u, code, 0, {}}; + request.context.waveSize = 64; + request.context.pixel = pixel; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 64; + request.layout.pushConstantSizeBytes = 128; + request.useCache = false; + const auto result = Recompile(request); + const auto& words = result.spirv.Words(); + std::map floats; + std::vector> clamps; + std::vector stored; + std::uint32_t depthVariable = 0; + bool depthReplacing = false; + for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) { + const auto op = static_cast(words[at] & 0xffffu); + if (op == spv::OpConstant && (words[at] >> 16u) == 4u) floats[words[at + 2]] = words[at + 3]; + if (op == spv::OpExtInst && words[at + 4] == 81u) clamps.push_back({words[at + 2], words[at + 6], words[at + 7]}); + if (op == spv::OpDecorate && words[at + 2] == spv::DecorationBuiltIn && words[at + 3] == spv::BuiltInFragDepth) depthVariable = words[at + 1]; + if (op == spv::OpStore) stored.push_back(words[at + 1]), stored.push_back(words[at + 2]); + if (op == spv::OpExecutionMode && words[at + 2] == spv::ExecutionModeDepthReplacing) depthReplacing = true; + } + require(depthVariable != 0u && depthReplacing, "depth export: the pixel shader does not replace gl_FragDepth"); + require(clamps.size() == 1u && floats[clamps[0][1]] == 0x3e800000u && floats[clamps[0][2]] == 0x3f400000u, "depth export: the exported depth was not NClamped to [ZMIN, ZMAX]"); + bool clampStored = false; + for (std::size_t i = 0; i + 1 < stored.size(); i += 2) clampStored = clampStored || (stored[i] == depthVariable && stored[i + 1] == clamps[0][0]); + require(clampStored, "depth export: gl_FragDepth was not written from the clamped value"); + const auto replay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(request)); + require(replay.request.context.pixel->depthExportMin == 0.25f && replay.request.context.pixel->depthExportMax == 0.75f, "depth export: the clamp did not survive serialization"); + { + auto tail = request; + tail.target.largePixelFunctionLdsBuffer = true; + tail.target.pixelFunctionStorageDwords = 2048u; + const auto both = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(tail)); + require(both.request.target.largePixelFunctionLdsBuffer && both.request.target.pixelFunctionStorageDwords == 2048u && + both.request.context.pixel->depthExportMin == 0.25f && both.request.context.pixel->depthExportMax == 0.75f, + "depth export: a version 49 request lost the bounds or the version 45 target tail"); + auto bytes = decodeBase64(RequestSerializer{}.Serialize(tail)); + require(bytes.size() > 8u && bytes[4] == 49u, "depth export: a depth exporting request did not use serialization version 49"); + static constexpr std::array boundBytes{0x00u, 0x00u, 0x80u, 0x3eu, 0x00u, 0x00u, 0x40u, 0x3fu}; + const auto found = std::search(bytes.begin(), bytes.end(), boundBytes.begin(), boundBytes.end()); + require(found != bytes.end() && std::search(found + 1, bytes.end(), boundBytes.begin(), boundBytes.end()) == bytes.end(), + "depth export: the clamp bounds were not written exactly once"); + bytes.erase(found, found + boundBytes.size()); + for (const std::uint8_t legacyVersion : {48u, 47u, 46u}) { + if (legacyVersion == 47u) bytes.pop_back(); + bytes[4] = legacyVersion; + const auto legacy = RequestSerializer{}.Deserialize(encodeBase64(bytes)); + require(legacy.request.context.pixel->depthExportEnable && legacy.request.context.pixel->depthExportMin == 0.0f && + legacy.request.context.pixel->depthExportMax == 1.0f && legacy.request.target.largePixelFunctionLdsBuffer && + legacy.request.target.pixelFunctionStorageDwords == 2048u, + legacyVersion == 46u ? "depth export: a version 46 depth exporting request did not read with default bounds and its target tail" + : legacyVersion == 47u ? "depth export: a version 47 depth exporting request did not read with default bounds and its target tail" + : "depth export: a version 48 depth exporting request did not read with default bounds and its target tail"); + } + for (const bool depthExport : {false, true}) { + auto dithered = request; + auto ditheredPixel = *dithered.context.pixel; + ditheredPixel.depthExportEnable = depthExport; + ditheredPixel.ditheredAlphaToCoverage = true; + ditheredPixel.ditheredAlphaSamples = 2u; + dithered.context.pixel = ditheredPixel; + auto ditheredBytes = decodeBase64(RequestSerializer{}.Serialize(dithered)); + require(ditheredBytes.size() > 8u && ditheredBytes[4] == 49u, "a two-sample dithered request did not use serialization version 49"); + if (depthExport) { + const auto bounds = std::search(ditheredBytes.begin(), ditheredBytes.end(), boundBytes.begin(), boundBytes.end()); + require(bounds != ditheredBytes.end(), "a two-sample dithered depth exporting request lacks the clamp bounds"); + ditheredBytes.erase(bounds, bounds + boundBytes.size()); + } + ditheredBytes[4] = 48u; + const auto main48 = RequestSerializer{}.Deserialize(encodeBase64(ditheredBytes)); + require(main48.request.context.pixel->ditheredAlphaToCoverage && main48.request.context.pixel->ditheredAlphaSamples == 2u && main48.request.context.pixel->depthExportEnable == depthExport && + (!depthExport || (main48.request.context.pixel->depthExportMin == 0.0f && main48.request.context.pixel->depthExportMax == 1.0f)), + "a version 48 two-sample dithered request, as main writes it with no depth tail, did not decode as written"); + } + } + std::vector key; + RecompileCacheKey::Build(request, key); + const auto first = key; + auto other = request; + auto changed = pixel; + changed.depthExportMax = 1.0f; + other.context.pixel = changed; + RecompileCacheKey::Build(other, key); + require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "depth export: the cache keys ignore the clamp"); + auto plain = pixel; + plain.depthExportEnable = false; + auto unexported = request; + unexported.context.pixel = plain; + RecompileCacheKey::Build(unexported, key); + const auto plainKey = key; + plain.depthExportMin = 0.0f; + plain.depthExportMax = 1.0f; + unexported.context.pixel = plain; + RecompileCacheKey::Build(unexported, key); + require(key == plainKey, "depth export: the clamp keyed a shader without depth export"); + auto open = pixel; + open.depthExportMin = -std::numeric_limits::infinity(); + open.depthExportMax = std::numeric_limits::infinity(); + auto unclamped = request; + unclamped.context.pixel = open; + const auto unclampedResult = Recompile(unclamped); + const auto& openWords = unclampedResult.spirv.Words(); + bool openClamp = false; + bool openStore = false; + std::uint32_t openDepth = 0; + for (std::size_t at = 5; at < openWords.size() && (openWords[at] >> 16u) != 0; at += openWords[at] >> 16u) { + const auto op = static_cast(openWords[at] & 0xffffu); + if (op == spv::OpExtInst && openWords[at + 4] == 81u) openClamp = true; + if (op == spv::OpDecorate && openWords[at + 2] == spv::DecorationBuiltIn && openWords[at + 3] == spv::BuiltInFragDepth) openDepth = openWords[at + 1]; + if (op == spv::OpStore && openWords[at + 1] == openDepth && openDepth != 0u) openStore = true; + } + require(!openClamp && openStore, "depth export: an unclamped export (DISABLE_VIEWPORT_CLAMP) was clamped or not stored"); +} + void verifyVertexInfoVersion19() { using namespace ShaderRecompiler; const std::array code{0xbf810000u}; @@ -5954,6 +6585,7 @@ void verifyVertexInfoVersion19() { version20Vertex.pop_back(); version20Vertex.pop_back(); version20Vertex.pop_back(); + version20Vertex.pop_back(); version20Vertex[4] = 20u; const auto replay20Vertex = serializer.Deserialize(encodeBase64(version20Vertex)); require(replay20Vertex.request.context.vertex.has_value() && replay20Vertex.request.context.vertex->paClVsOutCntl == 0x89abcdefu, "a main version20 request lost PA_CL_VS_OUT_CNTL"); @@ -5967,6 +6599,7 @@ void verifyVertexInfoVersion19() { legacy.pop_back(); legacy.pop_back(); legacy.pop_back(); + legacy.pop_back(); legacy.erase(legacy.begin() + offset, legacy.begin() + offset + 4u); for (const auto version : {16u, 17u, 18u, 19u}) { legacy[4] = static_cast(version); @@ -6013,11 +6646,11 @@ void verifyVertexInfoVersion19() { const auto mergingBytes = decodeBase64(serializer.Serialize(merging)); require(mergingBytes.size() == plainPixel.size() && std::equal(plainPixel.begin(), plainPixel.begin() + mergeOffset, mergingBytes.begin()) && mergingBytes[mergeOffset] == 3u && mergingBytes[mergeOffset + 1] == 0x0fu && mergingBytes[mergeOffset + 2] == 0xf0u && mergingBytes[mergeOffset + 5] == 2u && mergingBytes[mergeOffset + 6] == 1u && mergingBytes[mergeOffset + 34] == 0xefu && mergingBytes[mergeOffset + 37] == 0x89u && mergingBytes[mergeOffset + 38] == 1u && mergingBytes[mergeOffset + 39] == 1u && std::equal(plainPixel.begin() + mergeOffset + static_cast(mergeBytes), plainPixel.end(), mergingBytes.begin() + mergeOffset + static_cast(mergeBytes)), "the color target merge is not the 40 bytes the version 46 pixel info adds after the sample order"); require(serializer.Deserialize(serializer.Serialize(merging)).request.context.pixel->targetMerge == merging.context.pixel->targetMerge, "a serialized request lost its color target merge"); - // A version 43 request: no color target merge, and no fused arithmetic flag, pixel function LDS flag or storage size at the end. + // A version 43 request: no color target merge, and no fused arithmetic flag, pixel function LDS flag, storage size or barycentric built-ins flag at the end. const auto version43Bytes = [&](std::vector bytes) { require(bytes.end() - bytes.begin() > mergeOffset + static_cast(mergeBytes) && std::equal(plainPixel.begin() + mergeOffset, plainPixel.begin() + mergeOffset + static_cast(mergeBytes), bytes.begin() + mergeOffset), "a replay fixture does not hold the default color target merge after the sample order"); bytes.erase(bytes.begin() + mergeOffset, bytes.begin() + mergeOffset + static_cast(mergeBytes)); - bytes.resize(bytes.size() - sizeof(std::uint32_t) - 2u); + bytes.resize(bytes.size() - sizeof(std::uint32_t) - 3u); return bytes; }; auto version42 = version43Bytes(plainPixel); @@ -6179,13 +6812,14 @@ void verifyDepthRemapRequests() { const auto plainBytes = decodeBase64(plain); const auto remappedBytes = decodeBase64(remapped); constexpr std::size_t pointTailBytes = 13u; - require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 13u + pointTailBytes && std::equal(plainBytes.begin(), plainBytes.end() - 13 - pointTailBytes, remappedBytes.begin()) && std::equal(plainBytes.end() - 6 - pointTailBytes, plainBytes.end(), remappedBytes.end() - 6 - pointTailBytes), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags, the point tail, the fused arithmetic flag, the pixel function LDS flag and the pixel function storage size of a vertex request"); + require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 14u + pointTailBytes && std::equal(plainBytes.begin(), plainBytes.end() - 14 - pointTailBytes, remappedBytes.begin()) && std::equal(plainBytes.end() - 7 - pointTailBytes, plainBytes.end(), remappedBytes.end() - 7 - pointTailBytes), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags, the point tail, the fused arithmetic flag, the pixel function LDS flag, the pixel function storage size and the barycentric built-in flag of a vertex request"); for (const std::uint8_t legacyVersion : {32u, 34u}) { auto legacyBytes = remappedBytes; legacyBytes.resize(legacyBytes.size() - sizeof(std::uint32_t)); legacyBytes.pop_back(); legacyBytes.pop_back(); legacyBytes.pop_back(); + legacyBytes.pop_back(); legacyBytes[4] = legacyVersion; legacyBytes.resize(legacyBytes.size() - 6u - pointTailBytes); const auto legacy = serializer.Deserialize(encodeBase64(legacyBytes)); @@ -6197,6 +6831,7 @@ void verifyDepthRemapRequests() { version35.pop_back(); version35.pop_back(); version35.pop_back(); + version35.pop_back(); version35[4] = 35u; version35.resize(version35.size() - 1u - pointTailBytes); const auto replay35 = serializer.Deserialize(encodeBase64(version35)); @@ -6244,8 +6879,8 @@ void verifyMergedHullConfiguration() { require(!serializer.Deserialize(separate).request.graphics->tessellation->mergedHull, "a separate hull configuration was read as merged"); constexpr std::size_t pointTailBytes = 13u; auto legacy = decodeBase64(merged); - require(legacy.size() > 54u + pointTailBytes && legacy[4] == 47u, "a merged hull request did not use serialization version 47"); - legacy.resize(legacy.size() - sizeof(std::uint32_t) - 2u - pointTailBytes); + require(legacy.size() > 55u + pointTailBytes && legacy[4] == 49u, "a merged hull request did not use serialization version 49"); + legacy.resize(legacy.size() - sizeof(std::uint32_t) - 3u - pointTailBytes); legacy.pop_back(); require(legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration"); legacy.erase(legacy.end() - 52); @@ -6357,7 +6992,7 @@ void verifyLegacyPixelRequests() { require(!pixel.orderedPixelShader, "a legacy request became a primitive-ordered pixel shader"); require(request.shader.stage == ShaderStage::Fragment && request.shader.code.size() == 1u && request.shader.code[0] == 0xbf810000u && !request.context.vertex.has_value() && request.context.memory.empty(), "legacy guest context was misaligned"); require(request.target.vulkanVersion == 0x00401000u && request.target.spirvVersion == 0x00010300u && request.target.subgroupSize == 64u && request.layout.firstBinding == 11u && request.layout.pushConstantSizeBytes == 128u, "legacy target or binding layout was misaligned"); - require(request.useCache == (version == 1u) && request.target.nonConstantImageOffsets == (version >= 6u) && request.target.srgbDecodeFormats == 0u && !request.target.narrowSubgroupClock && !request.target.float64DenormPreserve && !request.target.registrationModules && !request.target.float16DenormPreserve, "legacy request trailer was misread"); + require(request.useCache == (version == 1u) && request.target.nonConstantImageOffsets == (version >= 6u) && request.target.srgbDecodeFormats == 0u && !request.target.narrowSubgroupClock && !request.target.float64DenormPreserve && !request.target.registrationModules && !request.target.float16DenormPreserve && !request.target.fragmentBarycentricBuiltins, "legacy request trailer was misread"); const auto upgraded = serializer.Deserialize(serializer.Serialize(request)); require(upgraded.request.context.pixel->targetExportMapping == pixel.targetExportMapping && RecompileCacheKey::ContextHash(upgraded.request) == RecompileCacheKey::ContextHash(request), "upgrading a legacy capture changed its pixel mapping"); } @@ -8418,6 +9053,27 @@ void verifyCubeDepthCompareMode() { require(modes[selected].cube && (modes[selected].emulatedCompare & EmulatedCompare::Enabled) != 0u, "cube color comparison did not select its emulated runtime mode"); } +void verifyShortCubeMode() { + using namespace ShaderRecompiler; + ImageResource image{}; + image.resourceClass = ImageResourceClass::Sampled; + image.dimension = RdnaImageDimension::Dim2DArray; + DescriptorValue descriptor{{0x02143930u, 71u << 20u, 0u, 0xb1b00facu, 4u, 0u, 0u, 0u}, 8u}; + const auto modes = ResourceMaterializer::RuntimeImageModes(image); + require(modes[ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)].cube, "a sampled cube with five slices did not select a cube runtime mode"); + image.sampledOnly = false; + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)); }, "fewer than six slices is implemented only for sampling", "a loaded cube with five slices was accepted"); + for (const auto range : {0u, 5u, 5u | (5u << 16u), 6u}) { + descriptor.dwords[4] = range; + require(modes[ResourceMaterializer::RuntimeImageMode(image, descriptor, modes)].cube, "a loaded cube with six faces or more was rejected"); + } + image.sampledOnly = true; + image.resourceClass = ImageResourceClass::Storage; + image.written = true; + descriptor.dwords[4] = 3u | (1u << 16u); + expectFailure([&] { static_cast(ResourceMaterializer::RuntimeImageMode(image, descriptor, ResourceMaterializer::RuntimeImageModes(image))); }, "fewer than six slices is implemented only for sampling", "a stored cube with three slices was accepted"); +} + void verifyVolumeArrayLoadMode() { using namespace ShaderRecompiler; const DescriptorValue descriptor{{0x02466600u, 0x01400000u, 0u, 0xa0500204u, 1u, 0x00700000u, 0u, 0u}, 8u}; @@ -8711,6 +9367,7 @@ int main(int argc, char** argv) { verifyImageTableBulkKeyScan(); verifyImageTableSrtKey(); verifyImageTableMaskedOffset(); + verifyImageTableFirstLaneScale(); verifyImageTableKeyUnion(); verifyImageTableLateKey(); verifyImageTableUnkeyedAccess(); @@ -8816,6 +9473,7 @@ int main(int argc, char** argv) { verifyVertexInfoVersion19(); verifyDepthRemapRequests(); verifyMergedHullConfiguration(); + verifyDepthExportClamp(); verifyPositionExportComponents(); verifyLegacyPixelRequests(); verifyPixelExportReplay(); @@ -8840,6 +9498,7 @@ int main(int argc, char** argv) { verifyGuardedNullPointers(); verifyFmaskImageMode(); verifyCubeDepthCompareMode(); + verifyShortCubeMode(); verifyVolumeArrayLoadMode(); verifyArrayVolumeStoreMode(); verifyMultisamplePlaneReadMode(); diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp index 2bb13e5283..5682d5838a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp @@ -24,7 +24,19 @@ constexpr std::uint32_t DsOperand32 = 0x00000401u; constexpr std::uint32_t FlatAtomicAddU32 = 0xdcc90000u; constexpr std::uint32_t FlatAtomicOperands = 0x0a7d0402u; +struct LockFence { + std::uint32_t scope = 0u; + std::uint32_t semantics = 0u; + std::size_t accessesBefore = 0; +}; + +struct LockRegion { + std::vector fences; + std::size_t accesses = 0; +}; + struct Usage { + std::vector regions; std::size_t elects = 0; std::size_t exchanges = 0; std::size_t releases = 0; @@ -40,7 +52,7 @@ void Require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } -std::vector RecompileProgram(std::span code) { +std::vector RecompileProgram(std::span code, bool ldsLockFences) { const std::array capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess}; const std::array userData{}; const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; @@ -59,6 +71,7 @@ std::vector RecompileProgram(std::span code) request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; request.target.maxWorkgroupInvocations = 1024; request.target.maxWorkgroupSharedMemoryBytes = 49152; + request.target.ldsLockFences = ldsLockFences; request.layout = {0, 0, 0, 128}; request.useCache = false; auto result = Recompile(request); @@ -101,8 +114,10 @@ Usage Scan(std::span words) { if (op == spv::OpBranchConditional && count >= 4u && words[cursor + 1u] == laneZeroCondition) laneZeroBody = words[cursor + 2u]; if (op == spv::OpConstant && count == 4) constants[words[cursor + 2]] = words[cursor + 3]; if (op == spv::OpGroupNonUniformElect) ++usage.elects; + if (locked && (op == spv::OpLoad || op == spv::OpStore || op == spv::OpAtomicIAdd || op == spv::OpAtomicCompareExchange)) ++usage.regions.back().accesses; if (op == spv::OpAtomicCompareExchange) { ++usage.exchanges; + if (!locked) usage.regions.emplace_back(); locked = true; if (count >= 4u) lockPointer = words[cursor + 3u]; } @@ -116,7 +131,13 @@ Usage Scan(std::span words) { ++usage.storeReleases; locked = false; } - if (op == spv::OpMemoryBarrier && locked) usage.fenceBetweenLock = true; + if (op == spv::OpMemoryBarrier && locked) { + usage.fenceBetweenLock = true; + const auto scope = constants.find(words[cursor + 1]); + const auto semantics = constants.find(words[cursor + 2]); + Require(scope != constants.end() && semantics != constants.end(), "a memory barrier inside the workgroup lock has a non-constant scope or semantics"); + usage.regions.back().fences.push_back({scope->second, semantics->second, usage.regions.back().accesses}); + } if (op == spv::OpAtomicIAdd) { ++usage.adds; if (locked) usage.addBetweenLock = true; @@ -156,11 +177,11 @@ Usage Scan(std::span words) { return usage; } -Usage Compile(std::initializer_list body) { +Usage Compile(std::initializer_list body, bool ldsLockFences = false) { std::vector code{0x7e020280u, 0x7e0402c1u, 0x7e060280u, 0x7e080281u}; code.insert(code.end(), body); code.insert(code.end(), {0xbf8cc07fu, 0xbf810000u}); - return Scan(RecompileProgram(code)); + return Scan(RecompileProgram(code, ldsLockFences)); } void Check64BitOnlyLocks() { @@ -190,6 +211,28 @@ void CheckFlatAtomicTakesLockWith64Bit() { Require(!usage.fenceBetweenLock, "a 32-bit flat atomic into the LDS aperture emits a memory barrier while it holds the workgroup lock"); } +void RequireFencedRegions(const Usage& usage, std::size_t expected, const std::string& name) { + Require(usage.regions.size() == expected, name + " with LDS lock fences took the workgroup lock " + std::to_string(usage.regions.size()) + " times, expected " + std::to_string(expected)); + for (const auto& region : usage.regions) { + Require(region.fences.size() == 2, name + " with LDS lock fences emitted " + std::to_string(region.fences.size()) + " memory barriers inside the workgroup lock, expected 2"); + const auto& acquire = region.fences.front(); + const auto& release = region.fences.back(); + Require(acquire.scope == spv::ScopeWorkgroup && release.scope == spv::ScopeWorkgroup, name + " with LDS lock fences emitted a lock fence that is not workgroup scope"); + Require((acquire.semantics & spv::MemorySemanticsAcquireMask) != 0 && (acquire.semantics & spv::MemorySemanticsWorkgroupMemoryMask) != 0, name + " with LDS lock fences does not start its critical section with an acquire fence on workgroup memory"); + Require((release.semantics & spv::MemorySemanticsReleaseMask) != 0 && (release.semantics & spv::MemorySemanticsWorkgroupMemoryMask) != 0, name + " with LDS lock fences does not end its critical section with a release fence on workgroup memory"); + Require(region.accesses != 0 && acquire.accessesBefore == 0 && release.accessesBefore == region.accesses, name + " with LDS lock fences accesses LDS outside the fences inside the workgroup lock"); + } +} + +void CheckLockFences() { + RequireFencedRegions(Compile({DsAddU64, DsOperands}, true), 1, "ds_add_u64"); + const auto mixed = Compile({DsAddU64, DsOperands, DsAddU32Hi, DsOperand32}, true); + RequireFencedRegions(mixed, 2, "ds_add_u32 with ds_add_u64"); + Require(mixed.adds == 1 && mixed.addBetweenLock, "with LDS lock fences, the 32-bit OpAtomicIAdd is not between the lock acquire and the lock release"); + const auto unfenced = Compile({DsAddU32Hi, DsOperand32}, true); + Require(unfenced.regions.empty() && !unfenced.fenceBetweenLock, "with LDS lock fences, a program with only ds_add_u32 got a workgroup lock"); +} + void CheckFlatAtomicOnlyIsUnchanged() { const auto usage = Compile({FlatAtomicAddU32, FlatAtomicOperands}); Require(usage.elects == 0, "a program with only a 32-bit flat atomic got a workgroup lock: elect " + std::to_string(usage.elects)); @@ -212,6 +255,7 @@ int main() { CheckFlatAtomicTakesLockWith64Bit(); CheckFlatAtomicOnlyIsUnchanged(); Check32BitOnlyIsUnchanged(); + CheckLockFences(); } catch (const std::exception& error) { std::fprintf(stderr, "SpirvLdsAtomicLock: %s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp index 956897cb51..84b682ac38 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp @@ -474,7 +474,107 @@ void testInternedSpirvReleasesUnusedWords() { check(std::ranges::equal(again.Words(), words) && released.Lock().empty(), "SPIR-V interned again after its release changed its words"); } +std::vector foldedBitcasts(bool separateBlocks, bool differentInput, bool differentType, std::uint32_t nanOpcode, std::uint32_t decorationMask = 0u) { + SpirvModule module; + module.EmitCapability(spv::CapabilityShader); + module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); + const auto voidType = module.Type(spv::OpTypeVoid); + const auto boolType = module.Type(spv::OpTypeBool); + const auto wordType = module.Type(spv::OpTypeInt, 32u, 0u); + const auto floatType = module.Type(spv::OpTypeFloat, 32u); + const auto otherType = differentType ? module.Type(spv::OpTypeInt, 32u, 1u) : floatType; + const auto vectorType = module.Type(spv::OpTypeVector, wordType, 3u); + const auto input = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassInput, vectorType), spv::StorageClassInput); + module.AddAnnotation(spv::OpDecorate, input, spv::DecorationBuiltIn, spv::BuiltInGlobalInvocationId); + const auto firstOutputType = nanOpcode != 0u ? boolType : floatType; + const auto secondOutputType = nanOpcode != 0u ? boolType : otherType; + const std::array outputs{ + module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassPrivate, firstOutputType), spv::StorageClassPrivate), + module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassPrivate, secondOutputType), spv::StorageClassPrivate)}; + const auto mainId = module.AllocateId(); + const auto functionType = module.Type(spv::OpTypeFunction, voidType); + const auto zero = module.Constant(spv::OpConstant, wordType, 0u); + const auto trueValue = module.Constant(spv::OpConstantTrue, boolType); + module.EmitEntryPoint(spv::ExecutionModelGLCompute, mainId, "main", {input}); + module.AddExecutionMode(mainId, spv::ExecutionModeLocalSize, 1u, 1u, 1u); + module.AddFunction(spv::OpFunction, voidType, mainId, spv::FunctionControlMaskNone, functionType); + module.AddFunction(spv::OpLabel, module.AllocateId()); + const auto loaded = module.AllocateId(); + const auto first = module.AllocateId(); + const auto second = module.AllocateId(); + const auto alias = module.AllocateId(); + module.AddFunction(spv::OpLoad, vectorType, loaded, input); + module.AddFunction(spv::OpCompositeExtract, wordType, first, loaded, 0u); + module.AddFunction(spv::OpCompositeExtract, wordType, second, loaded, 1u); + module.AddFunction(spv::OpSelect, wordType, alias, trueValue, first, second); + const auto emit = [&](bool other) { + const auto cast = module.AllocateId(); + module.AddFunction(spv::OpBitcast, other ? otherType : floatType, cast, other ? (differentInput ? second : alias) : first); + if ((decorationMask & (other ? 2u : 1u)) != 0u) module.AddAnnotation(spv::OpDecorate, cast, spv::DecorationRelaxedPrecision); + if ((decorationMask & (other ? 8u : 4u)) != 0u) { + const auto group = module.AllocateId(); + module.AddAnnotation(spv::OpDecorationGroup, group); + module.AddAnnotation(spv::OpDecorate, group, spv::DecorationRelaxedPrecision); + module.AddAnnotation(spv::OpGroupDecorate, group, cast); + } + auto result = cast; + if (nanOpcode != 0u) { + result = module.AllocateId(); + if (nanOpcode == spv::OpIsNan) module.AddFunction(nanOpcode, boolType, result, cast); + else module.AddFunction(nanOpcode, boolType, result, cast, cast); + } + module.AddFunction(spv::OpStore, outputs[other ? 1u : 0u], result); + }; + if (separateBlocks) { + const auto condition = module.AllocateId(); + const auto left = module.AllocateId(); + const auto right = module.AllocateId(); + const auto merge = module.AllocateId(); + module.AddFunction(spv::OpINotEqual, boolType, condition, first, zero); + module.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone); + module.AddFunction(spv::OpBranchConditional, condition, left, right); + module.AddFunction(spv::OpLabel, left); + emit(false); + module.AddFunction(spv::OpBranch, merge); + module.AddFunction(spv::OpLabel, right); + emit(true); + module.AddFunction(spv::OpBranch, merge); + module.AddFunction(spv::OpLabel, merge); + } else { + emit(false); + emit(true); + } + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + return SpecializeSpirv(module.Finalize()); +} + +void testFoldedBitcastsAndNanTests() { + for (const auto nanOpcode : std::array{0u, spv::OpIsNan, spv::OpFUnordNotEqual}) { + const auto same = opcodeCounts(foldedBitcasts(false, false, false, nanOpcode)); + const auto other = opcodeCounts(foldedBitcasts(false, true, false, nanOpcode)); + const auto branches = opcodeCounts(foldedBitcasts(true, false, false, nanOpcode)); + const auto count = [](const auto& counts, std::uint32_t op) { + const auto found = counts.find(op); + return found == counts.end() ? 0u : found->second; + }; + check(count(same, spv::OpBitcast) == 1u, "folding a select left repeated bitcasts of one value in a block"); + check(count(other, spv::OpBitcast) == 2u, "folding merged bitcasts of different input values"); + check(count(branches, spv::OpBitcast) == 2u, "folding reused a bitcast from a sibling branch"); + if (nanOpcode != 0u) { + check(count(same, nanOpcode) == 1u, "folding left repeated NaN tests of one value in a block"); + check(count(other, nanOpcode) == 2u, "folding merged NaN tests of different input values"); + check(count(branches, nanOpcode) == 2u, "folding reused a NaN test from a sibling branch"); + } + } + check(opcodeCounts(foldedBitcasts(false, false, true, 0u))[spv::OpBitcast] == 2u, "folding merged bitcasts to different result types"); + for (const auto decorations : {1u, 2u, 3u, 4u, 8u}) { + check(opcodeCounts(foldedBitcasts(false, false, false, 0u, decorations))[spv::OpBitcast] == 2u, "folding merged a decorated bitcast with another value"); + } +} + int main() { + testFoldedBitcastsAndNanTests(); testFoldingLeavesNoDeadBlocksOrConstants(); testIdsBeyondTheBoundAreRejected(); testStructExtractsAreNotShuffled(); diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp index b50010c578..1cf79f16d3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp @@ -384,7 +384,7 @@ std::optional> storedLaneFunction(const bool function = false; for (std::size_t offset = 5; offset < words.size(); offset += words[offset] >> spv::WordCountShift) { const auto opcode = static_cast(words[offset] & spv::OpCodeMask); - const auto* operands = &words[offset + 1]; + const auto* operands = words.data() + offset + 1; if (opcode == spv::OpFunction) function = true; if (opcode == spv::OpConstant) values[operands[1]] = {0u, operands[2]}; if (opcode == spv::OpVariable && operands[2] == spv::StorageClassInput) inputs.insert(operands[1]); diff --git a/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp b/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp index 0578ee283d..b3f20c7fa2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp @@ -144,11 +144,18 @@ void verifyEmittedBarriers() { require(single.subgroupControl == 0 && single.subgroupMemory == 0, "with wave LDS memory barriers, a single-wave workgroup did not keep workgroup-scope barriers"); } +void verifyLdsLockFenceDrivers() { + for (const auto& item : Cases) { + require(AgcDriver::LdsLockFencesRequired(item.driver) == (item.driver == VK_DRIVER_ID_MOLTENVK), "driver " + std::to_string(static_cast(item.driver)) + ": expected LDS lock fences only on MoltenVK"); + } +} + } int main() { try { verifyDriverQuirk(); + verifyLdsLockFenceDrivers(); verifyEmittedBarriers(); verifyLdsMemoryBarrierScope(); std::puts("subgroup barrier tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 4068a2ce41..8b6296af50 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -6,6 +6,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" @@ -87,7 +88,9 @@ void testValidation() { expectFailure([] { sceAgcDriverAgrSubmitDcb(nullptr); }); expectFailure([] { sceAgcDriverSubmitAcb(0x20, nullptr); }); expectFailure([&] { sceAgcDriverSubmitAcb(0, &packet); }); - expectFailure([&] { sceAgcDriverSubmitAcb(0x58, &packet); }); + check(sceAgcDriverSubmitAcb(0x58, &packet) == 0, "queue 0x58 submit failed"); + AgcDriverWaitIdle_nid_postfix(); + expectFailure([&] { sceAgcDriverSubmitAcb(0x59, &packet); }); packet.dw_num = 2; expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }); packet.dw_num = 3; @@ -208,6 +211,7 @@ void testEndOfPipeInterrupts() { check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "flip-only queue submit failed"); AgcDriverWaitIdle_nid_postfix(); check(output->ready.load() == 1, "flip-only queue flip was not handed to its output"); + check(AgcDriver::Graphics::HostImportFramesEnded() == 1, "the first flip did not end a host import frame"); check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &graphicsTag && events[0].data == 1 && sceAgcDriverGetEqEventType(events.data()) == 0, "flip on a queue that never requested a release interrupt did not raise its event"); check(owner->GetTriggeredEvents(events.data(), 2) == 0, "flip-only queue event was not cleared"); std::array words{0xc0064900, 0, 1u << 24u, 0, 0, 0, 0, 0}; @@ -229,6 +233,7 @@ void testEndOfPipeInterrupts() { check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "flip after a release interrupt submit failed"); AgcDriverWaitIdle_nid_postfix(); check(output->ready.load() == 2, "flip after a release interrupt was not handed to its output"); + check(AgcDriver::Graphics::HostImportFramesEnded() == 2, "a flip packet executed on the queue did not end a host import frame"); check(owner->GetTriggeredEvents(events.data(), 2) == 0, "flip on a queue that already raised its own release interrupt raised a second event"); words[2] = 1u << 24u; check(sceAgcDriverSubmitDcb(&packet) == 0, "release before sustained flips submit failed"); @@ -259,6 +264,13 @@ void testEndOfPipeInterrupts() { check(output->ready.load() == static_cast(4 + sustainedFlips + index), "flip paired with a release interrupt was not handed to its output"); check(owner->GetTriggeredEvents(events.data(), 2) == 0, "a flip raised an event within two flips of the queue's own release interrupt"); } + const auto framesBefore = AgcDriver::Graphics::HostImportFramesEnded(); + const auto readyBefore = output->ready.load(); + std::thread([] { AgcDriverSubmitFlip_nid_postfix(AgcDriver::FlipInfo{7, -1, 1, 0}); }).join(); + AgcDriverWaitIdle_nid_postfix(); + static_cast(owner->GetTriggeredEvents(events.data(), 2)); + check(output->ready.load() == readyBefore + 1, "flip from a thread that never submitted queue 0 was not handed to its output"); + check(AgcDriver::Graphics::HostImportFramesEnded() == framesBefore + 1, "a CPU flip from a thread that never submitted queue 0 did not end a host import frame"); AgcDriverUnregisterVideoOutput_nid_postfix(7, output); alignas(8) static volatile std::uint64_t label = 0; const auto labelAddress = reinterpret_cast(&label); @@ -433,6 +445,31 @@ void testWaitFreeSubmissionBehindHeldOne() { AgcDriverWaitIdle_nid_postfix(); } +void testQueue0SubmissionAfterEarlierWaitFreeWork() { + alignas(64) static volatile std::uint32_t first = 0, second = 0; + constexpr std::uint32_t writes = 20000; + std::vector words; + for (std::uint32_t i = 1; i <= writes; ++i) { + const auto write = writeData(&first, i); + words.insert(words.end(), write.begin(), write.end()); + } + submit(0x20, words); + submit(0, commands(writeData(&second, 1))); + waitFor(&second, 1, "queue 0's submission never ran"); + check(first == writes, "queue 0 ran a submission before another queue's earlier wait-free submission finished"); + AgcDriverWaitIdle_nid_postfix(); +} + +void testQueue0SubmissionBehindWaitingQueue() { + alignas(64) static volatile std::uint32_t label = 0, other = 0, done = 0; + submit(0x20, commands(waitEqual(&label, 1))); + submit(0x20, commands(writeData(&other, 1))); + submit(0, commands(writeData(&label, 1), writeData(&done, 1))); + check(waitFor(&done, 1, "queue 0 never ran while another queue waited on it") < std::chrono::milliseconds(500), "queue 0 waited for a wait-free submission queued behind another queue's wait on queue 0"); + waitFor(&other, 1, "the wait-free submission behind the satisfied wait never ran"); + AgcDriverWaitIdle_nid_postfix(); +} + void testMultiAcbs() { alignas(64) static volatile std::uint32_t value = 0, done = 0, gate = 0; check(sceAgcDriverSubmitMultiAcbs(0x20, nullptr, nullptr, 0) == 0, "empty multi-ACB submit failed"); @@ -1030,6 +1067,8 @@ int main(int argc, char** argv) { testMultiAcbs(); testWaitFreeSubmissionBehindHeldOne(); testWaitFreeSubmissionTheCpuWaitsFor(); + testQueue0SubmissionAfterEarlierWaitFreeWork(); + testQueue0SubmissionBehindWaitingQueue(); testMultiSubmissions(); testShaderHeaderAlignment(); testHeaderWithoutProgramAddress(); diff --git a/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp index b978b4c018..7aaf23fe9f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp @@ -133,6 +133,10 @@ struct Hull { void readRing(IrValue& offset, IrValue& predicate) { auto& loaded = builder.Emit(IrOpcode::LoadBufferU32, IrType::U32, {&ringResource(), &builder.Constant(0u), &offset, &offChip(), &predicate}, MemoryFlags{RingMemory, RingReadPc}); static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&loaded})); + } + + IrValue& packed() { + return builder.BitwiseOr(builder.ShiftLeftLogical(invocation(), builder.Constant(8u)), relativePatch()); } IrValue& merge(std::uint32_t firstMultiple, std::uint32_t secondMultiple) { @@ -341,6 +345,14 @@ bool LowersUnderComplementedBallot(Conjunction conjunction) { return LoweredAt(hull.program, address); } +bool OwnControlPoint(const IrValue* address, std::uint32_t stride) { + if (address == nullptr || address->Opcode() != IrOpcode::IAdd32 || !address->Argument(1)->Resolve()->HasImmediate() || address->Argument(1)->Resolve()->ImmediateU32() != 0u) return false; + const IrValue* scaled = address->Argument(0)->Resolve(); + if (scaled->Opcode() != IrOpcode::IMul32 || scaled->Argument(1)->Resolve()->ImmediateU32() != stride) return false; + const IrValue* invocation = scaled->Argument(0)->Resolve(); + return invocation->Opcode() == IrOpcode::GetBuiltin && invocation->Argument(0)->Resolve()->ImmediateU32() == static_cast(StageInputKind::InvocationId); +} + bool run(const char* name, const std::function& body) { bool passed = false; try { @@ -548,6 +560,29 @@ int main() { passed &= run("a hull read of another control point's output at an immediate address past the last control point reads the last control point", [] { return EmittedControlPoint(IrShaderStage::TessellationControl, TessellationAttribute::ControlOutput, InputControlPoints * 16u) == InputControlPoints - 1u; + }); + + passed &= run("a hull read at its control point masked out of the packed control point and relative patch reads its own input control point", [] { + Hull hull; + auto& masked = hull.builder.BitwiseAnd(hull.packed(), hull.builder.Constant(0x1f00u)); + hull.read(hull.inPatch(hull.builder.ShiftRightLogical(masked, hull.builder.Constant(3u)))); + const Outcome outcome = Lower(hull.program); + if (!outcome.lowered) std::fprintf(stderr, "refused: %s\n", outcome.reason.c_str()); + return outcome.lowered && OwnControlPoint(outcome.emitted, LocalStride); + }); + + passed &= run("a mask that drops the low bit of the packed control point is refused", [] { + Hull hull; + auto& masked = hull.builder.BitwiseAnd(hull.packed(), hull.builder.Constant(0x1e00u)); + hull.read(hull.inPatch(hull.builder.ShiftRightLogical(masked, hull.builder.Constant(3u)))); + return RefusedBecause(hull.program, "not affine"); + }); + + passed &= run("a mask with a gap below the packed control point is refused", [] { + Hull hull; + auto& masked = hull.builder.BitwiseAnd(hull.packed(), hull.builder.Constant(0x1f0fu)); + hull.read(hull.inPatch(hull.builder.ShiftRightLogical(masked, hull.builder.Constant(3u)))); + return RefusedBecause(hull.program, "not affine"); }); return passed ? 0 : 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp index 591732ee62..ab76da6171 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp @@ -522,6 +522,64 @@ constexpr std::array TwoSampleDitherCases{{ {0x00007e00u, 4u, {0x00u, 0x00u, 0x00u, 0x00u}}, }}; +void DccSrgbClearTests(AgcDriver::VulkanDevice& device, std::uint32_t samples) { + const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 4); + const ColorTargetLayout allocation(Width, Height, ColorTileMode::RenderTarget, 8); + std::vector> blocks; + struct ClearTargets { + AgcDriver::VulkanDevice& device; + ~ClearTargets() { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + device.SubmitRecorded(true); + device.WaitIdle(); + AgcDriver::Graphics::ClearMultisampleTargets(device.Device()); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + } + } clearTargets{device}; + const auto targetFor = [&](const Block& block, std::uint32_t count, VkFormat format) { + auto target = DecodeTarget(block, count); + target.format = format; + target.elementBytes = 4; + target.bytes = layout.Bytes(); + return target; + }; + for (const auto format : {VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB}) { + for (const auto key : {0x00u, 0x40u, 0x80u, 0xc0u}) { + for (const auto alphaOnMsb : {false, true}) { + blocks.push_back(std::make_unique(allocation.Bytes() * samples)); + auto& source = *blocks.back(); + std::memset(source.data, 0x5a, source.bytes); + blocks.push_back(std::make_unique(BlockBytes)); + auto& metadata = *blocks.back(); + std::memset(metadata.data, key, metadata.bytes); + auto target = targetFor(source, samples, format); + target.dccAddress = metadata.Address(); + target.dccPipeAligned = true; + target.dccAlphaOnMsb = alphaOnMsb; + Draw(device, target, false, Float32Export, Program(Color{0.25f, 0.5f, 0.75f, 0.125f}, Float32Export, 0u), false, true); + blocks.push_back(std::make_unique(allocation.Bytes())); + auto& resolved = *blocks.back(); + std::memset(resolved.data, 0xa5, resolved.bytes); + AgcDriver::Graphics::ColorMetadataPass pass{}; + pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve; + pass.source = target; + pass.targets = {targetFor(resolved, 1u, format)}; + device.ColorMetadataPass(pass); + const auto stored = ReadBack(device, resolved); + const auto bytes = std::as_bytes(std::span(stored)); + std::array expected{}; + const auto keys = key == 0x00u ? AgcDriver::Graphics::DccKeys::Clear0000 : key == 0x40u ? AgcDriver::Graphics::DccKeys::Clear0001 : key == 0x80u ? AgcDriver::Graphics::DccKeys::Clear1110 : AgcDriver::Graphics::DccKeys::Clear1111; + Require(AgcDriver::Graphics::FillDccClear(format, keys, alphaOnMsb, expected), "the sRGB DCC expected texel was not encoded"); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + for (std::uint32_t channel = 0; channel < 4; ++channel) Require(bytes[layout.Offset(x, y) + channel] == expected[channel], "an sRGB DCC import or resolve changed an undrawn sample endpoint"); + } + } + } + } + } +} + void DccFastClearTests(AgcDriver::VulkanDevice& device, std::uint32_t selectedSamples) { const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 8); const auto expectRange = [](std::string_view message, const char* plane, std::uint64_t address, std::size_t bytes, std::uint32_t samples) { @@ -1147,6 +1205,19 @@ int main(int argc, char** argv) { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; + if (argc == 3 && std::string_view(argv[1]) == "--dcc-srgb-clear") { + const auto samples = std::string_view(argv[2]) == "2" ? 2u : std::string_view(argv[2]) == "4" ? 4u : std::string_view(argv[2]) == "8" ? 8u : 0u; + Require(samples != 0, "unknown sRGB DCC test sample count"); + try { + DccSrgbClearTests(*device, samples); + } catch (const std::runtime_error& error) { + if (std::string_view(error.what()) != "AGC graphics: the device cannot render color with the target's sample count") throw; + std::printf("skipped, the device lacks the requested multisample color attachments: %s\n", error.what()); + return VulkanTestSkipped; + } + std::cout << "Multisampled sRGB DCC clear tests passed\n"; + return 0; + } if ((argc == 2 || argc == 3) && std::string_view(argv[1]) == "--dcc-fast-clear") { const auto samples = argc == 2 ? 0u : std::string_view(argv[2]) == "2" ? 2u : std::string_view(argv[2]) == "4" ? 4u : std::string_view(argv[2]) == "8" ? 8u : 0u; Require(argc == 2 || samples != 0, "unknown DCC test sample count"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp index 51fa4f8630..0a1624612e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "Recompiler.hpp" +#include "prx/libc/include/GuestAllocations.hpp" #include "Optimization/ResourceProgram.hpp" #include "VulkanTestDevice.hpp" #include @@ -150,6 +151,30 @@ void ScalarReads(AgcDriver::VulkanDevice& device) { device.WaitIdle(); Require(Output == baseline, "vector or mixed buffer read changed with reserved bit 30 set"); } + request.shader.code = scalarCode; + request.shader.codeAddress = reinterpret_cast(scalarCode.data()); + struct BoundsCase { + std::uint32_t stride; + std::uint32_t records; + bool inBounds; + }; + constexpr std::array bounds{{ + {0u, 0u, false}, {0u, 7u, false}, {0u, 8u, true}, {1u, 7u, false}, + {2u, 3u, false}, {2u, 4u, true}, {4u, 0x40000000u, true}, {0x3fffu, 0xffffffffu, true}, + }}; + GuestAllocations::Mutation().Add(Input.data(), Input.size(), true, false, true); + for (const auto& test : bounds) { + auto input = BufferDescriptor(Input.data(), test.records); + input[1] |= test.stride << 16u; + std::copy(input.begin(), input.end(), userData.begin()); + Output.fill(Fill); + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1); + device.WaitIdle(); + const auto value = test.inBounds ? expected : 0u; + Require(std::all_of(Output.begin(), Output.end(), [value](std::uint32_t word) { return word == value; }), "scalar buffer bounds differ for stride " + std::to_string(test.stride) + " records " + std::to_string(test.records)); + } + GuestAllocations::Mutation().Remove(Input.data()); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/CommittedReadMapping.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/CommittedReadMapping.cpp new file mode 100644 index 0000000000..fb8dcd5ad9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/CommittedReadMapping.cpp @@ -0,0 +1,114 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +namespace { +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} +#ifdef _WIN32 +void Run() { + constexpr std::size_t bytes = 65536; + constexpr std::size_t page = 4096; + auto* source = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, bytes)); + Require(source != nullptr, "cannot reserve snapshot fixture"); + GuestArena::GuestArenaCommit_nid_postfix(source, bytes, PAGE_READWRITE, bytes); + std::memset(source, 0x5a, bytes); + const auto address = reinterpret_cast(source); + { GuestAllocations::Mutation mutation; mutation.Add(source, bytes, true, true, false); } + std::array snapshot{}; + const auto prime = [&] { + { GuestAllocations::Mutation mutation; mutation.Protect(source, bytes, true, true, false, [] {}); } + Require(AgcDriver::GuestMemory::Accessible(source, bytes), "snapshot fixture is not readable"); + }; + const auto check = [&](bool hole) { + for (std::size_t index = 0; index < bytes; ++index) { + const auto expected = hole && index >= 2 * page && index < 3 * page ? std::byte{} : std::byte{0x5a}; + Require(snapshot[index] == expected, "committed snapshot lost readable bytes or copied inaccessible bytes"); + } + }; + for (const DWORD protection : {PAGE_NOACCESS, PAGE_READWRITE | PAGE_GUARD}) { + prime(); + DWORD previous = 0; + Require(VirtualProtect(source + 2 * page, page, protection, &previous), "cannot protect snapshot hole"); + snapshot.fill(std::byte{0xff}); + AgcDriver::GuestMemory::ReadCommitted(address, snapshot); + check(true); + MEMORY_BASIC_INFORMATION memory{}; + Require(VirtualQuery(source + 2 * page, &memory, sizeof(memory)) == sizeof(memory), "cannot query snapshot hole"); + Require(memory.Protect == protection, "snapshot read changed the hole's protection"); + Require(!AgcDriver::GuestMemory::Accessible(source, bytes), "failed snapshot retained stale readable page states"); + Require(VirtualProtect(source + 2 * page, page, PAGE_READWRITE, &previous), "cannot restore snapshot hole"); + prime(); + AgcDriver::GuestMemory::ReadCommittedUnsynced(address, snapshot); + check(false); + prime(); + Require(VirtualProtect(source + 2 * page, page, protection, &previous), "cannot protect comparison hole"); + std::fill(snapshot.begin() + 2 * page, snapshot.begin() + 3 * page, std::byte{0x77}); + Require(AgcDriver::GuestMemory::EqualsCommitted(address, snapshot), "committed comparison did not skip an inaccessible page"); + snapshot[page - 1] = std::byte{}; + Require(!AgcDriver::GuestMemory::EqualsCommittedUnsynced(address, snapshot), "committed comparison missed a readable byte difference"); + Require(!AgcDriver::GuestMemory::Accessible(source, bytes), "failed comparison retained stale readable page states"); + Require(VirtualQuery(source + 2 * page, &memory, sizeof(memory)) == sizeof(memory), "cannot query comparison hole"); + Require(memory.Protect == protection, "committed comparison changed the hole's protection"); + Require(VirtualProtect(source + 2 * page, page, PAGE_READWRITE, &previous), "cannot restore comparison hole"); + } + prime(); + std::atomic done{false}; + std::thread mutation([&] { + DWORD previous = 0; + while (!done.load(std::memory_order_acquire)) { + VirtualProtect(source + 2 * page, page, PAGE_NOACCESS, &previous); + VirtualProtect(source + 2 * page, page, PAGE_READWRITE, &previous); + } + }); + bool stable = true; + try { + for (unsigned iteration = 0; iteration < 500; ++iteration) { + AgcDriver::GuestMemory::ReadCommittedUnsynced(address, snapshot); + stable = stable && std::all_of(snapshot.begin(), snapshot.begin() + 2 * page, [](auto value) { return value == std::byte{0x5a}; }); + stable = stable && std::all_of(snapshot.begin() + 3 * page, snapshot.end(), [](auto value) { return value == std::byte{0x5a}; }); + } + } catch (...) { + done.store(true, std::memory_order_release); + mutation.join(); + throw; + } + done.store(true, std::memory_order_release); + mutation.join(); + Require(stable, "concurrent snapshot discarded stable readable pages"); + { GuestAllocations::Mutation mutation; mutation.Remove(source); } + GuestArena::GuestArenaReset_nid_postfix(source, bytes); + GuestArena::GuestArenaRelease_nid_postfix(source, bytes); +} +#endif +} + +int main() { + try { +#ifdef _WIN32 + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(); + device->WaitIdle(); + std::puts("committed snapshot mapping changes passed"); + return 0; +#else + return VulkanTestSkipped; +#endif + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthExportClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthExportClamp.cpp new file mode 100644 index 0000000000..19084d49af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthExportClamp.cpp @@ -0,0 +1,75 @@ +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include + +namespace { + +using DepthFastClearHarness::Compile; +using DepthFastClearHarness::Covered; +using DepthFastClearHarness::Draw; +using DepthFastClearHarness::Height; +using DepthFastClearHarness::Require; +using DepthFastClearHarness::Shaders; +using DepthFastClearHarness::Surface; +using DepthFastClearHarness::Width; + +alignas(4096) std::array Depth{}; + +alignas(256) constexpr std::array ExportOnePointFive{0x7e0002f2, 0xf800100f, 0x00000000, 0x7e0202ff, 0x3fc00000, 0xf8001881, 0x00000001, 0xbf810000}; +alignas(256) constexpr std::array ExportMinusHalf{0x7e0002f2, 0xf800100f, 0x00000000, 0x7e0202ff, 0xbf000000, 0xf8001881, 0x00000001, 0xbf810000}; + +Shaders WithDepthExport(AgcDriver::VulkanDevice& device, const Shaders& plain, std::span code, float minimum, float maximum) { + Shaders shaders = plain; + const auto target = device.Target(); + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = true; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + pixel.depthExportEnable = true; + pixel.depthExportMin = minimum; + pixel.depthExportMax = maximum; + std::vector pixelUserData(4, 0u); + const std::array pixelMemory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderRecompiler::ShaderStage::Fragment, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(plain.vertexPush, target) + }; + fragment.useCache = false; + shaders.pixel = ShaderRecompiler::Recompile(fragment); + return shaders; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto plain = Compile(*device); + const Surface surface{reinterpret_cast(Depth.data()), 0, 0, false, VK_FORMAT_D32_SFLOAT}; + constexpr float low = 0.25f; + constexpr float high = 0.75f; + const auto high15 = WithDepthExport(*device, plain, ExportOnePointFive, low, high); + Require(Draw(*device, high15, surface, {.depthTest = true, .depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the depth-exporting draw did not cover the target"); + Require(Draw(*device, plain, surface, {.depthTest = true, .depthWrite = false, .depthCompare = VK_COMPARE_OP_GREATER, .minDepth = 0.9f, .maxDepth = 0.9f}) == Covered, "an exported depth of 1.5 was not clamped to ZMAX 0.75"); + const auto minus = WithDepthExport(*device, plain, ExportMinusHalf, low, high); + Require(Draw(*device, minus, surface, {.depthTest = true, .depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the second depth-exporting draw did not cover the target"); + Require(Draw(*device, plain, surface, {.depthTest = true, .depthWrite = false, .depthCompare = VK_COMPARE_OP_LESS, .minDepth = 0.1f, .maxDepth = 0.1f}) == Covered, "an exported depth of -0.5 was not clamped to ZMIN 0.25"); + const auto open = WithDepthExport(*device, plain, ExportOnePointFive, -std::numeric_limits::infinity(), std::numeric_limits::infinity()); + Require(Draw(*device, open, surface, {.depthTest = true, .depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the unclamped depth-exporting draw did not cover the target"); + Require(Draw(*device, plain, surface, {.depthTest = true, .depthWrite = false, .depthCompare = VK_COMPARE_OP_GREATER, .minDepth = 0.9f, .maxDepth = 0.9f}) == 0u, "an unclamped depth export (DISABLE_VIEWPORT_CLAMP) was clamped"); + std::puts("depth export clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp index 09383ccc9a..a7c6dde0eb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -459,10 +459,12 @@ void PooledPlaneCopies(const Context& context, const WatchedBlock& first, const DepthSurfaceView(context, DepthTarget{first.Address(), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); DepthSurfaceView(context, DepthTarget{second.Address(), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); const auto sample = [&](const WatchedBlock& block, int round) { + Recorder::CountPresent(); + Recorder::CountPresent(); lastFilledImage = VK_NULL_HANDLE; bufferToImageCopies = 0; Require(DepthSurfaceTexture(context, words, ArrayOver(block.Address()), components) != nullptr, "the 2D array over a depth surface was not sampled through its depth planes in round " + std::to_string(round)); - Require(DepthPlaneCopyCount() == 1, "the depth plane copies outgrew a budget that holds one in round " + std::to_string(round) + ", " + std::to_string(DepthPlaneCopyCount()) + " copies"); + Require(DepthPlaneCopyCount() == 1, "the depth plane copies outgrew a budget that holds one, two presents after the other was sampled, in round " + std::to_string(round) + ", " + std::to_string(DepthPlaneCopyCount()) + " copies"); Require(lastFilledImage.load() != VK_NULL_HANDLE && bufferToImageCopies.load() == 1, "a plane copy made again over a full budget was not filled from its surface once in round " + std::to_string(round) + ", " + std::to_string(bufferToImageCopies.load()) + " fills"); }; sample(first, 0); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp index 6f8c3c8bdc..0486d4ba8c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlanePartialStore.cpp @@ -24,9 +24,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -140,6 +142,27 @@ int main(int argc, char** argv) { const auto draw = [&](const Surface& surface, const DrawOptions& options) { return Draw(*device, shaders, surface, options, PlaneExtent, Colors); }; const DrawOptions writeHalf{.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}; + if (argc == 2 && std::string_view(argv[1]) == "--cpu-write-after-memo") { + if (!AgcDriver::GuestMemory::WriteWatched() || !AgcDriver::GuestMemory::Watched(base, PlaneBytes)) return VulkanTestSkipped; + Fill(plane, 1.0f); + Require(draw(depth, writeHalf) == PlaneTexels, "the memo regression depth draw did not cover the plane"); + AgcDriver::GuestMemory::BumpCollectEpoch(); + AgcDriver::GuestMemory::CollectWrites(base, PlaneBytes); + const auto vertexAddress = base + 2u * Block + 256u; + const std::array descriptor{static_cast(vertexAddress), static_cast((vertexAddress >> 32u) & 0xffffu) | (32u << 16u), 4u, 0x0004dfacu}; + auto* table = reinterpret_cast(plane) + 4u * Block + 4096u; + std::memcpy(table, descriptor.data(), sizeof(descriptor)); + const auto stores = AgcDriver::Graphics::FlushDepthPlanes(base + 4u * Block + 128u, 192u); + device->WaitIdle(); + Require(std::memcmp(table, descriptor.data(), sizeof(descriptor)) == 0, "a depth flush overwrote CPU descriptors written after a memoized collect"); + Require(stores.empty(), "a depth flush stored the unit repurposed by the CPU"); + Require(!ReadStart(*device, base).empty(), "preserving the CPU descriptors dropped an untouched depth unit"); + RequireUnit(plane, 0, 0.5f, UnitTexels, "the untouched unit retains its GPU depth results"); + Require(std::memcmp(table, descriptor.data(), sizeof(descriptor)) == 0, "storing an untouched depth unit changed the CPU descriptors"); + std::puts("depth plane CPU descriptors after memoized collect tests passed"); + return 0; + } + Fill(plane, 1.0f); Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 did not cover the plane"); Require(AgcDriver::Graphics::DepthPlanesStale(), "the draw writing depth left the plane unregistered"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp index f77432e092..108954d47f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" @@ -70,6 +71,7 @@ constexpr std::size_t PaddedCubeOffset = 0x120000; constexpr std::size_t SmallCubeOffset = 0x180000; constexpr std::size_t LargeCubeOffset = 0x200000; constexpr std::size_t GuestDepthBitsOffset = 0x480000; +constexpr std::size_t FrameCopiesOffset = 0x520000; constexpr float DrawnDepth = 0.25f; constexpr float DrawnD16Depth = 0.2f; constexpr std::uint32_t DrawnD16Bits = 13107; @@ -414,6 +416,43 @@ void BaseSliceCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span Expect(output, Bits(0.5f), "D32 2D array from base slice 1 through a 2D shader image"); } +void FrameCopiesCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + constexpr VkExtent2D extent{128, 256}; + constexpr VkDeviceSize budget = 1u << 20u; + const auto stride = AgcDriver::Graphics::DepthSliceBytes(extent, 4u); + Require(FrameCopiesOffset + 9u * stride <= BlockBytes, "the three 3-slice 128x256 D32 arrays must fit the guest block"); + const std::array bases{block.At(FrameCopiesOffset), block.At(FrameCopiesOffset + 3u * stride), block.At(FrameCopiesOffset + 6u * stride)}; + const std::array expected{0.375f, 0.625f, 0.875f}; + for (std::size_t copy = 0; copy < bases.size(); ++copy) { + DrawDepth(device, reinterpret_cast(bases[copy]), extent, VK_FORMAT_D32_SFLOAT, DrawnDepth); + for (std::uint32_t slice = 1; slice < 3u; ++slice) FillSlice(bases[copy] + slice * stride, stride, Bits(expected[copy]), 4u); + } + const Shape shape{Format32Float, extent.width, extent.height, Type2DArray, 2, Swizzle64KbZX}; + const auto sample = [&](std::size_t copy) { + Load(device, bases[copy], shape, 2, output); + Expect(output, Bits(expected[copy]), "D32 2D array " + std::to_string(copy) + " slice 2"); + }; + AgcDriver::Graphics::Recorder::CountPresent(); + AgcDriver::Graphics::Recorder::CountPresent(); + sample(0); + sample(1); + sample(0); + sample(1); + const auto kept = AgcDriver::Graphics::DepthPlaneCopyCount(); + Require(kept == 2, std::to_string(kept) + " depth plane copies were kept after two copies over the 1 MiB budget were sampled in one present, expected both"); + const auto pair = AgcDriver::Graphics::DepthPlaneCopyBytes(); + Require(pair > budget && pair <= 2u * budget, "the two copies sampled in one present hold " + std::to_string(pair) + " bytes, the case needs more than the 1 MiB budget and no more than twice it"); + sample(2); + const auto capped = AgcDriver::Graphics::DepthPlaneCopyBytes(); + Require(capped <= 2u * budget, "the depth plane copies sampled in one present hold " + std::to_string(capped) + " bytes in " + std::to_string(AgcDriver::Graphics::DepthPlaneCopyCount()) + " copies, more than twice the 1 MiB budget"); + Require(AgcDriver::Graphics::DepthPlaneCopyCount() == 2, std::to_string(AgcDriver::Graphics::DepthPlaneCopyCount()) + " depth plane copies were kept after a third copy took the ones sampled in one present past twice the budget, expected the two most recently used"); + AgcDriver::Graphics::Recorder::CountPresent(); + AgcDriver::Graphics::Recorder::CountPresent(); + sample(1); + const auto left = AgcDriver::Graphics::DepthPlaneCopyCount(); + Require(left == 1, std::to_string(left) + " depth plane copies were kept two presents after the other copy was last sampled, expected only the newest"); +} + void ExpectRefusal(AgcDriver::VulkanDevice& device, GuestBlock& block, std::size_t offset, const Shape& shape, std::uint32_t baseArray, const std::string& expected, const std::string& what) { const auto words = TextureDescriptor(block.At(offset), shape); auto resource = AgcDriver::Graphics::DecodeTextureResource(words); @@ -441,21 +480,37 @@ int main(int argc, char** argv) { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const std::span output(reinterpret_cast(block.At(OutputOffset)), Threads); + const auto present = [&] { + if (!evicting) return; + AgcDriver::Graphics::Recorder::CountPresent(); + AgcDriver::Graphics::Recorder::CountPresent(); + }; ArrayCase(*device, block, output); + present(); CubeCase(*device, block, output); + present(); IntegerCase(*device, block, output); + present(); DepthBitsArrayCase(*device, block, output); + present(); DepthBitsBaseSliceCase(*device, block, output); + present(); GuestDepthBitsCase(*device, block, output); + present(); FirstSliceCase(*device, block, output); + present(); PaddedCubeCase(*device, block, output); + present(); SmallD16CubeCase(*device, block, output); + present(); PaddedD16CubeCase(*device, block, output); + present(); BaseSliceCase(*device, block, output); RefusalCase(*device, block); if (evicting) { const auto kept = AgcDriver::Graphics::DepthPlaneCopyCount(); Require(kept >= 1 && kept < 8, std::to_string(kept) + " depth plane copies were kept with a 1 MiB budget, expected at least the newest and fewer than the 8 an unbounded cache keeps"); + FrameCopiesCase(*device, block, output); } std::puts("depth plane sampling tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp index 45f1e60352..cff3638ece 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawCacheRelocation.cpp @@ -318,6 +318,10 @@ int main(int argc, char** argv) { MovedDataHitsTheCache(libraries); UnrelocatedAliasesStayOut(libraries); MovedForwardedWordReachesStageDataHit(libraries); + if (std::getenv("APS5_VERIFY_DRAW_RECIPE") != nullptr) { + const auto counters = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters(); + Require(counters.verifyDecodeMismatches == 0, "the effective relocated draw decode differed from its full decode"); + } AgcDriverShutdown_nid_postfix(); std::cout << "A draw whose data moved reused the cached draw " << (libraries ? "with" : "without") << " graphics pipeline libraries\n"; return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp index 2051585c2d..542cb8c3a4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawShaderFailure.cpp @@ -297,11 +297,12 @@ void SuccessfulDrawEmitsNoFailureRecord(const char* tracePath) { const auto recorded = TraceLine(ReadTraceFile(tracePath), "draw-recorded token=" + token); const auto batch = TraceField(recorded, "batch"); Require(!batch.empty() && batch != "0", "the draw-recorded record carries no actual batch serial"); - Require(ReadTraceFile(tracePath).find("submit batch=" + batch + " ") != std::string::npos, "the recorded batch was not submitted"); - const auto submit = TraceLine(ReadTraceFile(tracePath), "submit batch=" + batch + " "); + Require(WaitForTraceRecord(tracePath, "submit-confirmed batch=" + batch + " tokens=[" + token + "]\n"), "the confirmed submit record lost the draw token set"); + const auto completedTrace = ReadTraceFile(tracePath); + Require(completedTrace.find("submit batch=" + batch + " ") != std::string::npos, "the recorded batch was not submitted"); + const auto submit = TraceLine(completedTrace, "submit batch=" + batch + " "); Require(submit == "submit batch=" + batch + " tokens=[" + token + "]", "the submit record lost the draw token set"); - Require(ReadTraceFile(tracePath).find("submit-confirmed batch=" + batch + " tokens=[" + token + "]") != std::string::npos, "the confirmed submit record lost the draw token set"); - Require(trace.find("draw-shader-request-failed") == std::string::npos, "a successful draw emitted a draw-shader-request-failed record"); + Require(completedTrace.find("draw-shader-request-failed") == std::string::npos, "a successful draw emitted a draw-shader-request-failed record"); } void DisabledCorrelationTraceLeavesDrawBehaviorUnchanged(const char* tracePath) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32FmaDenormalMode2.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32FmaDenormalMode2.cpp new file mode 100644 index 0000000000..58e6442cc3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32FmaDenormalMode2.cpp @@ -0,0 +1,241 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd54b000au, 0x041a0b04u, 0x7e160306u, 0x56160b04u, + 0x58180d04u, 0x3f7fffffu, 0x5a1a0b04u, 0x80000001u, 0xe0781000u, 0x80010a01u, 0xbf810000u, +}; + +constexpr const char* Names[Results] = {"v_fma_f32 v10, v4, v5, v6", "v_fmac_f32 v11, v4, v5", "v_fmamk_f32 v12, v4, 0x3f7fffff, v6", "v_fmaak_f32 v13, v4, v5, 0x80000001"}; + +constexpr std::uint32_t Rows[Threads][3] = { + {0x00000000u, 0x7f800000u, 0x3f000000u}, + {0x00800000u, 0x00000000u, 0x00000000u}, + {0x00800000u, 0x00000000u, 0x80000000u}, + {0x00800000u, 0x00000000u, 0x00000001u}, + {0x00800000u, 0x00000000u, 0x807fffffu}, + {0x00800000u, 0x00000000u, 0x00400000u}, + {0x00800000u, 0x00000000u, 0x80800001u}, + {0x00800000u, 0x80000000u, 0x00000000u}, + {0x00800000u, 0x80000000u, 0x80000000u}, + {0x00800000u, 0x00400000u, 0x00000001u}, + {0x00800000u, 0x00800000u, 0x80000000u}, + {0x00800000u, 0x3f800000u, 0x80800001u}, + {0x00800000u, 0x3f7fffffu, 0x00000000u}, + {0x00800000u, 0x3f7fffffu, 0x80000000u}, + {0x00800000u, 0x3f7fffffu, 0x00000001u}, + {0x00800000u, 0x3f7fffffu, 0x807fffffu}, + {0x00800000u, 0x3f7fffffu, 0x00400000u}, + {0x00800000u, 0x3f7fffffu, 0x00800000u}, + {0x00800000u, 0x3f7fffffu, 0x80800001u}, + {0x00800000u, 0x3f7fffffu, 0x3f800000u}, + {0x00800000u, 0x3f7fffffu, 0xbf800000u}, + {0x00800000u, 0x3f7fffffu, 0x3f7fffffu}, + {0x00800000u, 0x3f7fffffu, 0x3f000000u}, + {0x00800000u, 0x3f7fffffu, 0x1f800000u}, + {0x00800000u, 0x3f7fffffu, 0x9fffffffu}, + {0x00800000u, 0x3f7fffffu, 0x20000000u}, + {0x00800000u, 0x3f000000u, 0x00000000u}, + {0x00800000u, 0x3f000000u, 0x80000000u}, + {0x00800000u, 0x3f000000u, 0x00000001u}, + {0x00800000u, 0x3f000000u, 0x807fffffu}, + {0x00800000u, 0x3f000000u, 0x00400000u}, + {0x00800000u, 0x3f000000u, 0x80800001u}, + {0x00800000u, 0x1f800000u, 0x80000000u}, + {0x80800001u, 0x3f800000u, 0x00800000u}, + {0x80800001u, 0x3f000000u, 0x7f800000u}, + {0x3f800000u, 0x80800001u, 0x00800000u}, + {0x3f800000u, 0x7fc00000u, 0x80800001u}, + {0x3f7fffffu, 0xbf800000u, 0x3f000000u}, + {0x1f800000u, 0x3f800000u, 0x20000000u}, + {0x9fffffffu, 0x00000001u, 0x80000000u}, + {0x9fffffffu, 0x20000000u, 0x00400000u}, + {0x20000000u, 0x3f800000u, 0x00000000u}, + {0x20000000u, 0x9fffffffu, 0x20000000u}, + {0x20000000u, 0x9fffffffu, 0x7fc00000u}, + {0x7f800000u, 0x00400000u, 0x00000001u}, + {0xa041753bu, 0x1f0d790au, 0x80000000u}, + {0x9efedcd2u, 0x9ff05395u, 0x80000000u}, + {0x1f47e4fdu, 0x1eb6f5c4u, 0x80000000u}, + {0x9e9e1749u, 0x1f7a0022u, 0x8086d61eu}, + {0x9fc47dc5u, 0x1f7a84eeu, 0x80ad97ffu}, + {0x9e398753u, 0x20a52a9au, 0x809fead7u}, + {0x1ee684f0u, 0x204b1dd4u, 0x00000000u}, + {0x1e8933deu, 0x205cc4a7u, 0x00000000u}, + {0x1f19a9f4u, 0x9fbc243cu, 0x80000000u}, + {0x9eb3c398u, 0x9f630477u, 0x00000000u}, + {0x1e32d290u, 0x1fa0e0b3u, 0x00000000u}, + {0x1f074330u, 0x9e704ff0u, 0x00000000u}, + {0x1ea63ad2u, 0x1e7c9dc9u, 0x806cc811u}, + {0x9f42587bu, 0xa0dd6a0cu, 0x80000000u}, + {0x202187c6u, 0x1eb02829u, 0x80000000u}, + {0x1fe6f5d7u, 0x208cc5a7u, 0x80df394au}, + {0x1f2af88cu, 0x9e6ce54fu, 0x80000000u}, + {0x1eeac417u, 0x9e8ead57u, 0x00000000u}, + {0x9e406f2cu, 0x20e09d8du, 0x004226bcu} +}; +constexpr std::uint32_t Expected[Threads][Results] = { + {0xffc00000u, 0xffc00000u, 0x3f000000u, 0xffc00000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x80800001u, 0x80800001u, 0x80000002u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x00800000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x80000001u, 0x80000001u, 0x80000002u, 0x00800000u}, + {0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u}, + {0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u}, + {0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u}, + {0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u}, + {0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u}, + {0x01000000u, 0x01000000u, 0x01000000u, 0x00800000u}, + {0x80000002u, 0x80000002u, 0x80000002u, 0x00800000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00800000u}, + {0xbf800000u, 0xbf800000u, 0xbf800000u, 0x00800000u}, + {0x3f7fffffu, 0x3f7fffffu, 0x3f7fffffu, 0x00800000u}, + {0x3f000000u, 0x3f000000u, 0x3f000000u, 0x00800000u}, + {0x1f800000u, 0x1f800000u, 0x1f800000u, 0x00800000u}, + {0x9fffffffu, 0x9fffffffu, 0x9fffffffu, 0x00800000u}, + {0x20000000u, 0x20000000u, 0x20000000u, 0x00800000u}, + {0x00400000u, 0x00400000u, 0x00800000u, 0x00400000u}, + {0x00400000u, 0x00400000u, 0x00800000u, 0x00400000u}, + {0x00400000u, 0x00400000u, 0x00800000u, 0x00400000u}, + {0x00400000u, 0x00400000u, 0x00800000u, 0x00400000u}, + {0x00400000u, 0x00400000u, 0x00800000u, 0x00400000u}, + {0x80400001u, 0x80400001u, 0x80000002u, 0x00400000u}, + {0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u}, + {0x80000001u, 0x80000001u, 0x80000000u, 0x80800001u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x80400000u}, + {0x80000001u, 0x80000001u, 0x3f7fffffu, 0x80800001u}, + {0x7fc00000u, 0x7fc00000u, 0x3f7fffffu, 0x7fc00000u}, + {0xbefffffeu, 0xbefffffeu, 0x3fbfffffu, 0xbf7fffffu}, + {0x20400000u, 0x20400000u, 0x20400000u, 0x1f800000u}, + {0x80000000u, 0x80000000u, 0x9ffffffeu, 0x80000000u}, + {0x80800000u, 0x80800000u, 0x9ffffffeu, 0x80800000u}, + {0x20000000u, 0x20000000u, 0x1fffffffu, 0x20000000u}, + {0x20000000u, 0x20000000u, 0x20800000u, 0x80800000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x80800000u}, + {0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u}, + {0x80357485u, 0x80357485u, 0xa041753au, 0x80357485u}, + {0x001de847u, 0x001de847u, 0x9efedcd1u, 0x001de847u}, + {0x0008edcbu, 0x0008edcbu, 0x1f47e4fcu, 0x0008edcbu}, + {0x80907c4bu, 0x80907c4bu, 0x9e9e1748u, 0x8009a62du}, + {0x80ddaa35u, 0x80ddaa35u, 0x9fc47dc4u, 0x80301236u}, + {0x80bdd79du, 0x80bdd79du, 0x9e398752u, 0x801decc6u}, + {0x002db992u, 0x002db992u, 0x1ee684efu, 0x002db992u}, + {0x001d947eu, 0x001d947eu, 0x1e8933ddu, 0x001d947eu}, + {0x801c3ba4u, 0x801c3ba4u, 0x1f19a9f3u, 0x801c3ba4u}, + {0x0009f699u, 0x0009f699u, 0x9eb3c397u, 0x0009f699u}, + {0x00070609u, 0x00070609u, 0x1e32d28fu, 0x00070609u}, + {0x8003f7cau, 0x8003f7cau, 0x1f07432fu, 0x8003f7cau}, + {0x00029021u, 0x00029021u, 0x1ea63ad1u, 0x00029021u}, + {0x00a816e4u, 0x00a816e4u, 0x9f42587au, 0x00a816e4u}, + {0x001bc9acu, 0x001bc9acu, 0x202187c5u, 0x001bc9acu}, + {0x001ec83du, 0x001ec83du, 0x1fe6f5d6u, 0x00fe0187u}, + {0x8004f1b2u, 0x8004f1b2u, 0x1f2af88bu, 0x8004f1b2u}, + {0x800416beu, 0x800416beu, 0x1eeac416u, 0x800416beu}, + {0x802a35edu, 0x802a35edu, 0x9e406f2bu, 0x802a35edu} +}; + +std::array BufferDescriptor(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::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::optional Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + 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}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + request.context.floatMode = floatMode; + std::optional result; + try { + result.emplace(ShaderRecompiler::Recompile(request)); + } catch (const std::exception& error) { + return std::string(error.what()); + } + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return std::nullopt; +} + +void Check(bool ieee) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t i = 0; i < Results; ++i) { + const std::uint32_t actual = Output[tid * Results + i]; + Require(actual == Expected[tid][i], std::string(Names[i]) + " f32 denormal mode 2" + (ieee ? ", IEEE=1" : ", IEEE=0") + " with v4 " + Hex(Rows[tid][0]) + ", v5 " + Hex(Rows[tid][1]) + ", v6 " + Hex(Rows[tid][2]) + " is " + Hex(actual) + ", expected " + Hex(Expected[tid][i])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool keepsDenormals = device->Target().float32DenormPreserve; + std::uint32_t checked = 0u; + for (const bool ieee : {false, true}) { + const ShaderRecompiler::ShaderFloatMode floatMode{0xe0u, true, ieee, false}; + const auto refusal = Run(*device, floatMode); + if (refusal) { + Require(!keepsDenormals && refusal->find("needs DenormPreserve 32") != std::string::npos, "FLOAT_MODE " + Hex(floatMode.floatMode) + ": " + *refusal); + continue; + } + ++checked; + Check(ieee); + } + std::printf("f32 fma denormal mode 2 tests passed (%u of 2 IEEE modes run; the others need DenormPreserve 32)\n", checked); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32FractFloorDenormalModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32FractFloorDenormalModes.cpp new file mode 100644 index 0000000000..a07fcb0547 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32FractFloorDenormalModes.cpp @@ -0,0 +1,254 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e144104u, 0x7e164904u, 0x7e184504u, 0x7e1a8104u, + 0xe0781000u, 0x80010a01u, 0xbf810000u, +}; + +constexpr const char* Names[Results] = {"v_fract_f32", "v_floor_f32", "v_ceil_f32", "v_frexp_mant_f32"}; + +constexpr std::uint32_t Values[Threads] = { + 0x00000000u, 0x00000001u, 0x00000002u, 0x00000003u, 0x00400000u, 0x007ffffeu, 0x007fffffu, 0x00800000u, + 0x00800001u, 0x00ffffffu, 0x01000000u, 0x0c000000u, 0x33ffffffu, 0x34000000u, 0x3effffffu, 0x3f000000u, + 0x3f7fffffu, 0x3f800000u, 0x3fc00000u, 0x40490fdbu, 0x4b000000u, 0x4b7fffffu, 0x7f7fffffu, 0x7f800000u, + 0x7fc00000u, 0x7fc12345u, 0x80000000u, 0x80000001u, 0x80000002u, 0x80000003u, 0x80400000u, 0x807ffffeu, + 0x807fffffu, 0x80800000u, 0x80800001u, 0x80ffffffu, 0x81000000u, 0x8c000000u, 0xb3ffffffu, 0xb4000000u, + 0xbeffffffu, 0xbf000000u, 0xbf7fffffu, 0xbf800000u, 0xbfc00000u, 0xc0490fdbu, 0xcb000000u, 0xcb7fffffu, + 0xff7fffffu, 0xff800000u, 0xffc00000u, 0xffc12345u, 0x804fbb3fu, 0x805ef2e1u, 0x0065c8e8u, 0x806b9087u, + 0x0063529du, 0x0053119au, 0x007350b6u, 0x800e7d89u, 0x003c0a75u, 0x0030bcadu, 0x004976e8u, 0x0001adbau +}; +constexpr std::uint32_t Mode1Expected[Threads][Results] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f400000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f7ffffcu}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f7ffffeu}, + {0x00800000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x00800001u, 0x00000000u, 0x3f800000u, 0x3f000001u}, + {0x00ffffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x01000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x0c000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x33ffffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x34000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x3effffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x3f000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x3f7fffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f000000u}, + {0x3f000000u, 0x3f800000u, 0x40000000u, 0x3f400000u}, + {0x3e10fdb0u, 0x40400000u, 0x40800000u, 0x3f490fdbu}, + {0x00000000u, 0x4b000000u, 0x4b000000u, 0x3f000000u}, + {0x00000000u, 0x4b7fffffu, 0x4b7fffffu, 0x3f7fffffu}, + {0x00000000u, 0x7f7fffffu, 0x7f7fffffu, 0x3f7fffffu}, + {0xffc00000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf400000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf7ffffcu}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf7ffffeu}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000001u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7ffffeu, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x3f7ffffeu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f000000u, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x3f000000u, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x33800000u, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x00000000u, 0xbf800000u, 0xbf800000u, 0xbf000000u}, + {0x3f000000u, 0xc0000000u, 0xbf800000u, 0xbf400000u}, + {0x3f5bc094u, 0xc0800000u, 0xc0400000u, 0xbf490fdbu}, + {0x00000000u, 0xcb000000u, 0xcb000000u, 0xbf000000u}, + {0x00000000u, 0xcb7fffffu, 0xcb7fffffu, 0xbf7fffffu}, + {0x00000000u, 0xff7fffffu, 0xff7fffffu, 0xbf7fffffu}, + {0xffc00000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf1f767eu}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf3de5c2u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f4b91d0u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf57210eu}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f46a53au}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f262334u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f66a16cu}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf67d890u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f7029d4u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f42f2b4u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f12edd0u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f56dd00u} +}; +constexpr std::uint32_t Mode2Expected[Threads][Results] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00800000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x00800001u, 0x00000000u, 0x3f800000u, 0x3f000001u}, + {0x00ffffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x01000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x0c000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x33ffffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x34000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x3effffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x3f000000u, 0x00000000u, 0x3f800000u, 0x3f000000u}, + {0x3f7fffffu, 0x00000000u, 0x3f800000u, 0x3f7fffffu}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f000000u}, + {0x3f000000u, 0x3f800000u, 0x40000000u, 0x3f400000u}, + {0x3e10fdb0u, 0x40400000u, 0x40800000u, 0x3f490fdbu}, + {0x00000000u, 0x4b000000u, 0x4b000000u, 0x3f000000u}, + {0x00000000u, 0x4b7fffffu, 0x4b7fffffu, 0x3f7fffffu}, + {0x00000000u, 0x7f7fffffu, 0x7f7fffffu, 0x3f7fffffu}, + {0xffc00000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000001u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7fffffu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f7ffffeu, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x3f7ffffeu, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x3f000000u, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x3f000000u, 0xbf800000u, 0x80000000u, 0xbf000000u}, + {0x33800000u, 0xbf800000u, 0x80000000u, 0xbf7fffffu}, + {0x00000000u, 0xbf800000u, 0xbf800000u, 0xbf000000u}, + {0x3f000000u, 0xc0000000u, 0xbf800000u, 0xbf400000u}, + {0x3f5bc094u, 0xc0800000u, 0xc0400000u, 0xbf490fdbu}, + {0x00000000u, 0xcb000000u, 0xcb000000u, 0xbf000000u}, + {0x00000000u, 0xcb7fffffu, 0xcb7fffffu, 0xbf7fffffu}, + {0x00000000u, 0xff7fffffu, 0xff7fffffu, 0xbf7fffffu}, + {0xffc00000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +std::array BufferDescriptor(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::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::optional Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Input[tid * Inputs] = Values[tid]; + 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}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + request.context.floatMode = floatMode; + std::optional result; + try { + result.emplace(ShaderRecompiler::Recompile(request)); + } catch (const std::exception& error) { + return std::string(error.what()); + } + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return std::nullopt; +} + +void Check(std::uint32_t denormals, bool ieee) { + const auto& expected = denormals == 1u ? Mode1Expected : Mode2Expected; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t i = 0; i < Results; ++i) { + const std::uint32_t actual = Output[tid * Results + i]; + Require(actual == expected[tid][i], std::string(Names[i]) + " f32 denormal mode " + std::to_string(denormals) + (ieee ? ", IEEE=1" : ", IEEE=0") + " of " + Hex(Values[tid]) + " is " + Hex(actual) + ", expected " + Hex(expected[tid][i])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool keepsDenormals = device->Target().float32DenormPreserve; + std::uint32_t checked = 0u; + for (const std::uint32_t denormals : {1u, 2u}) { + for (const bool ieee : {false, true}) { + const ShaderRecompiler::ShaderFloatMode floatMode{0xc0u | denormals << 4u, true, ieee, false}; + const auto refusal = Run(*device, floatMode); + if (refusal) { + Require(denormals == 1u && !keepsDenormals && refusal->find("needs DenormPreserve 32") != std::string::npos, "FLOAT_MODE " + Hex(floatMode.floatMode) + ": " + *refusal); + continue; + } + ++checked; + Check(denormals, ieee); + } + } + std::printf("f32 fract, floor, ceil and frexp_mant denormal mode 1 and 2 tests passed (%u of 4 modes run; the others need DenormPreserve 32)\n", checked); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GcnBufferFormatLookup.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GcnBufferFormatLookup.cpp new file mode 100644 index 0000000000..e85dcfb736 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GcnBufferFormatLookup.cpp @@ -0,0 +1,208 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "Recompiler.hpp" +#include "RdnaDecoder/RdnaDescriptorFormat.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 32; +constexpr std::uint32_t Vertices = 3; +constexpr std::uint32_t Fill = 0xdeadbeefu; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uintptr_t GcnAddressLimit = std::uintptr_t{1} << 44u; +constexpr std::array InputWords{0x12345678u, 0x9abcdef0u, 0x0badf00du, 0x600dcafeu}; +alignas(256) std::array Pixels{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0300000u, 0x80010100u, 0xbf8c0f70u, 0xe0700000u, 0x80020100u, 0xf80008cfu, 0x03020100u, 0xbf810000u, +}; + +alignas(256) constexpr std::array PixelCode{ + 0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u, +}; + +class LowBlock { +public: + LowBlock() : block(static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, BlockBytes))) { + Require(block != nullptr, "GCN buffer format lookup: cannot allocate the buffer block in the guest arena"); + GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, 0x04u, BlockBytes); + } + + ~LowBlock() { + GuestArena::GuestArenaReset_nid_postfix(block, BlockBytes); + GuestArena::GuestArenaRelease_nid_postfix(block, BlockBytes); + } + + LowBlock(const LowBlock&) = delete; + LowBlock& operator=(const LowBlock&) = delete; + + std::uintptr_t Address() const { return reinterpret_cast(block); } + bool Addressable() const { return Address() + BlockBytes <= GcnAddressLimit; } + std::span Input() const { return std::span(block, 4); } + std::span Output() const { return std::span(block + 64, 4); } + + std::array Stored() const { + std::array words{}; + AgcDriver::GuestMemory::Read(reinterpret_cast(Output().data()), std::as_writable_bytes(std::span(words)), 4); + return words; + } + +private: + std::uint32_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes, std::uint32_t dataFormat, std::uint32_t numberFormat) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0xfacu | (numberFormat << 12u) | (dataFormat << 15u)}; +} + +std::vector UserData(const LowBlock& block, std::uint32_t dataFormat, std::uint32_t numberFormat) { + std::vector userData(12, 0u); + const auto input = BufferDescriptor(block.Input().data(), 16u, dataFormat, numberFormat); + const auto output = BufferDescriptor(block.Output().data(), 16u, 4u, 4u); + std::copy(input.begin(), input.end(), userData.begin() + 4); + std::copy(output.begin(), output.end(), userData.begin() + 8); + return userData; +} + +ShaderRecompiler::RecompileRequest VertexRequest(std::span userData, const ShaderRecompiler::SpirvTarget& target, std::span memory) { + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {64u, 0, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, memory}, + target, + {0, 0, 0, 64} + }; + request.useCache = false; + return request; +} + +std::uint32_t PrivateStores(std::span words) { + std::set privateMemory; + std::uint32_t stores = 0; + for (std::size_t offset = 5; offset < words.size();) { + const auto count = words[offset] >> spv::WordCountShift; + Require(count != 0 && count <= words.size() - offset, "GCN buffer format lookup: malformed SPIR-V instruction"); + const auto opcode = words[offset] & spv::OpCodeMask; + if (opcode == spv::OpVariable && count >= 4 && words[offset + 3] == spv::StorageClassPrivate) privateMemory.insert(words[offset + 2]); + if ((opcode == spv::OpAccessChain || opcode == spv::OpInBoundsAccessChain) && count >= 4 && privateMemory.contains(words[offset + 3])) privateMemory.insert(words[offset + 2]); + if (opcode == spv::OpStore && count >= 3 && privateMemory.contains(words[offset + 1])) ++stores; + offset += count; + } + return stores; +} + +void CheckModule(const AgcDriver::VulkanDevice& device, const LowBlock& block, std::span memory) { + auto target = device.Target(); + target.registrationModules = true; + const auto userData = UserData(block, 4u, 4u); + const auto result = ShaderRecompiler::Recompile(VertexRequest(userData, target, memory)); + Require(result.registrationModule, "GCN buffer format lookup: the vertex shader was not emitted in registration form"); + const auto stores = PrivateStores(result.spirv.Words()); + Require(stores == 0u, "GCN buffer format lookup: the registration module stores " + std::to_string(stores) + " times into private memory to translate DATA_FORMAT and NUM_FORMAT, which drivers keep in per-invocation scratch"); +} + +void Draw(AgcDriver::VulkanDevice& device, const LowBlock& block, std::uint32_t dataFormat, std::uint32_t numberFormat, std::span vertexMemory, const ShaderRecompiler::RecompileResult& pixelResult, std::uint32_t vertexPush, std::vector& registrationModule) { + Pixels.fill(std::byte{0x40}); + std::copy(InputWords.begin(), InputWords.end(), block.Input().begin()); + std::fill(block.Output().begin(), block.Output().end(), Fill); + const auto userData = UserData(block, dataFormat, numberFormat); + const auto vertexResult = ShaderRecompiler::Recompile(VertexRequest(userData, device.Target(), vertexMemory)); + const auto what = "DATA_FORMAT " + std::to_string(dataFormat) + " NUM_FORMAT " + std::to_string(numberFormat); + Require(static_cast(vertexResult.pushConstants.size()) == vertexPush, "GCN buffer format lookup, " + what + ": the vertex push constants changed size"); + if (device.Target().registrationModules) { + if (registrationModule.empty()) registrationModule = vertexResult.spirv.Copy(); + Require(std::ranges::equal(vertexResult.spirv.Words(), registrationModule), "GCN buffer format lookup, " + what + ": registration modules: the V# specialized the vertex module"); + } + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, device.Target())} + }}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + 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 = {}; + const AgcDriver::Pm4::DrawParameters draw{0, Vertices, 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); + const auto translated = ShaderRecompiler::GcnBufferFormats()[(dataFormat << 3u) | numberFormat]; + const auto expected = translated != 0u ? InputWords[0] : 0u; + const auto output = block.Stored(); + char text[96]; + std::snprintf(text, sizeof(text), ": translated format %u, loaded 0x%08x, expected 0x%08x", translated, output[0], expected); + Require(output[0] == expected, "GCN buffer format lookup, " + what + text); + Require(std::all_of(output.begin() + 1, output.end(), [](std::uint32_t value) { return value == Fill; }), "GCN buffer format lookup, " + what + ": the store wrote past its dword"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + ShaderRecompiler::SetGcnGuestShaders(true); + const LowBlock block; + if (!block.Addressable()) { + std::printf("skipped, the guest arena block at 0x%llx lies above the 44-bit GCN V# address range\n", static_cast(block.Address())); + return VulkanTestSkipped; + } + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + CheckModule(*device, block, vertexMemory); + const auto firstUserData = UserData(block, 4u, 4u); + const auto vertexPush = static_cast(ShaderRecompiler::Recompile(VertexRequest(firstUserData, device->Target(), vertexMemory)).pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, + {64u, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + device->Target(), + PixelPushLayout(vertexPush, device->Target()) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + std::vector registrationModule; + for (std::uint32_t dataFormat = 0; dataFormat < 16u; ++dataFormat) { + for (std::uint32_t numberFormat = 0; numberFormat < 8u; ++numberFormat) Draw(*device, block, dataFormat, numberFormat, vertexMemory, pixelResult, vertexPush, registrationModule); + } + std::puts("GCN buffer format lookup tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleShortCube.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleShortCube.cpp new file mode 100644 index 0000000000..0a8c867276 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleShortCube.cpp @@ -0,0 +1,143 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 6; +constexpr std::uint32_t Words = 8; +constexpr std::uint32_t ResultWord = 4; +constexpr std::uint32_t Size = 64; +constexpr std::uint32_t Slices = 8; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t TypeCube = 11; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array SampleCode{ + 0x34020085, 0xe03c1000, 0x80000201, 0xbf8c3f70, 0xf09c0f18, 0x00610502, 0xbf8c3f70, 0xe0781010, 0x80000501, 0xbf810000, +}; + +alignas(256) constexpr std::array LoadCode{ + 0x34020085, 0xe03c1000, 0x80000201, 0xbf8c3f70, 0xf0001f18, 0x00010502, 0xbf8c3f70, 0xe0781010, 0x80000501, 0xbf810000, +}; + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +std::array TextureDescriptor(std::uint32_t baseArray, std::uint32_t depth) { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32Float << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | (TypeCube << 28u), + depth | (baseArray << 16u), + 0u, + 0u, + 0u, + }; +} + +void FillTexture() { + const auto surface = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(0u, Slices - 1u))); + Require(surface.layers == Slices && surface.guestBytes <= Texels.size(), "image sample short cube: the slices do not fit the texel storage"); + for (std::uint32_t slice = 0; slice < Slices; ++slice) { + const auto value = Bits(static_cast(slice + 1u)); + for (std::uint64_t offset = 0; offset < surface.layerBytes; offset += 4u) { + std::copy_n(reinterpret_cast(&value), 4u, &Texels[surface.GuestLayerOffset(slice) + offset]); + } + } +} + +std::array BufferDescriptor(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 BilinearSampler() { + return {0u, 0xfffu << 12u, (1u << 20u) | (1u << 22u) | (1u << 26u), 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t baseArray, std::uint32_t depth) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(baseArray, depth); + const auto sampler = BilinearSampler(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + 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{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckSamples(AgcDriver::VulkanDevice& device, std::uint32_t baseArray, std::uint32_t depth) { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Buffer[tid * Words] = Bits(1.5f); + Buffer[tid * Words + 1u] = Bits(1.5f); + Buffer[tid * Words + 2u] = Bits(static_cast(tid)); + } + Run(device, SampleCode, baseArray, depth); + for (std::uint32_t face = 0; face < Threads; ++face) { + const float value = std::bit_cast(Buffer[face * Words + ResultWord]); + const float expected = static_cast(baseArray + face + 1u); + Require(value == expected, "image_sample_lz cube with BASE_ARRAY " + std::to_string(baseArray) + " and DEPTH " + std::to_string(depth) + ": face " + std::to_string(face) + " read " + std::to_string(value) + ", expected slice " + std::to_string(baseArray + face) + " (" + std::to_string(expected) + ")"); + } +} + +void CheckLoadRejected(AgcDriver::VulkanDevice& device) { + Buffer.fill(0u); + try { + Run(device, LoadCode, 0u, 4u); + } catch (const std::exception& error) { + Require(std::string_view(error.what()).find("fewer than six slices") != std::string_view::npos, std::string("image_load through a cube with five slices failed for another reason: ") + error.what()); + return; + } + throw std::runtime_error("image_load through a cube with five slices was accepted, but hardware loads stop at DEPTH"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillTexture(); + for (const auto [baseArray, depth] : {std::pair{0u, 5u}, std::pair{0u, 0u}, std::pair{0u, 4u}, std::pair{0u, 3u}, std::pair{0u, 1u}, std::pair{2u, 6u}, std::pair{2u, 5u}}) CheckSamples(*device, baseArray, depth); + CheckLoadRejected(*device); + std::puts("image sample short cube tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp index 2e4954f926..19753d3482 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTable.cpp @@ -1498,11 +1498,19 @@ constexpr std::uint32_t TargetWidth = 64; constexpr std::uint32_t TargetHeight = 32; alignas(256) std::array Pixels{}; alignas(256) std::array VertexResults{}; -alignas(256) std::array PixelResults{}; +alignas(256) std::array PixelResults{}; alignas(256) std::array GraphicsTable{}; constexpr std::array, 3> Triangle{{{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}}}; -std::pair DrawTables(AgcDriver::VulkanDevice& device, std::uint32_t vertexKey, std::uint32_t pixelKey) { +alignas(256) constexpr std::array SharedSamplerCode{ + 0xf4080700u, 0xfa000020u, 0xf4080800u, 0xfa000030u, 0xf4080900u, 0xfa000040u, 0xf4080a00u, 0xfa000050u, + 0x7e0202ffu, 0x3c400000u, 0x7e0402ffu, 0x3e000000u, + 0xf09c8f08u, 0x01090401u, 0xf09c8f08u, 0x010a0c01u, + 0x7e100280u, 0xe0781000u, 0x80070408u, 0xe0781010u, 0x80070c08u, + 0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u, +}; + +std::pair DrawTables(AgcDriver::VulkanDevice& device, std::uint32_t vertexKey, std::uint32_t pixelKey, bool sharedSampler = false) { const auto target = device.Target(); Pixels.fill(std::byte{0}); VertexResults.fill(0xdeadbeefu); @@ -1537,9 +1545,15 @@ std::pair DrawTables(AgcDriver::VulkanDevice& devi std::copy(table.begin(), table.end(), pixelUserData.begin()); pixelUserData[4] = pixelKey; std::copy(pixelOutput.begin(), pixelOutput.end(), pixelUserData.begin() + 8); - const std::array pixelMemory{{{reinterpret_cast(PixelTableCode.data()), std::as_bytes(std::span(PixelTableCode))}, {reinterpret_cast(GraphicsTable.data()), std::as_bytes(std::span(GraphicsTable))}}}; + const std::span pixelCode = sharedSampler ? std::span(SharedSamplerCode) : std::span(PixelTableCode); + if (sharedSampler) { + const auto root = reinterpret_cast(Srt.data()); + pixelUserData = {static_cast(root), static_cast(root >> 32u)}; + std::copy(pixelOutput.begin(), pixelOutput.end(), Srt.begin() + 8); + } + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}, {reinterpret_cast(GraphicsTable.data()), std::as_bytes(std::span(GraphicsTable))}, {reinterpret_cast(Srt.data()), std::as_bytes(std::span(Srt))}}}; ShaderRecompiler::RecompileRequest fragment{ - {ShaderStage::Fragment, reinterpret_cast(PixelTableCode.data()), PixelTableCode, 0, {}}, + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, target, PixelPushLayout(vertexPush, target) @@ -1570,6 +1584,41 @@ std::pair DrawTables(AgcDriver::VulkanDevice& devi return {vertexResult.PipelineVariantId(), pixelResult.PipelineVariantId()}; } +void RunSharedSamplerTests(AgcDriver::VulkanDevice& device, const std::string& mode, bool draw) { + Require(mode == "uint" || mode == "sint" || mode == "point", "unknown shared sampler mode"); + Fill(); + const auto floatImage = Image(FloatTexels.data(), Format32Float); + const auto integerImage = mode == "sint" ? Image(SintTexels.data(), Format32SInt) : Image(UintTexels.data(), Format32UInt); + const auto& sampler = mode == "point" ? PointClamp : LinearWrap; + std::array images{}; + std::copy(integerImage.begin(), integerImage.end(), images.begin()); + std::copy(floatImage.begin(), floatImage.end(), images.begin() + 4); + std::copy(sampler.begin(), sampler.end(), Srt.begin() + 12); + std::copy(images.begin(), images.end(), Srt.begin() + 16); + const auto integer = mode == "sint" ? static_cast(SintTexels[0]) : UintTexels[0]; + const auto filtered = std::bit_cast(mode == "point" ? FloatTexels[0] : 0.5f); + if (draw) { + std::copy(floatImage.begin(), floatImage.end(), GraphicsTable.begin()); + DrawTables(device, 0u, 0u, true); + Require(PixelResults[0] == integer, "shared sampler draw: integer texel changed"); + Require(PixelResults[4] == filtered, "shared sampler draw: float filtering changed"); + } else { + alignas(256) constexpr std::array code{ + 0xf4080700u, 0xfa000020u, 0xf4080800u, 0xfa000030u, 0xf4080900u, 0xfa000040u, 0xf4080a00u, 0xfa000050u, + 0x360200bfu, 0x7e020d01u, 0x060202ffu, 0x3f400000u, 0x100202ffu, 0x3c800000u, 0x7e0402ffu, 0x3e000000u, + 0xf09c8f08u, 0x01090401u, 0xf09c8f08u, 0x010a0c01u, + 0x7e120202u, 0x34101286u, 0x4a101100u, 0x34101084u, + 0xe0781000u, 0x80070408u, 0xe0781400u, 0x80070c08u, 0xbf810000u, + }; + const auto result = RunDirectCode(device, code, sampler, images); + Require(Output[0] == integer, "shared sampler dispatch: integer texel changed"); + Require(Output[256] == filtered, "shared sampler dispatch: float filtering changed"); + Require(Holds(result.samplers, sampler), "shared sampler dispatch: original sampler missing"); + Require(Holds(result.samplers, mode == "point" ? PointClamp : PointWrap), "shared sampler dispatch: point sampler missing"); + } + std::printf("shared sampler %s %s: integer=%u float_bits=%u passed\n", draw ? "draw" : "dispatch", mode.c_str(), integer, filtered); +} + void RunGraphicsTests(AgcDriver::VulkanDevice& device) { const auto floatImage = Image(FloatTexels.data(), Format32Float); const auto uintImage = Image(UintTexels.data(), Format32UInt); @@ -1585,6 +1634,9 @@ void RunGraphicsTests(AgcDriver::VulkanDevice& device) { for (std::uint32_t vertex = 0; vertex < 3u; ++vertex) Require(VertexResults[vertex * 4u] == static_cast(SintTexels[0]), "image table graphics: the vertex stage read the wrong entry after the keys changed"); Require(PixelResults[0] == std::bit_cast(FloatTexels[0]), "image table graphics: the pixel stage read the wrong entry after the keys changed"); Require(first == second, "image table graphics: other table keys built another pipeline"); + for (const auto* mode : {"uint", "sint", "point"}) { + for (const bool draw : {false, true}) RunSharedSamplerTests(device, mode, draw); + } } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp index 765947dc99..104fb882b6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include namespace { @@ -66,6 +68,14 @@ constexpr std::array WideSlots{SrtSlots - 1u, SrtSlots - 2u, Srt constexpr std::array WideTableCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x9310b010u, 0xf42c0502u, 0x20000000u, 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; constexpr std::array TableCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x8f108510u, 0xf42c0502u, 0x20000000u, 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; +constexpr std::size_t VertexOffset = 256; +constexpr std::size_t PixelOffset = 512; +constexpr std::size_t DrawSrtBytes = 64; +constexpr std::size_t DrawKeys = SrtSlots * SrtBytes / DrawSrtBytes; +constexpr std::size_t ColorBytes = 1024; +constexpr std::array TriangleVertexCode{0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, 0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u}; +constexpr std::array TablePixelCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xd7650002u, 0x000100c1u, 0x7e200502u, 0x8f108510u, 0xbf8cc07fu, 0xf42c0502u, 0x20000000u, 0x7e000280u, 0x7e020280u, 0xbf8cc07fu, 0xf09c0f08u, 0x00450000u, 0xbf8c3f70u, 0xf800180fu, 0x03020100u, 0xbf810000u}; + class GuestBlock { public: GuestBlock() { @@ -166,6 +176,95 @@ void SrtMovingPerDispatch(const GuestBlock& block) { Require(most <= Driver::DispatchCacheBytes(), "image table cache bounds: dispatches reading a 2 MiB table through " + std::to_string(SrtSlots) + " SRTs kept " + std::to_string(most >> 20u) + " MiB of captured words in the dispatch cache, over its budget of " + std::to_string(Driver::DispatchCacheBytes() >> 20u) + " MiB"); } +void Draw(const GuestBlock& block, std::size_t key) { + const auto color = block.At(OutputOffset); + const auto vertex = block.At(VertexOffset); + const auto pixel = block.At(PixelOffset); + const auto srt = block.At(SrtOffset + key * DrawSrtBytes); + const std::vector> context{ + {0x2d5, 0x2000}, {0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800}, {0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0}, + {0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa}, {0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff}, + {0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240}, {0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010}, + {0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028}, {0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u}, {0x3b8, 0x9000000}, {0x1e0, 0}, + {0xc, 0}, {0xd, 0x40040}, {0x81, 0x80000000}, {0x82, 0x40040}, {0x90, 0x80000000}, {0x91, 0x40040}, {0x94, 0x80000000}, {0x95, 0x40040}, + {0x318, static_cast(color >> 8u)}, {0x390, static_cast(color >> 40u)}, + {0x10f, std::bit_cast(32.0f)}, {0x110, std::bit_cast(32.0f)}, {0x111, std::bit_cast(-2.0f)}, {0x112, std::bit_cast(2.0f)}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, {0xb4, 0}, {0xb5, std::bit_cast(1.0f)}, {0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 0}, {0x8c, 0}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 2u << 1u}, + {0x00c, static_cast(srt)}, {0x00d, static_cast(srt >> 32u)}, + }; + std::vector words; + const auto append = [&](const std::vector& packet) { words.insert(words.end(), packet.begin(), packet.end()); }; + append(Pm4(0x79, {0x242, 4})); + for (const auto& [offset, value] : context) append(Pm4(0x69, {offset, value})); + for (const auto& [offset, value] : shader) append(Pm4(0x76, {offset, value})); + append(Pm4(0x2d, {3, 2})); + Packet dcb{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&dcb) == 0, "image table draw cache bounds: the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); +} + +unsigned BrightestColor(const GuestBlock& block) { + AgcDriver::GuestMemory::FlushGpuWrites(block.At(OutputOffset), ColorBytes); + const auto* bytes = block.Bytes(OutputOffset); + return *std::max_element(bytes, bytes + ColorBytes); +} + +void SrtMovingPerDraw(const GuestBlock& block) { + using AgcDriver::DriverDetail::Driver; + std::memcpy(block.Bytes(VertexOffset), TriangleVertexCode.data(), sizeof(TriangleVertexCode)); + std::memcpy(block.Bytes(PixelOffset), TablePixelCode.data(), sizeof(TablePixelCode)); + const auto fill = [&](std::uint32_t surface) { + for (std::uint32_t record = 0; record < Records; ++record) SetRecord(block, record, surface * Aliases); + }; + fill(3u); + for (std::size_t key = 0; key < DrawKeys; ++key) { + auto* srt = reinterpret_cast(block.Bytes(SrtOffset + key * DrawSrtBytes)); + std::fill(srt, srt + DrawSrtBytes / 4u, 0u); + const auto heap = V(block.At(HeapOffset + key * Stride), Stride, Records - static_cast(key)); + std::copy(heap.begin(), heap.end(), srt); + } + auto& driver = Driver::Get(); + const auto budget = Driver::DrawCacheBytes(); + const auto before = driver.DrawCacheCounters(); + std::uint64_t most = 0; + std::size_t entries = 0; + for (std::size_t key = 0; key < DrawKeys; ++key) { + for (std::size_t sight = 0; sight < 2; ++sight) { + Draw(block, key); + const auto size = driver.DrawCacheSize(); + Require(size.variantBytes <= budget, "image table draw cache bounds: after draw " + std::to_string(2 * key + sight) + " the draw cache kept " + std::to_string(size.entries) + " entries holding " + std::to_string(size.variantBytes >> 10u) + " KiB, over its budget of " + std::to_string(budget >> 10u) + " KiB"); + most = std::max(most, size.variantBytes); + entries = std::max(entries, size.entries); + } + } + const auto filled = driver.DrawCacheCounters(); + const auto inserts = filled.inserts - before.inserts; + const auto evictions = filled.evictions - before.evictions; + const auto byteEvictions = filled.byteEvictions - before.byteEvictions; + const auto counts = " (" + std::to_string(inserts) + " inserts, " + std::to_string(evictions) + " evictions, " + std::to_string(byteEvictions) + " over the byte budget, at most " + std::to_string(entries) + " entries holding " + std::to_string(most >> 10u) + " KiB)"; + Require(BrightestColor(block) == 3u, "image table draw cache bounds: the draws through the table wrote " + std::to_string(BrightestColor(block)) + ", expected 3"); + Require(entries >= 2 && inserts >= DrawKeys / 2, "image table draw cache bounds: the draws were not kept in the draw cache" + counts); + Require(inserts * (most / entries) > 2 * budget, "image table draw cache bounds: the draws did not insert more than twice the budget" + counts); + Require(byteEvictions >= 1 && byteEvictions == evictions, "image table draw cache bounds: the draw cache was not bounded by its byte budget" + counts); + + fill(9u); + std::memset(block.Bytes(OutputOffset), 0, ColorBytes); + Draw(block, 0); + const auto rebuilt = driver.DrawCacheCounters(); + Require(rebuilt.absent - filled.absent == 1 && rebuilt.inserts - filled.inserts == 1, "image table draw cache bounds: the first key, evicted, was not looked up without an entry and inserted again (" + std::to_string(rebuilt.absent - filled.absent) + " absent, " + std::to_string(rebuilt.inserts - filled.inserts) + " inserts)"); + Require(BrightestColor(block) == 9u, "image table draw cache bounds: the evicted key drawn again wrote " + std::to_string(BrightestColor(block)) + ", expected 9"); + std::memset(block.Bytes(OutputOffset), 0, ColorBytes); + Draw(block, 0); + const auto hit = driver.DrawCacheCounters(); + Require(hit.hits - rebuilt.hits == 1, "image table draw cache bounds: the evicted key, inserted again, was not hit by its next draw"); + Require(BrightestColor(block) == 9u, "image table draw cache bounds: the hit on the rebuilt key wrote " + std::to_string(BrightestColor(block)) + ", expected 9"); + std::puts(("image table draw cache bounds passed" + counts).c_str()); +} + std::array TextureWords(const GuestBlock& block, std::uint32_t element) { const auto address = block.At(TexturesOffset + static_cast(element / Aliases) * TextureBytes); return {static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 7u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, element % Aliases, 0u}; @@ -337,9 +436,11 @@ void SetBudgets() { #ifdef _WIN32 _putenv_s("APS5_RESOURCE_CACHE_TABLE_ELEMENTS", "8192"); _putenv_s("APS5_DISPATCH_CACHE_BYTES", "16777216"); + _putenv_s("APS5_DRAW_CACHE_BYTES", "16777216"); #else setenv("APS5_RESOURCE_CACHE_TABLE_ELEMENTS", "8192", 1); setenv("APS5_DISPATCH_CACHE_BYTES", "16777216", 1); + setenv("APS5_DRAW_CACHE_BYTES", "16777216", 1); #endif } @@ -398,6 +499,7 @@ int main(int argc, char** argv) { } TablesChangingPerDispatch(block); SrtMovingPerDispatch(block); + SrtMovingPerDraw(block); AgcDriverShutdown_nid_postfix(); std::puts("image table cache bounds tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp index 306f35cbd0..eeb642dcbc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp @@ -162,6 +162,8 @@ public: explicit ProducerSubmissionGate(AgcDriver::VulkanDevice& device) : device(device) { #ifdef _WIN32 constexpr const char* libraryName = "vulkan-1.dll"; +#elif defined(__APPLE__) + constexpr const char* libraryName = "libvulkan.1.dylib"; #else constexpr const char* libraryName = "libvulkan.so.1"; #endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp index 5581d46dcc..5777b04e35 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -269,6 +270,17 @@ void Check(const std::byte* volume, const AgcDriver::Graphics::ColorTarget& colo int main(int argc, char** argv) { try { + const bool directReadback = std::getenv("APS5_TEST_DIRECT_READBACK") != nullptr; + if (directReadback) { + std::remove("private-readback.ready"); + char extent[64]; + std::snprintf(extent, sizeof(extent), "%ux%ux%u", Width, Height, Slices); +#ifdef _WIN32 + Require(_putenv_s("APS5_DUMP_STORAGE", extent) == 0 && _putenv_s("APS5_DUMP_STORAGE_GATE", "private-readback.ready") == 0, "cannot set private readback options"); +#else + Require(setenv("APS5_DUMP_STORAGE", extent, 1) == 0 && setenv("APS5_DUMP_STORAGE_GATE", "private-readback.ready", 1) == 0, "cannot set private readback options"); +#endif + } const bool vertexOnly = argc == 2 && std::strcmp(argv[1], "--vertex-only") == 0; Require(argc == 1 || vertexOnly, "usage: layered volume draw [--vertex-only]"); const auto device = OpenVulkanTestDevice(); @@ -283,6 +295,38 @@ int main(int argc, char** argv) { const auto color = VolumeTarget(volume.Data()); Run(*device, color, false, PixelCode, false); Check(volume.Data(), color, "vertex draw", WhiteBands); + if (directReadback) { + char name[80]; + std::snprintf(name, sizeof(name), "storage_direct_%llx_0.raw", static_cast(color.address)); + std::remove(name); + AgcDriver::Graphics::StorageTexture::FlushDumpSelected("closed readback gate fixture"); + Require(!std::filesystem::exists(name), "private readback ignored its closed gate"); + auto* trigger = std::fopen("private-readback.ready", "wb"); + Require(trigger != nullptr, "cannot create private readback trigger"); + Require(std::fclose(trigger) == 0, "cannot close private readback trigger"); + AgcDriver::Graphics::StorageTexture::FlushDumpSelected("private readback fixture"); + std::remove("private-readback.ready"); + auto* file = std::fopen(name, "rb"); + Require(file != nullptr, "clean volume image was not exported by direct readback"); + std::array header{}; + Require(std::fread(header.data(), sizeof(header), 1, file) == 1, "private readback header is incomplete"); + Require(header[0] >= Width && header[1] == Height && header[2] == VK_FORMAT_R8G8B8A8_UNORM, "private readback header disagrees with the volume"); + std::vector pixels(static_cast(header[0]) * Height * Slices * 4u); + Require(std::fread(pixels.data(), 1, pixels.size(), file) == pixels.size(), "private readback pixels are incomplete"); + Require(std::fgetc(file) == EOF, "private readback has unexpected trailing pixels"); + Require(std::fclose(file) == 0, "cannot close private readback"); + for (std::uint32_t z = 0; z < Slices; ++z) { + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const bool drawn = z >= FirstSlice && x / BandWidth == z - FirstSlice; + const auto expected = drawn ? WhiteBands[z - FirstSlice] : Kept; + for (std::uint32_t channel = 0; channel < 4; ++channel) Require(pixels[((z * Height + y) * header[0] + x) * 4u + channel] == expected, "private readback changed a volume texel"); + } + } + } + std::puts("private clean-volume direct readback pixels verified"); + return 0; + } const GuestVolume fieldVolume((VolumeTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}); const auto fieldColor = VolumeTarget(fieldVolume.Data()); Run(*device, fieldColor, false, LayerFieldPixelCode, true); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LogNearOne.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LogNearOne.cpp index 8821426321..49af2cd503 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LogNearOne.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LogNearOne.cpp @@ -193,8 +193,8 @@ bool IsPinned(std::uint32_t bits, std::uint32_t width) { return magnitude == 0u || magnitude >= infinity; } -void Run(AgcDriver::VulkanDevice& device) { - for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); +void Run(AgcDriver::VulkanDevice& device, const std::uint32_t (&rows)[Threads][Inputs] = Rows) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(rows[tid]), std::end(rows[tid]), &Input[tid * Inputs]); Output.fill(0xdeadbeefu); std::vector userData(8, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); @@ -235,6 +235,22 @@ void Check() { } } +void CheckBoundaries(AgcDriver::VulkanDevice& device) { + constexpr std::uint32_t cases[][3] = { + {0x3f5fffffu, 0xbe4544c7u, 12u}, + {0x3f600001u, 0xbe4544bau, 6u}, + {0x3f8fffffu, 0x3e2e00c8u, 6u}, + {0x3f900001u, 0x3e2e00dcu, 12u} + }; + std::uint32_t rows[Threads][Inputs]{}; + for (std::size_t lane = 0; lane < std::size(cases); ++lane) rows[lane][0] = cases[lane][0]; + Run(device, rows); + for (std::size_t lane = 0; lane < std::size(cases); ++lane) { + const auto actual = Output[lane * Results]; + Require(Distance(actual, cases[lane][1], 32u) <= cases[lane][2], "log near-one boundary: input " + Hex(cases[lane][0]) + " is " + Hex(actual) + ", expected " + Hex(cases[lane][1])); + } +} + } int main() { @@ -243,6 +259,7 @@ int main() { if (!device) return VulkanTestSkipped; Run(*device); Check(); + CheckBoundaries(*device); std::puts("log near one tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MeshBarycentricEmulation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MeshBarycentricEmulation.cpp index 58f20d44f9..9e1f7f4a4a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MeshBarycentricEmulation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MeshBarycentricEmulation.cpp @@ -115,6 +115,7 @@ ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, std::spa Pixels.fill(Unwritten); auto target = device.Target(); target.fragmentShaderBarycentricEnabled = false; + target.fragmentBarycentricBuiltins = false; const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Vertices.size()), 0, 1, 0, false}; std::vector meshUserData(12, 0u); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp index 5cc437a819..ad2cbf182e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampleRetirement.cpp @@ -466,6 +466,10 @@ public: void StorageRemap() { GuestBlock block; const auto target = Target(block); + if (MayReorderSamples(context, target.samples) && SampleOrderStorageFormat(context, target.format, target.elementBytes, target.samples) != VK_FORMAT_UNDEFINED) { + std::puts("the device stores a multisampled color target in the guest sample order, so it is born storable with no promotion to retire; the MSAA storage-promotion remap is skipped"); + return; + } const auto resource = Resource(block); std::shared_ptr held; VkImageView sampledView = VK_NULL_HANDLE, storageView = VK_NULL_HANDLE; @@ -530,6 +534,7 @@ public: const auto target = Target(block); auto twin = target; twin.format = VK_FORMAT_R8G8B8A8_SRGB; + const bool bornStorable = MayReorderSamples(context, target.samples) && SampleOrderStorageFormat(context, target.format, target.elementBytes, target.samples) != VK_FORMAT_UNDEFINED; const auto resource = Resource(block); VkImage renderedImage = VK_NULL_HANDLE, twinImage = VK_NULL_HANDLE, promotedImage = VK_NULL_HANDLE, promotedTwinImage = VK_NULL_HANDLE; Image rendered{}, promoted{}; @@ -545,7 +550,7 @@ public: { std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); promoted = ImageFor(MultisampleTargetStorageView(context, resource), &promotedImage); - Require(promoted.generation != rendered.generation, "MSAA storage promotion kept the rendered image"); + Require(bornStorable == (promoted.generation == rendered.generation), bornStorable ? "a born-storable MSAA target was promoted to a new image" : "MSAA storage promotion kept the rendered image"); const auto promotedTwin = MultisampleTargetView(context, twin); ImageFor(promotedTwin, &promotedTwinImage); Require(promotedTwinImage == promotedImage, "after storage promotion an sRGB use of the MSAA surface still views the retired image"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp index d3fbd1918a..9dc8ed5458 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" @@ -58,7 +59,7 @@ using namespace AgcDriver::Graphics; class Device { public: - Device() { + explicit Device(bool importHost = false) { #ifdef _WIN32 library = SDL_LoadObject("vulkan-1.dll"); #elif defined(__APPLE__) @@ -108,11 +109,39 @@ public: queue.queueFamilyIndex = family; queue.queueCount = 1u; queue.pQueuePriorities = &priority; - VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; + std::vector extensions; + if (importHost) { + const auto enumerateExtensions = function("vkEnumerateDeviceExtensionProperties"); + Check(enumerateExtensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties"); + extensions.resize(count); + Check(enumerateExtensions(context.physical, nullptr, &count, extensions.data()), "vkEnumerateDeviceExtensionProperties"); + } + const auto supported = [&](const char* name) { return std::any_of(extensions.begin(), extensions.end(), [&](const auto& item) { return std::strcmp(item.extensionName, name) == 0; }); }; + const bool externalHostExtensions = supported(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) && supported(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + const std::array externalHostNames{VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME}; + VkPhysicalDeviceBufferDeviceAddressFeatures address{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES}; + if (externalHostExtensions) { + VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &address}; + function("vkGetPhysicalDeviceFeatures2")(context.physical, &features); + } + const bool externalHost = externalHostExtensions && address.bufferDeviceAddress == VK_TRUE; + address.pNext = nullptr; + address.bufferDeviceAddressCaptureReplay = VK_FALSE; + address.bufferDeviceAddressMultiDevice = VK_FALSE; + VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, externalHost ? &address : nullptr}; device.queueCreateInfoCount = 1u; device.pQueueCreateInfos = &queue; device.pEnabledFeatures = &enabled; + device.enabledExtensionCount = externalHost ? static_cast(externalHostNames.size()) : 0u; + device.ppEnabledExtensionNames = externalHost ? externalHostNames.data() : nullptr; Check(function("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice"); + if (externalHost) { + VkPhysicalDeviceExternalMemoryHostPropertiesEXT host{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT}; + VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &host}; + function("vkGetPhysicalDeviceProperties2")(context.physical, &properties); + context.hostImportAlignment = host.minImportedHostPointerAlignment; + context.bufferDeviceAddress = true; + } context.deviceProc = function("vkGetDeviceProcAddr"); context.formatProperties = function("vkGetPhysicalDeviceFormatProperties"); context.imageFormatProperties = function("vkGetPhysicalDeviceImageFormatProperties"); @@ -1145,6 +1174,53 @@ void CheckUnwrittenGuestAccess(Context context, VkFormat format, std::uint32_t f std::printf("Native depth format=%u samples=%u: guest access retired the unwritten surface and refused the written one\n", static_cast(format), fragments); } +enum class BufferWrite { DepthPlane, Htile }; + +void CheckBufferWrite(Context context, VkFormat format, std::uint32_t fragments, BufferWrite write) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments)); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + TargetMemory memory(bytes, true), metadata(65536u, true), unrelated(65536u, true); + const auto guestByte = [](std::size_t offset) { return static_cast(offset * 5u + 1u); }; + for (std::size_t offset = 0u; offset < bytes; ++offset) memory.Data()[offset] = guestByte(offset); + std::memset(metadata.Data(), 0, metadataBytes); + DepthTarget target{memory.Address(), 0u, extent, format, 0.0f, 0u, metadata.Address(), false, fragments}; + target.depthSwizzle = 24u; + target.iterate256 = target.htilePipeAligned = target.htileDepthOnly = true; + Recorder recorder(context); + recorder.Activate(); + struct Cleanup { + Recorder& recorder; + Context context; + ~Cleanup() { recorder.Sync(); ClearCachedTextures(context.device); ClearDepthSurfaces(context.device); } + } cleanup{recorder, context}; + const auto resident = [&] { return HasResidentDepthGuards_nid_no_patch() && DepthSurfaceAt(target.address); }; + const auto upload = [&](std::uint64_t address, std::size_t length) { + GuestBufferMemory buffer(context); + buffer.AddWritable(address, length); + buffer.Upload(false); + Require(HostImportCovers(context, address, length), "the native depth buffer-write fixture was not bound as a host import"); + buffer.WriteBack(); + }; + Require(DepthSurfaceView(context, target) != VK_NULL_HANDLE && resident(), "native depth buffer-write fixture has no resident owner"); + upload(unrelated.Address(), 256u); + Require(resident(), "an unrelated buffer write retired a native depth surface"); + if (write == BufferWrite::DepthPlane) upload(target.address + bytes - 256u, 256u); + else upload(target.htileAddress, metadataBytes); + Require(!resident(), "a buffer the GPU may write left an unwritten native depth surface resident over it"); + for (std::size_t offset = 0u; offset < bytes; ++offset) Require(memory.Data()[offset] == guestByte(offset), "releasing an unwritten native depth surface for a buffer write changed its guest bytes"); + Require(std::all_of(metadata.Data(), metadata.Data() + metadataBytes, [](std::byte value) { return value == std::byte{0}; }), "releasing an unwritten native depth surface for a buffer write changed its HTILE"); +} + +void CheckBufferWrites(Context context, VkFormat format, std::uint32_t fragments) { + if (context.hostImportAlignment == 0u) { + std::printf("skipped native depth buffer writes: the device lacks VK_EXT_external_memory_host, VK_KHR_buffer_device_address or the bufferDeviceAddress feature\n"); + return; + } + for (const auto write : {BufferWrite::DepthPlane, BufferWrite::Htile}) CheckBufferWrite(context, format, fragments, write); + std::printf("Native depth format=%u samples=%u: a GPU buffer write over the depth or HTILE released the unwritten surface\n", static_cast(format), fragments); +} + void CheckHtileRouting(Context context, VkFormat format, std::uint32_t fragments) { const VkExtent2D extent{5u, 3u}; const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments)); @@ -1497,6 +1573,15 @@ int main(int argc, char** argv) { CheckUnwrittenGuestAccess(context, format, samples); } } + { + const Device importing(true); + const auto importContext = importing.GetContext(); + for (const auto format : {VK_FORMAT_D16_UNORM, VK_FORMAT_D32_SFLOAT}) { + for (const auto samples : {2u, 4u, 8u}) { + if (DepthSupported(importContext, format, samples)) CheckBufferWrites(importContext, format, samples); + } + } + } CheckEligibility(context); std::printf("Native multisampled depth cases checked=%u unavailable=%u\n", checked, unavailable); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp index 1acad9581f..4afab26121 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnorm4444PartialWrite.cpp @@ -475,6 +475,10 @@ int main() { std::puts("skipped, the device lacks fragmentShaderPixelInterlock"); return VulkanTestSkipped; } + if (!TargetHasCapability(device->Target(), spv::CapabilityStorageImageReadWithoutFormat)) { + std::puts("skipped, the device lacks shaderStorageImageReadWithoutFormat"); + return VulkanTestSkipped; + } const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 2); Block block(layout.Bytes()); const auto color = DecodeTarget(block); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PairedWaveZeroBranches.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PairedWaveZeroBranches.cpp new file mode 100644 index 0000000000..15d3b89297 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PairedWaveZeroBranches.cpp @@ -0,0 +1,151 @@ +#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" +#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::uint32_t Lanes = 64; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t OutputOffset = 4096; +constexpr std::uint32_t Sentinel = 0xdeadbeefu; +constexpr std::uint32_t Never = 0xffffffffu; + +alignas(256) constexpr std::array BranchesCode{ + 0x7d840080u, 0x7e040200u, 0x7e060201u, 0xd5010002u, 0x01a80502u, 0xd5010003u, 0x01a80703u, 0xdc308000u, + 0x017d0002u, 0xbf8c3f70u, 0xbe880380u, 0xbe8a047eu, 0x7da40006u, 0xbf880001u, 0x88088108u, 0xbefe040au, + 0x7d840006u, 0xbf860001u, 0x88088208u, 0xbf068006u, 0xbf840001u, 0x88088408u, 0x7daa0006u, 0xbf890001u, + 0x88088808u, 0xbefe040au, 0x7d8a0006u, 0xbf870001u, 0x88089008u, 0xbf078006u, 0xbf850001u, 0x8808a008u, + 0x34080082u, 0x4a0a0208u, 0xdc708000u, 0x00040504u, 0xbf810000u, +}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "paired wave zero branches: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + } + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +class Reserved { +public: + Reserved() { +#ifdef _WIN32 + range = VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE, PAGE_NOACCESS); +#else + range = mmap(nullptr, BlockBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (range == MAP_FAILED) range = nullptr; +#endif + Require(range != nullptr, "paired wave zero branches: cannot reserve an inaccessible range"); + } + ~Reserved() { +#ifdef _WIN32 + VirtualFree(range, 0, MEM_RELEASE); +#else + munmap(range, BlockBytes); +#endif + } + Reserved(const Reserved&) = delete; + Reserved& operator=(const Reserved&) = delete; + std::uint64_t Address() const { return static_cast(reinterpret_cast(range)); } + +private: + void* range = nullptr; +}; + +std::array Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, const Reserved& unmapped) { + std::memset(guest.Data(), 0, BlockBytes); + auto* output = guest.Data() + OutputOffset; + for (std::uint32_t lane = 0; lane < Lanes; ++lane) std::memcpy(output + lane * 4u, &Sentinel, 4u); + const auto data = static_cast(reinterpret_cast(guest.Data())); + const auto out = static_cast(reinterpret_cast(output)); + const std::vector userData{ + static_cast(data), static_cast(data >> 32u), + static_cast(unmapped.Address()), static_cast(unmapped.Address() >> 32u), + static_cast(out), static_cast(out >> 32u), + Never, + }; + const std::span code(BranchesCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 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.target.spannedWaves = false; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + std::array words{}; + std::memcpy(words.data(), output, sizeof(words)); + return words; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Reserved unmapped; + const auto words = Run(*device, guest, unmapped); + Require(words[0] == Sentinel, "paired wave zero branches: lane 0 kept running after its faulting read (wrote " + std::to_string(words[0]) + ")"); + const std::array branches{"s_cbranch_execz", "s_cbranch_vccz", "s_cbranch_scc0", "s_cbranch_execnz", "s_cbranch_vccnz", "s_cbranch_scc1"}; + for (std::uint32_t lane = 1; lane < Lanes; ++lane) { + if (words[lane] == Sentinel && lane == Lanes / 2u) continue; + for (std::uint32_t bit = 0; bit < branches.size(); ++bit) { + Require((words[lane] & (1u << bit)) == 0u, std::string("paired wave zero branches: lane ") + std::to_string(lane) + " fell through " + branches[bit] + " after another invocation of its wave stopped (wrote " + std::to_string(words[lane]) + ")"); + } + Require(words[lane] == 0u, "paired wave zero branches: lane " + std::to_string(lane) + " wrote " + std::to_string(words[lane]) + ", expected 0"); + } + std::puts("paired wave zero branch tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PassBreakReasons.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PassBreakReasons.cpp new file mode 100644 index 0000000000..91a050e469 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PassBreakReasons.cpp @@ -0,0 +1,164 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using namespace AgcDriver::Graphics; +using DepthFastClearHarness::Compile; +using DepthFastClearHarness::Height; +using DepthFastClearHarness::Shaders; +using DepthFastClearHarness::Triangles; +using DepthFastClearHarness::Width; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Samples = 4; + +alignas(4096) std::array ColorA{}; +alignas(4096) std::array ColorB{}; +alignas(4096) std::array DepthA{}; +alignas(4096) std::array DepthB{}; +alignas(4096) std::array DepthC{}; + +struct Counts { + DrawPassCounts passes; + std::array breaks; +}; + +Counts Snapshot() { + return {DrawPassTotals()[0], DrawPassBreaks()}; +} + +void Expect(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("pass break reasons: " + what); +} + +std::string Describe(const Counts& before, const Counts& after) { + std::string text = std::to_string(after.passes.passesBegun - before.passes.passesBegun) + " begun, " + std::to_string(after.passes.passesContinued - before.passes.passesContinued) + " continued;"; + for (std::size_t i = 0; i < PassBreakCount; ++i) { + if (after.breaks[i] != before.breaks[i]) text += " " + std::string(PassBreakNames[i]) + " " + std::to_string(after.breaks[i] - before.breaks[i]); + } + return text; +} + +void ExpectBreak(const Counts& before, const Counts& after, PassBreak reason, const std::string& what) { + Expect(after.passes.passesBegun - before.passes.passesBegun == 1 && after.passes.passesContinued == before.passes.passesContinued, what + " did not begin exactly one pass: " + Describe(before, after)); + for (std::size_t i = 0; i < PassBreakCount; ++i) { + const std::uint64_t expected = i == static_cast(reason) ? 1u : 0u; + Expect(after.breaks[i] - before.breaks[i] == expected, what + " was not counted once as '" + PassBreakNames[static_cast(reason)] + "': " + Describe(before, after)); + } +} + +void Draw(const Context& context, AgcDriver::VulkanDevice& device, const Shaders& compiled, std::span color, std::uint64_t depth) { + const std::array shaders{{ + {ShaderStage::Vertex, &compiled.vertex, 0}, + {ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())} + }}; + State state{}; + state.stages = {ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(color.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, color.size(), 0xe4u}; + state.color.surfaceAddress = state.color.address; + state.color.samples = Samples; + state.colors = {state.color}; + state.hasColorTarget = true; + state.depth = DepthTarget{depth, 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false, Samples}; + state.depthTest = true; + state.depthWrite = true; + state.depthCompare = VK_COMPARE_OP_LESS; + state.samples = Samples; + state.renderExtent = {Width, Height}; + 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 = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangles().size()), 0, 1, 0, false}; + AgcDriver::Graphics::Draw(context, state, draw, shaders); +} + +} + +int main() { + try { +#ifdef _WIN32 + _putenv_s("APS5_PROFILE_DRAW", "1"); +#else + setenv("APS5_PROFILE_DRAW", "1", 1); +#endif + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + if ((context.limits.framebufferColorSampleCounts & Samples) == 0 || (context.limits.framebufferDepthSampleCounts & Samples) == 0) { + std::puts("skipped, the device cannot render four-sample color and depth"); + return VulkanTestSkipped; + } + const auto shaders = Compile(*device); + device->WaitIdle(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Recorder recorder(context); + recorder.Activate(); + const auto depthA = reinterpret_cast(DepthA.data()); + const auto depthB = reinterpret_cast(DepthB.data()); + const auto depthC = reinterpret_cast(DepthC.data()); + auto before = Snapshot(); + Draw(context, *device, shaders, ColorA, depthA); + auto after = Snapshot(); + ExpectBreak(before, after, PassBreak::NewBatch, "the first draw of a batch"); + Draw(context, *device, shaders, ColorA, depthB); + Draw(context, *device, shaders, ColorB, depthC); + recorder.Submit(); + recorder.Sync(); + before = Snapshot(); + Draw(context, *device, shaders, ColorA, depthA); + after = Snapshot(); + ExpectBreak(before, after, PassBreak::NewBatch, "the first draw after a submit"); + before = after; + Draw(context, *device, shaders, ColorA, depthA); + after = Snapshot(); + Expect(after.passes.passesContinued - before.passes.passesContinued == 1 && after.breaks == before.breaks, "a draw with the open pass's key and no hazard did not continue it: " + Describe(before, after)); + recorder.Commands(); + before = Snapshot(); + Draw(context, *device, shaders, ColorA, depthA); + after = Snapshot(); + ExpectBreak(before, after, PassBreak::Command, "a draw after a non-draw command"); + before = after; + Draw(context, *device, shaders, ColorA, depthB); + after = Snapshot(); + ExpectBreak(before, after, PassBreak::Image, "a draw switching to a depth target the open pass does not touch while sharing its color target"); + before = after; + Draw(context, *device, shaders, ColorB, depthC); + after = Snapshot(); + ExpectBreak(before, after, PassBreak::Key, "a draw into targets the open pass does not touch"); + recorder.Submit(); + recorder.Sync(); + std::puts("pass break reasons tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp index 6eb4c60d13..60254b35ef 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp @@ -82,10 +82,11 @@ std::array BufferDescriptor(const void* data, std::uint32_t st return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; } -ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulation emulation, std::span pixelCode, std::uint32_t inputs, VkCullModeFlags cullMode = VK_CULL_MODE_NONE, bool flat = false, VkProvokingVertexModeEXT provoking = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, bool strip = false) { +ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulation emulation, std::span pixelCode, std::uint32_t inputs, VkCullModeFlags cullMode = VK_CULL_MODE_NONE, bool flat = false, VkProvokingVertexModeEXT provoking = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, bool strip = false, bool builtins = false) { Pixels.fill(Unwritten); auto target = device.Target(); target.fragmentShaderBarycentricEnabled = false; + target.fragmentBarycentricBuiltins = builtins; static const auto trianglePositions = ClipPositions(Emulation::Geometry); static const auto rectanglePositions = ClipPositions(Emulation::RectList); static const auto stripPositions = StripPositions(); @@ -429,6 +430,17 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; + if (device->Target().fragmentBarycentricBuiltins) { + const auto perspective = PixelInputBit(PixelInput::PerspectiveCenter); + const auto linear = PixelInputBit(PixelInput::LinearCenter); + auto builtin = Draw(*device, Emulation::Tessellation, BarycentricPixelCode, perspective, VK_CULL_MODE_NONE, false, VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, false, true); + Require(!builtin.barycentricEmulation.active, "reading PERSP_CENTER I/J with barycentric built-ins needed generated stages"); + const auto builtinSmooth = CheckBarycentrics(Emulation::Tessellation, "PERSP_CENTER I/J from BaryCoordKHR", true); + builtin = Draw(*device, Emulation::Tessellation, BarycentricPixelCode, linear, VK_CULL_MODE_NONE, false, VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, false, true); + Require(!builtin.barycentricEmulation.active, "reading LINEAR_CENTER I/J with barycentric built-ins needed generated stages"); + const auto builtinLinear = CheckBarycentrics(Emulation::Tessellation, "LINEAR_CENTER I/J from BaryCoordNoPerspKHR", false); + std::printf("barycentric built-in tests passed: %u perspective and %u linear I/J pixels\n", builtinSmooth, builtinLinear); + } const bool geometry = device->GeometryLimits().has_value(); const bool tessellation = device->Target().tessellation.has_value(); if (!geometry && !tessellation) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp index f9d09c28f4..edd0f8e67b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelExecInterpolation.cpp @@ -28,6 +28,10 @@ alignas(256) constexpr std::array ExecCopyPixelCode{ 0xbefe0a7e, 0xc8080000, 0xbe80047e, 0xbefe0400, 0xc8090001, 0xf800180f, 0x02020202, 0xbf810000, }; +alignas(256) constexpr std::array RestoredEntryExecPixelCode{ + 0xbe80047e, 0xbefe0a7e, 0xc8080000, 0xc8090001, 0xbefe0400, 0xf800180f, 0x02020202, 0xbf810000, +}; + alignas(256) constexpr std::array RepeatedWqmPixelCode{ 0xbefe0a7e, 0xc8080000, 0xbefe0a7e, 0xc8090001, 0xf800180f, 0x02020202, 0xbf810000, }; @@ -131,7 +135,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { state.blend.colorWriteMask = 15; state.blends = {state.blend}; state.blendConstants = {}; - for (const auto code : {std::span(ExecCopyPixelCode), std::span(RepeatedWqmPixelCode)}) { + for (const auto code : {std::span(ExecCopyPixelCode), std::span(RepeatedWqmPixelCode), std::span(RestoredEntryExecPixelCode)}) { const auto result = compile(code); Require(!result.barycentricEmulation.active, "changing EXEC between v_interp_p1/p2 needed a generated stage"); Pixels.fill(-7.0f); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp index 988207259f..1ca0f10c3e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PooledBufferRange.cpp @@ -483,7 +483,7 @@ std::uint32_t ReadPastEnd(const Context& context, AgcDriver::Graphics::Buffer& s void RobustReadPastEnd(const Context& context) { constexpr VkBufferUsageFlags usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - const auto classBytes = BufferPool::Capacity(RequestedBytes, HostMemory); + const auto classBytes = BufferPool::Capacity(RequestedBytes); Require(classBytes > (PastEnd + 1) * sizeof(std::uint32_t), "the requested buffer's size class does not reach the word read past its end"); { AgcDriver::Graphics::Buffer earlier(context, classBytes, usage); @@ -513,10 +513,10 @@ void ExpectCreated(std::initializer_list sizes, const std::string& void ExactSizes(const Context& context) { constexpr VkBufferUsageFlags usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - const auto hostClass = BufferPool::Capacity(100, HostMemory); - const auto deviceClass = BufferPool::Capacity(3000, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - Require(BufferPool::Capacity(80, HostMemory) == hostClass && hostClass != 80, "80 and 100 bytes are not one host size class"); - Require(BufferPool::Capacity(2500, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == deviceClass && deviceClass != 2500, "2500 and 3000 bytes are not one device size class"); + const auto hostClass = BufferPool::Capacity(100); + const auto deviceClass = BufferPool::Capacity(3000); + Require(BufferPool::Capacity(80) == hostClass && hostClass != 80, "80 and 100 bytes are not one host size class"); + Require(BufferPool::Capacity(2500) == deviceClass && deviceClass != 2500, "2500 and 3000 bytes are not one device size class"); CreatedSizes().clear(); { AgcDriver::Graphics::Buffer fresh(context, 100, usage); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SameQueueLabelWait.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SameQueueLabelWait.cpp index 607faa3dd3..239b0d7cf6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SameQueueLabelWait.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SameQueueLabelWait.cpp @@ -39,6 +39,7 @@ struct Slot { alignas(256) std::array spinDescriptor{}; alignas(256) volatile std::uint32_t label = 0; alignas(256) volatile std::uint32_t done = 0; + alignas(256) volatile std::uint32_t gate = 0; }; class GuestBlock { @@ -131,6 +132,35 @@ std::uint64_t Run(Slot& slot, std::uint32_t value, bool wait) { return AgcDriver::GuestMemory::CollectEpochBumps() - before; } +std::uint64_t RunLanded(Slot& slot, std::uint32_t value, bool wait) { + slot.done = 0; + slot.gate = 0; + std::vector words; + for (std::uint32_t index = 0; index < Waits; ++index) { + ReleaseLabel(words, &slot.label, value + index); + WaitEqual(words, &slot.gate, index + 1); + if (wait) WaitEqual(words, &slot.label, value + index); + } + WriteData(words, &slot.done, 1); + const auto before = AgcDriver::GuestMemory::CollectEpochBumps(); + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x20, &packet) == 0, "gated compute submit failed"); + const auto start = std::chrono::steady_clock::now(); + for (std::uint32_t index = 0; index < Waits; ++index) { + while (slot.label != value + index) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(10), "label " + std::to_string(index) + " of the gated submission did not reach memory"); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + slot.gate = index + 1; + } + while (slot.done == 0) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(10), "the gated submission did not finish"); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + AgcDriverWaitIdle_nid_postfix(); + return AgcDriver::GuestMemory::CollectEpochBumps() - before; +} + } int main() { @@ -149,6 +179,14 @@ int main() { extra = std::min(extra, with > without ? with - without : 0); } Require(extra < Waits / 2, "WAIT_REG_MEMs satisfied by labels their own queue released earlier in the submission started new collect epochs: " + std::to_string(extra) + " extra bumps for " + std::to_string(Waits) + " waits"); + std::uint64_t landedExtra = ~std::uint64_t{0}; + for (std::uint32_t attempt = 0; attempt < 3; ++attempt) { + const auto without = RunLanded(slot, 1000 + attempt * 2 * Waits, false); + const auto with = RunLanded(slot, 1000 + (attempt * 2 + 1) * Waits, true); + std::cout << "landed attempt " << attempt << ": " << with << " bumps with " << Waits << " waits, " << without << " without\n"; + landedExtra = std::min(landedExtra, with > without ? with - without : 0); + } + Require(landedExtra < Waits / 2, "WAIT_REG_MEMs that found the labels their own queue released earlier in the submission already in memory started new collect epochs: " + std::to_string(landedExtra) + " extra bumps for " + std::to_string(Waits) + " waits"); std::cout << "same-queue label wait test passed\n"; AgcDriverShutdown_nid_postfix(); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp index 0daf8e08e4..0cc6a3b6b3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp @@ -15,6 +15,7 @@ #include "Recompiler.hpp" #include "Optimization/ResourceProgram.hpp" #include "VulkanTestDevice.hpp" +#include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -70,6 +71,8 @@ constexpr std::uint32_t Uint8x4Info = 0x8428u; constexpr std::uint32_t Format8888Unorm = 56; constexpr std::uint32_t Format8888Uint = 60; constexpr std::uint32_t Format8888Sint = 61; +constexpr std::uint32_t Float10_11_11Info = 0x0718u; +constexpr std::uint32_t Format11_11_10Float = 36; constexpr std::uint32_t Type2DMsaa = 14; constexpr std::uint32_t TileRenderTarget = 0x1b; constexpr std::uint32_t TileZ64KBX = 0x18; @@ -233,6 +236,20 @@ AgcDriver::Graphics::ColorTarget DecodeSwappedTarget(const Block& block) { return color; } +AgcDriver::Graphics::ColorTarget DecodePackedFloatTarget(const Block& block) { + AgcDriver::Registers cx; + cx[0x318] = static_cast(block.Address() >> 8u); + cx[0x31b] = 0; + cx[0x31c] = Float10_11_11Info; + cx[0x31d] = MultisampledAttrib; + cx[0x3b0] = ((Side - 1u) << 14u) | (Side - 1u); + cx[0x3b8] = TiledAttrib3; + cx[0x390] = static_cast(block.Address() >> 40u); + const auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); + Require(color.format == VK_FORMAT_B10G11R11_UFLOAT_PACK32 && color.samples == Samples && color.extent.width == Side && color.extent.height == Side, "the 10_11_11 float target did not decode as a 16x16 B10G11R11 surface of 4 samples"); + return color; +} + AgcDriver::Graphics::ColorTarget DecodeUintTarget(const Block& block) { AgcDriver::Registers cx; cx[0x318] = static_cast(block.Address() >> 8u); @@ -267,6 +284,20 @@ void CheckSwappedSamples(const std::vector>& loaded) { Require(drawn != 0, "no sample of the BGRA8 target holds the drawn color"); } +void CheckPackedFloatSamples(const std::vector>& loaded) { + const std::array expected{Drawn[0], Drawn[1], Drawn[2], 1.0f}; + const std::array background{0.0f, 0.0f, 0.0f, 1.0f}; + std::size_t drawn = 0; + for (const auto& texel : loaded) { + if (texel == background) continue; + char text[256]; + std::snprintf(text, sizeof(text), "a sample of the 10_11_11 float target read through a format 36 descriptor reads (%g, %g, %g, %g) instead of the drawn (%g, %g, %g, 1)", texel[0], texel[1], texel[2], texel[3], expected[0], expected[1], expected[2]); + Require(texel == expected, text); + ++drawn; + } + Require(drawn != 0, "no sample of the 10_11_11 float target holds the drawn color"); +} + void CheckUintSamples(const std::vector>& loaded) { std::size_t drawn = 0; for (const auto& texel : loaded) { @@ -434,9 +465,20 @@ void RequireStoreRefused(AgcDriver::VulkanDevice& device, const std::array words, spv::Capability capability) { + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + Require(count != 0u && count <= words.size() - cursor, "a SPIR-V instruction is truncated"); + if ((words[cursor] & 0xffffu) == spv::OpCapability && words[cursor + 1u] == static_cast(capability)) return true; + cursor += count; + } + return false; +} + std::vector> RunLoad(AgcDriver::VulkanDevice& device, const std::array& texture, bool arrayed, std::uint32_t fragments) { std::vector userData; const auto program = CompileLoad(device, texture, userData, arrayed); + Require(!DeclaresCapability(program.spirv, spv::CapabilityImageMSArray), "the multisampled image_load declared ImageMSArray, which only multisampled arrayed storage images need"); std::vector> loaded(Side * Side * fragments); const auto codeAddress = reinterpret_cast((arrayed ? ArrayLoadCode : LoadCode).data()); for (std::uint32_t first = 0; first < loaded.size(); first += Threads) { @@ -1040,6 +1082,12 @@ int main(int argc, char** argv) { CheckSwappedSamples(swappedSamples); CheckFirstSampleReads(*device, TargetDescriptor(swapped, SampleShift, Format8888Unorm), swappedSamples, true, "from a 4x BGRA8 target read through an RGBA8 descriptor"); std::puts("a 4x BGRA8 color target loaded through an RGBA8 texture descriptor reads its drawn color with red and blue swapped"); + Block packedFloat(4u * BlockBytes); + Draw(*device, TargetState(DecodePackedFloatTarget(packedFloat))); + const auto packedSamples = LoadSamples(*device, packedFloat, Format11_11_10Float); + CheckPackedFloatSamples(packedSamples); + CheckFirstSampleReads(*device, TargetDescriptor(packedFloat, SampleShift, Format11_11_10Float), packedSamples, true, "from a 4x 10_11_11 float target read through a format 36 descriptor"); + std::puts("a 4x 10_11_11 float color target loaded through a format 36 (11_11_10 float) texture descriptor reads its drawn color"); Block integer(4u * BlockBytes); Block signedFill(BlockBytes); if ((AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).limits.sampledImageIntegerSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp index 6cfb074620..2edc80d0ac 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp @@ -1051,6 +1051,7 @@ void storageMemoryTests(Context context) { auto held = Sampled(context, block.Surface(0)); const auto heap = LargestDeviceLocalHeap(context.memory); Require(heap.has_value(), "the device has no device-local heap"); + context.memory.memoryHeaps[*heap].size = std::max(context.memory.memoryHeaps[*heap].size, ReportedBudget); const auto half = std::min(context.memory.memoryHeaps[*heap].size / 2u, ReportedBudget / 2u); Require(ReadStorageBudget(context) == half, "52 GiB free of a 64 GiB budget does not leave the storage image cache half of the heap or of the budget"); context.memoryProperties2 = &ReportPressure; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarBufferRuntimeWord3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarBufferRuntimeWord3.cpp new file mode 100644 index 0000000000..86ca517d00 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarBufferRuntimeWord3.cpp @@ -0,0 +1,143 @@ +#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" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 4; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Records = 20; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0xe0300000, 0x80000200, 0xbf8c3f70, 0x7e1e0502, 0xbe8c0308, 0xbe8d0309, 0xbe8e030a, 0xf4200406, + 0xfa000004, 0xf4200446, 0xfa000010, 0xf4200486, 0xfa000014, 0xbf8cc07f, 0x7e060210, 0x7e080211, + 0x7e0a0212, 0x340c0084, 0xe0701000, 0x80010306, 0xe0701004, 0x80010406, 0xe0701008, 0x80010506, + 0xbf810000, +}; + +constexpr std::array Word3s{0x31016facu, 0x01016facu, 0x31000facu, 0x31816facu, 0x71016facu, 0xb1016facu, 0xf1016facu, 0x00000000u, 0xffffffffu}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "scalar buffer runtime word 3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(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::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t word3) { + Input.fill(word3); + Output.fill(0xdeadbeefu); + std::vector userData(12, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto data = BufferDescriptor(guest.Data(), Records); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(data.begin(), data.begin() + 3, userData.begin() + 8); + const std::span code(Code); + const std::span guestBytes(guest.Data(), 256); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}, {reinterpret_cast(guest.Data()), std::as_bytes(guestBytes)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(std::uint32_t word3) { + constexpr std::array expected{0xa0000001u, 0xa0000004u, 0u}; + constexpr std::array names{"offset 0x4", "offset 0x10", "offset 0x14 past the records"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t i = 0; i < expected.size(); ++i) { + const auto actual = Output[tid * Results + i]; + Require(actual == expected[i], "scalar buffer runtime word 3 " + Hex(word3) + ": lane " + std::to_string(tid) + " " + names[i] + " is " + Hex(actual) + ", expected " + Hex(expected[i])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t i = 0; i < 64; ++i) { + const std::uint32_t value = 0xa0000000u | i; + std::memcpy(guest.Data() + i * 4u, &value, sizeof(value)); + } + for (const auto word3 : Word3s) { + Run(*device, guest, word3); + Check(word3); + } + std::puts("scalar buffer runtime word 3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp index 14d571cf48..c2b8beaf47 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp @@ -83,7 +83,7 @@ std::array BufferDescriptor(const void* data, std::uint32_t st ShaderRecompiler::ShaderPixelStageInfo PixelInfo(std::uint32_t shaderControl) { const AgcDriver::Registers context{ - {0x1b3, 0x700u}, {0x1b4, 0x700u}, {0x1b6, 0u}, {0x1c5, 9u}, {0x203, shaderControl}, + {0x003, 0u}, {0x0b4, 0u}, {0x0b5, 0x3f800000u}, {0x1b3, 0x700u}, {0x1b4, 0x700u}, {0x1b6, 0u}, {0x1c5, 9u}, {0x203, shaderControl}, }; return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp index a861172480..726cb775a7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,16 @@ alignas(256) constexpr std::array HullCode{ 0xbf810000, }; +alignas(256) constexpr std::array HullMaskedControlPoint{ + 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, + 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, + 0x02000015, 0x362c02ff, 0x00001f00, 0x2c2c2c84, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, + 0xe0781000, 0x02020216, 0x7da82881, 0xbe94047e, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, + 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, + 0x34142484, 0xbefe0414, 0xe0781000, 0x0403180a, 0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, + 0xe0741000, 0x0202070b, 0xbf810000, +}; + alignas(256) constexpr std::array HullCompareBranch{ 0xd4040016, 0x00020080, 0xbeea0416, 0xbf870001, 0xbf800000, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff, 0x000000ff, @@ -565,7 +576,7 @@ struct Stages { ShaderRecompiler::RecompileResult fragment; }; -ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& target, ShaderStage stage, std::span code, std::uint32_t& pushCursor, const PatchDraw& patches = Triangles) { +ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& target, ShaderStage stage, std::span code, std::uint32_t& pushCursor, const PatchDraw& patches = Triangles, std::uint32_t depthRemapMode = 0u) { static const std::array domainUserData{}; const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; const bool hull = stage == ShaderStage::TessellationControl; @@ -582,10 +593,12 @@ ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& t pixel = info; } else { vertex = ShaderRecompiler::ShaderVertexStageInfo{}; + vertex->depthRemapMode = depthRemapMode; graphics = ShaderRecompiler::GraphicsCompileContext{hull ? 0u : 8u, {}, std::nullopt, patches.configuration, {reinterpret_cast(patches.indices.data()), static_cast(patches.indices.size()), 2, patches.instances}}; } auto layout = stage == ShaderStage::Fragment ? PixelPushLayout(pushCursor, target) : ShaderRecompiler::BindingLayout{0, 0, pushCursor, AgcDriver::Graphics::PipelinePushSlotBytes - pushCursor}; if (layout.pushConstantOffsetBytes < AgcDriver::Graphics::PipelinePushSlotBytes) layout.pushConstantSizeBytes = ShaderRecompiler::TessellationLevelPushOffsetBytes - layout.pushConstantOffsetBytes; + if (depthRemapMode != 0u && layout.pushConstantOffsetBytes < AgcDriver::Graphics::PipelinePushSlotBytes) layout.pushConstantSizeBytes = ShaderRecompiler::DepthRemapPushOffsetBytes - layout.pushConstantOffsetBytes; const ShaderRecompiler::RecompileRequest request{ {stage, reinterpret_cast(code.data()), code, 0, {}}, {64, hull ? 0u : 8u, userData, std::nullopt, pixel, vertex, memory}, @@ -634,7 +647,17 @@ std::uint32_t Row(float y) { return static_cast((1.0f - y) * 0.5f * Height); } -void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags cull, VkFrontFace front, const PatchDraw& patches = Triangles) { +struct DepthDraw { + std::uint64_t address; + bool write; + bool negativeOneToOne = false; + float scale = 1.0f; + float offset = 0.0f; + float clampMin = 0.0f; + float clampMax = 1.0f; +}; + +void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags cull, VkFrontFace front, const PatchDraw& patches = Triangles, const DepthDraw* depth = nullptr) { for (std::size_t i = 0; i < Pixels.size(); i += 4) { for (std::size_t c = 0; c < 4; ++c) Pixels[i + c] = std::byte{Background[c]}; } @@ -665,10 +688,95 @@ void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags state.blend.colorWriteMask = 15; state.blends = {state.blend}; state.blendConstants = {}; + if (depth != nullptr) { + state.depth = AgcDriver::Graphics::DepthTarget{depth->address, 0u, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0u}; + state.depthTest = true; + state.depthWrite = depth->write; + state.depthCompare = depth->write ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_LESS; + state.depthScale = depth->scale; + state.depthOffset = depth->offset; + state.depthClampMin = depth->clampMin; + state.depthClampMax = depth->clampMax; + state.negativeOneToOne = depth->negativeOneToOne; + state.viewport.minDepth = depth->negativeOneToOne ? depth->offset - depth->scale : depth->offset; + state.viewport.maxDepth = depth->offset + depth->scale; + auto context = device.graphicsContext(); + context.depthRangeUnrestricted = false; + AgcDriver::Graphics::ApplyDepthRemap(context, state); + } device.Draw(state, {reinterpret_cast(patches.indices.data()), static_cast(patches.indices.size()), 2, patches.instances, 0, true, 0, patches.firstInstance}, shaders); device.WaitIdle(); } +void RequireDepthRangeRemap(AgcDriver::VulkanDevice& device) { + auto target = device.Target(); + Require(device.graphicsContext().depthClamp, "tessellation depth remap needs depth clamping"); + target.depthRangeRemap = true; + const auto compile = [&](float z, std::uint32_t mode) { + std::vector domain(DomainCode.begin(), DomainCode.end()); + const auto literal = std::ranges::find(domain, 0x7e260280u); + Require(literal != domain.end(), "the domain fixture has no constant depth"); + *literal = 0x7e2602ffu; + domain.insert(literal + 1, std::bit_cast(z)); + std::uint32_t cursor = 0u; + return Stages{ + Compile(target, ShaderStage::Local, LocalCode, cursor), + Compile(target, ShaderStage::TessellationControl, HullCode, cursor), + Compile(target, ShaderStage::TessellationEvaluation, domain, cursor, Triangles, mode), + Compile(target, ShaderStage::Fragment, PixelCode, cursor), + }; + }; + const auto covered = [] { + std::uint32_t count = 0u; + for (std::size_t offset = 0; offset < Pixels.size(); offset += 4u) { + if (Pixels[offset] == std::byte{255}) ++count; + } + return count; + }; + Draw(device, compile(0.5f, 0u), VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); + const auto expected = covered(); + Require(expected != 0u, "the depth fixture drew no tessellated patches"); + struct Case { + bool symmetric; + float scale; + float offset; + float clampMin; + float clampMax; + float z; + bool drawn; + float probe; + bool passes; + }; + constexpr std::array cases{{ + {true, 1.0f, 0.0f, 0.0f, 1.0f, 0.75f, true, 0.5f, true}, + {true, 1.0f, 0.0f, 0.0f, 1.0f, -0.5f, true, 0.25f, false}, + {true, 1.0f, 0.0f, 0.0f, 1.0f, -1.25f, false, 0.5f, true}, + {true, 1.0f, 0.0f, 0.0f, 1.0f, 1.25f, false, 0.5f, true}, + {false, 2.0f, -0.5f, 0.0f, 1.0f, 0.25f, true, 0.125f, false}, + {false, 2.0f, -0.5f, 0.0f, 1.0f, 0.75f, true, 0.875f, true}, + {false, 2.0f, -0.5f, 0.0f, 1.0f, -0.125f, false, 0.5f, true}, + {false, 2.0f, -0.5f, 0.0f, 1.0f, 1.125f, false, 0.5f, true}, + {false, 2.0f, -0.5f, 0.25f, 0.75f, 0.4f, true, 0.35f, false}, + {false, 2.0f, -0.5f, 0.25f, 0.75f, 0.6f, true, 0.65f, true}, + {false, 2.0f, -0.5f, 0.25f, 0.75f, 0.2f, true, 0.2f, true}, + {false, 2.0f, -0.5f, 0.25f, 0.75f, 0.8f, true, 0.8f, false}, + {false, -2.0f, 1.5f, 0.0f, 1.0f, 0.4f, true, 0.65f, true}, + {false, -2.0f, 1.5f, 0.0f, 1.0f, 0.6f, true, 0.35f, false}, + }}; + alignas(4096) static std::array, cases.size()> depths{}; + for (std::size_t index = 0; index < cases.size(); ++index) { + const auto& test = cases[index]; + const auto address = static_cast(reinterpret_cast(depths[index].data())); + const DepthDraw write{address, true, test.symmetric, test.scale, test.offset, test.clampMin, test.clampMax}; + Draw(device, compile(test.z, test.symmetric ? 2u : 1u), VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, Triangles, &write); + Require(covered() == (test.drawn ? expected : 0u), "tessellation remap clipping differs in case " + std::to_string(index)); + const DepthDraw probe{address, false}; + Draw(device, compile(test.probe, 0u), VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, Triangles, &probe); + Require(covered() == (test.passes ? expected : 0u), "tessellation remap stored depth differs in case " + std::to_string(index)); + } + std::puts("Tessellation depth remap: 14 clipping and stored-depth cases passed"); +} + std::uint32_t CoveredPixels(std::uint32_t patch, bool interior) { std::uint32_t count = 0; for (std::uint32_t y = Row(0.5f); y <= Row(-0.5f) && y < Height; ++y) { @@ -1095,7 +1203,7 @@ void RequireRegisteredMergedStages() { } -int main() { +int main(int argc, char** argv) { try { auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; @@ -1104,6 +1212,10 @@ int main() { std::puts("tessellation: skipped, the device has no tessellation shaders"); return VulkanTestSkipped; } + if (argc == 2 && std::string_view(argv[1]) == "--depth-remap") { + RequireDepthRangeRemap(*device); + return 0; + } RequireSingleControlPointHulls(target); ExpectRefusal(target, ShaderStage::TessellationControl, HullUnguardedFactors, "not written by control point 0 alone"); ExpectRefusal(target, ShaderStage::TessellationControl, HullSquaredAddress, "not a whole number of control points"); @@ -1129,6 +1241,7 @@ int main() { ExpectRefusal(target, ShaderStage::TessellationControl, HullDescendingRecords, "outside its control points and patch constants", {{3u, 3u, 1u, 2u, 2u, 129u}, Indices}); RequirePatches(*device, target, HullCode, "hull program"); + RequirePatches(*device, target, HullMaskedControlPoint, "hull program masking its control point out of v1 for its off-chip record"); RequirePatches(*device, target, HullRestoredExecSaved, "hull program saving its restored exec"); RequirePatches(*device, target, HullNextInput, "hull program reading the next of 4 input control points of 24 bytes", NextInputTriangles, LocalCodeStride24); RequirePatches(*device, target, HullDynamicIndex, "hull program reading a control point at a computed index"); diff --git a/core/libs/prx/libSceAgcDriver/tools/ComputePipelineReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/ComputePipelineReplay.cpp index 5baabe910d..e9e403659d 100644 --- a/core/libs/prx/libSceAgcDriver/tools/ComputePipelineReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/ComputePipelineReplay.cpp @@ -17,6 +17,7 @@ struct Options { std::filesystem::path output; std::filesystem::path fixtureDirectory; bool fixture = false; + bool inspect = false; }; Options Parse(int argc, char** argv) { @@ -32,12 +33,13 @@ Options Parse(int argc, char** argv) { else if (argument == "--output") value(result.output); else if (argument == "--write-fixture") value(result.fixtureDirectory); else if (argument == "--fixture" && !result.fixture) result.fixture = true; + else if (argument == "--inspect" && !result.inspect) result.inspect = true; else if (argument.starts_with("--") || !result.source.empty()) throw std::runtime_error("unknown or repeated argument: " + std::string(argument)); else result.source = argument; } const auto sources = static_cast(!result.source.empty()) + static_cast(result.fixture) + static_cast(!result.fixtureDirectory.empty()); if (sources != 1) throw std::runtime_error("select a capture file, --fixture, or --write-fixture DIRECTORY"); - if (!result.fixtureDirectory.empty() && (!result.cacheIn.empty() || !result.cacheOut.empty() || !result.output.empty())) throw std::runtime_error("--write-fixture cannot be combined with replay options"); + if (!result.fixtureDirectory.empty() && (!result.cacheIn.empty() || !result.cacheOut.empty() || !result.output.empty() || result.inspect)) throw std::runtime_error("--write-fixture cannot be combined with replay options"); for (const auto& path : {result.cacheOut, result.output}) { if (!path.empty() && std::filesystem::exists(path)) throw std::runtime_error("output already exists: " + path.string()); } @@ -102,6 +104,7 @@ std::string Report(const Options& options, const AgcDriver::ComputePipelineCaptu << ",\n \"specialization_constants\":" << capture.specialization.size() << ",\n \"required_subgroup_size\":" << capture.requiredSubgroupSize << ",\n \"pipeline_flags\":" << capture.flags + << ",\n \"replay_pipeline_flags\":" << (capture.flags | (options.inspect ? static_cast(VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR) : 0u)) << ",\n \"stage_flags\":" << capture.stageFlags << ",\n \"captured_device\":"; WriteDevice(output, capture); @@ -113,10 +116,15 @@ std::string Report(const Options& options, const AgcDriver::ComputePipelineCaptu << ",\n \"cache_export_path\":" << Quote(options.cacheOut.string()) << ",\n \"cache_export_bytes\":" << (options.cacheOut.empty() ? 0u : replay.cache.size()) << ",\n \"driver_cache_state\":\"uncontrolled\"" - << ",\n \"dispatch_count\":0,\n \"pipeline_statistics\":false" - << ",\n \"milliseconds\":{\"shader_module\":" << replay.moduleMilliseconds - << ",\"layout\":" << replay.layoutMilliseconds << ",\"pipeline_create\":" << replay.pipelineMilliseconds - << ",\"cache\":" << replay.cacheMilliseconds << ",\"replay_total\":" << replay.totalMilliseconds << "}\n}\n"; + << ",\n \"dispatch_count\":0,\n \"pipeline_statistics\":" << (options.inspect ? "true" : "false") + << ",\n \"mode\":" << Quote(options.inspect ? "inspection" : "benchmark"); + if (options.inspect) { + output << ",\n \"milliseconds\":null\n}\n"; + } else { + output << ",\n \"milliseconds\":{\"shader_module\":" << replay.moduleMilliseconds + << ",\"layout\":" << replay.layoutMilliseconds << ",\"pipeline_create\":" << replay.pipelineMilliseconds + << ",\"cache\":" << replay.cacheMilliseconds << ",\"replay_total\":" << replay.totalMilliseconds << "}\n}\n"; + } return output.str(); } @@ -124,8 +132,9 @@ std::string Report(const Options& options, const AgcDriver::ComputePipelineCaptu int main(int argc, char** argv) { if (argc == 2 && std::string_view(argv[1]) == "--help") { - std::cout << "agc_compute_pipeline_replay CAPTURE|--fixture [--cache-in FILE] [--cache-out FILE] [--output FILE]\n" + std::cout << "agc_compute_pipeline_replay CAPTURE|--fixture [--cache-in FILE] [--cache-out FILE] [--output FILE] [--inspect]\n" "agc_compute_pipeline_replay --write-fixture DIRECTORY\n" + "--inspect requires APS5_PIPELINE_STATS=1, prints executable counters to stderr and omits benchmark timings.\n" "Compiles one compute pipeline without dispatch. Fresh application cache does not clear driver caches.\n"; return 0; } @@ -136,12 +145,19 @@ int main(int argc, char** argv) { return 0; } const auto* statistics = std::getenv("APS5_PIPELINE_STATS"); - if (statistics != nullptr && std::string_view(statistics) != "0" && *statistics != '\0') throw std::runtime_error("unset APS5_PIPELINE_STATS before benchmarking"); + const bool statisticsEnabled = statistics != nullptr && std::string_view(statistics) != "0" && *statistics != '\0'; + if (options.inspect && !statisticsEnabled) throw std::runtime_error("--inspect requires APS5_PIPELINE_STATS=1"); + if (!options.inspect && statisticsEnabled) throw std::runtime_error("unset APS5_PIPELINE_STATS before benchmarking"); const auto capture = options.fixture ? AgcDriver::ComputePipelineFixture() : AgcDriver::ComputePipelineCapture::Read(options.source); - if ((capture.flags & VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR) != 0) throw std::runtime_error("capture requests pipeline statistics; capture again with APS5_PIPELINE_STATS disabled"); + if (!options.inspect && (capture.flags & VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR) != 0) throw std::runtime_error("capture requests pipeline statistics; capture again with APS5_PIPELINE_STATS disabled"); const auto cache = ReadCache(options.cacheIn); AgcDriver::VulkanDevice device(AgcDriver::VulkanDevice::CompileOnly{}); - const auto replay = device.ReplayComputePipeline(capture, cache); + const auto replay = [&] { + if (!options.inspect) return device.ReplayComputePipeline(capture, cache); + auto inspection = capture; + inspection.flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; + return device.ReplayComputePipeline(inspection, cache); + }(); if (!options.cacheOut.empty()) WriteBytes(options.cacheOut, replay.cache); const auto report = Report(options, capture, replay); if (!options.output.empty()) WriteBytes(options.output, std::as_bytes(std::span(report.data(), report.size()))); diff --git a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp index 7bad5361bb..31d7e6b4ae 100644 --- a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp @@ -50,6 +50,7 @@ bool g_maintenance8 = false; bool g_robust2 = false; bool g_waveLdsMemoryBarriers = false; bool g_deviceScopeLdsMemoryBarriers = false; +bool g_ldsLockFences = false; bool g_code = false; bool Replay(const char* path) { @@ -151,6 +152,7 @@ bool Replay(const char* path) { if (g_robust2) request.request.target.robustBufferAccess2 = true; if (g_waveLdsMemoryBarriers) request.request.target.waveLdsMemoryBarriers = true; if (g_deviceScopeLdsMemoryBarriers) request.request.target.deviceScopeLdsMemoryBarriers = true; + if (g_ldsLockFences) request.request.target.ldsLockFences = true; const auto result = ShaderRecompiler::Recompile(request.request); std::printf(" recompiled: %zu SPIR-V words\n", result.spirv.size()); if (g_spirv) { @@ -188,7 +190,7 @@ bool Replay(const char* path) { int main(int argc, char** argv) { if (argc < 2) { - std::fprintf(stderr, "usage: agc_shader_replay [--dis] [--asm] [--cfg] [--mem] [--spv] [--prepare] [--materialize] [--code] [--maintenance8] [--robust2] [--wave-lds-memory-barriers] [--device-scope-lds-memory-barriers] ...\n the driver writes shader_
.req files when APS5_DUMP_SHADERS is set; shader_
.prepare.req holds a request whose preparation failed before any resource capture, so replay it with --prepare\n"); + std::fprintf(stderr, "usage: agc_shader_replay [--dis] [--asm] [--cfg] [--mem] [--spv] [--prepare] [--materialize] [--code] [--maintenance8] [--robust2] [--wave-lds-memory-barriers] [--device-scope-lds-memory-barriers] [--lds-lock-fences] ...\n the driver writes shader_
.req files when APS5_DUMP_SHADERS is set; shader_
.prepare.req holds a request whose preparation failed before any resource capture, so replay it with --prepare\n"); return 2; } int failures = 0; @@ -221,6 +223,10 @@ int main(int argc, char** argv) { g_deviceScopeLdsMemoryBarriers = true; continue; } + if (std::string(argv[i]) == "--lds-lock-fences") { + g_ldsLockFences = true; + continue; + } if (std::string(argv[i]) == "--mem") { g_memory = true; continue; diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 6f4e548a72..24c44d5ebf 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -633,6 +633,7 @@ std::size_t ResamplerHeld(const Instance& instance, std::size_t channels) { const auto& resampler = instance.resampler; if (resampler.frames.empty() || channels == 0) return 0; const std::size_t frames = resampler.frames.size() / channels; + if (resampler.position >= static_cast(frames)) return 0; const auto next = static_cast(resampler.position); return frames > next ? frames - next : 0; } @@ -655,8 +656,8 @@ void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t chann const std::size_t available = resampler.frames.size() / channels; std::vector frame(frameBytes); while (outputs.Room() >= frameBytes) { + if (available <= 2 || resampler.position >= static_cast(available - 2)) break; const auto index = static_cast(resampler.position); - if (index + 2 >= available) break; const double t = resampler.position - static_cast(index); for (std::size_t channel = 0; channel < channels; ++channel) { const double p0 = resampler.frames[(index - 1) * channels + channel]; @@ -671,7 +672,7 @@ void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t chann resampler.ratio = std::max(0.0f, resampler.ratio + resampler.change); } } - const auto index = std::min(static_cast(resampler.position), available); + const auto index = resampler.position >= static_cast(available) ? available : static_cast(resampler.position); if (index > 1) { resampler.frames.erase(resampler.frames.begin(), resampler.frames.begin() + static_cast((index - 1) * channels)); resampler.position -= static_cast(index - 1); diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index 96a00cca78..478f72de02 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAppContent/include/InventoryExports.hpp" #include "prx/libSceAppContent/include/Api.hpp" #include #include @@ -69,6 +70,23 @@ static void ClearDirectory(const std::filesystem::path& directory) { extern "C" { +int APS5_VABI sceAppContentCheckBundleLicenseOnDisc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAppContentDownloadDataGetBlockSize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAppContentGetGameTrialsFlag() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAppContentGetPlayableStatus() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceAppContentAddcontMount(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label, AppContentMountPoint* mount_point) { if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; (void)service_label; diff --git a/core/libs/prx/libSceAppContent/include/InventoryExports.hpp b/core/libs/prx/libSceAppContent/include/InventoryExports.hpp new file mode 100644 index 0000000000..2c7f64dfb8 --- /dev/null +++ b/core/libs/prx/libSceAppContent/include/InventoryExports.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAppContentCheckBundleLicenseOnDisc(); +int APS5_VABI sceAppContentDownloadDataGetBlockSize(); +int APS5_VABI sceAppContentGetGameTrialsFlag(); +int APS5_VABI sceAppContentGetPlayableStatus(); + +} diff --git a/core/libs/prx/libSceAppContent/libSceAppContent.hpp b/core/libs/prx/libSceAppContent/libSceAppContent.hpp index d92b11cf65..0d4428540d 100644 --- a/core/libs/prx/libSceAppContent/libSceAppContent.hpp +++ b/core/libs/prx/libSceAppContent/libSceAppContent.hpp @@ -1,3 +1,5 @@ #pragma once #include "prx/libSceAppContent/include/Api.hpp" + +#include "prx/libSceAppContent/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 7c97d07a55..21f0bf97c4 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(libSceAudioOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2User.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2Mastering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2System.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/InventoryExports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Unimplemented.cpp ) @@ -47,6 +48,21 @@ if(BUILD_TESTING) endif() endforeach() + add_test_executable(audio_out_default_open_tests tests/AudioOutDefaultOpen.cpp src/AudioOut.cpp src/AudioOut2PadMix.cpp src/AudioOutShutdown.cpp) + target_compile_definitions(audio_out_default_open_tests PRIVATE SDL_MAIN_HANDLED) + target_include_directories(audio_out_default_open_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_link_libraries(audio_out_default_open_tests PRIVATE libc libkernel libScePad) + configure_windows_unwind(audio_out_default_open_tests) + foreach(defaultOpenMode IN ITEMS zero_default wasapi_zero_default no_endpoint positive unknown) + set(defaultOpenTest "audio_out_default_open_${defaultOpenMode}") + add_test(NAME ${defaultOpenTest} COMMAND audio_out_default_open_tests ${defaultOpenMode}) + set_tests_properties(${defaultOpenTest} PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(${defaultOpenTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + add_test_executable(audio_out_buffer_size_tests tests/AudioOutBufferSize.cpp src/AudioOut.cpp src/AudioOut2PadMix.cpp src/AudioOutShutdown.cpp) target_compile_definitions(audio_out_buffer_size_tests PRIVATE SDL_MAIN_HANDLED) target_include_directories(audio_out_buffer_size_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) @@ -224,7 +240,8 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_test_executable(audio_out2_timing_tests tests/AudioOut2Timing.cpp) - target_include_directories(audio_out2_timing_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_include_directories(audio_out2_timing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_compile_definitions(audio_out2_timing_tests PRIVATE SDL_MAIN_HANDLED) target_link_libraries(audio_out2_timing_tests PRIVATE libSceAudioOut libc) configure_windows_unwind(audio_out2_timing_tests) add_test(NAME audio_out2_timing COMMAND audio_out2_timing_tests) diff --git a/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp b/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp index f0e8c87cc4..0d72f3578d 100644 --- a/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp +++ b/core/libs/prx/libSceAudioOut/include/AudioOutSdl.hpp @@ -1,6 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEAUDIOOUT_INCLUDE_AUDIOOUTSDL_HPP #define CORE_LIBS_PRX_LIBSCEAUDIOOUT_INCLUDE_AUDIOOUTSDL_HPP +#include + int InitializeSdlAudio_nid_no_patch(); +bool WasapiDefaultOutputAbsent_nid_no_patch(); + +inline bool SdlAudioReportsNoOutputEndpoint(const char* driver, int outputDevices) { + return outputDevices == 0 && driver != nullptr && std::strcmp(driver, "wasapi") == 0 && WasapiDefaultOutputAbsent_nid_no_patch(); +} #endif diff --git a/core/libs/prx/libSceAudioOut/include/InventoryExports.hpp b/core/libs/prx/libSceAudioOut/include/InventoryExports.hpp new file mode 100644 index 0000000000..13bd1029e9 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/include/InventoryExports.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioOut2GetSystemDebugState(); +int APS5_VABI sceAudioOut2LoContextCreateEx(); +int APS5_VABI sceAudioOut2LoContextGetAttributes(); +int APS5_VABI sceAudioOut2LoContextQueryMemoryEx(); +int APS5_VABI sceAudioOut2LoPortCreateEx(); +int APS5_VABI sceAudioOut2LoPortGetAttributes(); +int APS5_VABI sceAudioOut2LoPortSetAttributesEx(); +int APS5_VABI sceAudioOut2PortSetAttributesEx(); +int APS5_VABI sceAudioOut2SetHrtfIdForCronos(); +int APS5_VABI sceAudioOut2SetSpeakerInfo(); +int APS5_VABI sceAudioOut2SetTvCorrectionInfo(); + +} diff --git a/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp b/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp index d092ed95af..da1886b340 100644 --- a/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp +++ b/core/libs/prx/libSceAudioOut/libSceAudioOut.hpp @@ -2,3 +2,4 @@ #include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "prx/libSceAudioOut/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index cc2610c23e..a7e5f42d7f 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -142,7 +142,7 @@ static bool ensureSdlAudio() { } static bool openDevice(Port& port) { - if (!ensureSdlAudio()) { + if (!ensureSdlAudio() || SdlAudioReportsNoOutputEndpoint(SDL_GetCurrentAudioDriver(), SDL_GetNumAudioDevices(0))) { return false; } SDL_AudioSpec desired{}; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp index 905eaa42f5..43ed70daf4 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp @@ -93,14 +93,31 @@ static void Drain(AudioOut2Context& context, Clock::time_point now) { } // Grains queued and not yet played, as the title sees them. The caller holds the context lock. -static std::uint32_t QueueLevel(AudioOut2Context& context, Clock::time_point now) { - Drain(context, now); - if (context.device == 0) return context.queued; +static std::uint32_t DeviceQueueLevel(const AudioOut2Context& context) { const auto unplayed = UnplayedGrains(context); const auto cushion = CushionGrains(context); return unplayed > cushion ? std::min(context.queueDepth, unplayed - cushion) : 0; } +static std::uint32_t QueueLevel(AudioOut2Context& context, Clock::time_point now) { + Drain(context, now); + return context.device == 0 ? context.queued : DeviceQueueLevel(context); +} + +static void PublishQueueLevel(AudioOut2Context& context) { + const auto level = std::min(context.queueDepth, DeviceQueueLevel(context) + static_cast(context.advancedGrains.size())); + context.publishedQueueLevel.store(level, std::memory_order_release); +} + +struct QueueLevelPublisher { + AudioOut2Context& context; + const std::unique_lock& lock; + + ~QueueLevelPublisher() { + if (lock.owns_lock() && context.device != 0) PublishQueueLevel(context); + } +}; + static void DeviceCallback(void* userdata, Uint8* stream, int length); static void OpenDevice(AudioOut2Context& context) { @@ -108,6 +125,10 @@ static void OpenDevice(AudioOut2Context& context) { AUDIOOUT2_TRACE("SDL audio init failed: %s\n", SDL_GetError()); return; } + if (SdlAudioReportsNoOutputEndpoint(SDL_GetCurrentAudioDriver(), SDL_GetNumAudioDevices(0))) { + AUDIOOUT2_TRACE("SDL reports no WASAPI output endpoint\n"); + return; + } SDL_AudioSpec desired{}; desired.freq = static_cast(AUDIO_OUT2_SAMPLE_RATE); desired.format = AUDIO_F32SYS; @@ -221,7 +242,8 @@ static void Render(AudioOut2Context& context, const AudioOut2Grain& grain) { static void DeviceCallback(void* userdata, Uint8* stream, int length) { auto& context = *static_cast(userdata); - std::lock_guard lock(context.lock); + std::unique_lock lock(context.lock); + const QueueLevelPublisher publication{context, lock}; auto* out = reinterpret_cast(stream); const auto samples = static_cast(length) / sizeof(float); const auto cushionSamples = static_cast(CushionGrains(context)) * context.grain * AUDIO_OUT2_OUTPUT_CHANNELS; @@ -262,7 +284,8 @@ int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle ctx) try { const AudioOutCall call; auto* context = FromHandle(ctx); if (!context) return SCE_AUDIO_OUT2_ERROR_INVALID_HANDLE; - std::lock_guard lock(context->lock); + std::unique_lock lock(context->lock); + const QueueLevelPublisher publication{*context, lock}; if (context->device != 0) { if (!context->advancedMode) { while (!context->pendingGrains.empty()) { @@ -295,6 +318,7 @@ int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam* params, voi AudioOut2TraceSeconds(), params->max_ports, params->max_object_ports, params->guarantee_object_ports, params->queue_depth, params->num_grains, params->flags, buffer, buffer_size, static_cast(context), context->grain, 1000.0 * context->grain / AUDIO_OUT2_SAMPLE_RATE, context->queueDepth, AUDIO_OUT2_SAMPLE_RATE); OpenDevice(*context); + context->deviceBacked = context->device != 0; *ctx = reinterpret_cast(context); return 0; } @@ -321,6 +345,12 @@ int APS5_VABI sceAudioOut2ContextDestroy(AudioOut2ContextHandle ctx) try { int APS5_VABI sceAudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint32_t* queue_level, uint32_t* available_queue) { auto* context = FromHandle(ctx); if (!context) return SCE_AUDIO_OUT2_ERROR_INVALID_HANDLE; + if (context->deviceBacked && !AudioOut2TraceEnabled()) { + const auto level = context->publishedQueueLevel.load(std::memory_order_acquire); + if (queue_level) *queue_level = level; + if (available_queue) *available_queue = context->queueDepth - level; + return 0; + } std::lock_guard lock(context->lock); const auto level = std::min(context->queueDepth, QueueLevel(*context, Clock::now()) + static_cast(context->advancedGrains.size())); if (queue_level) *queue_level = level; @@ -336,6 +366,7 @@ int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t block auto* context = FromHandle(ctx); if (!context) return SCE_AUDIO_OUT2_ERROR_INVALID_HANDLE; std::unique_lock lock(context->lock); + const QueueLevelPublisher publication{*context, lock}; auto now = Clock::now(); const auto waitStart = now; while (QueueLevel(*context, now) >= context->queueDepth) { @@ -374,6 +405,7 @@ int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t block while (context->pendingGrains.size() >= 2) { const auto readAt = context->pendingSince.front() + READ_DELAY; if (Clock::now() < readAt) { + PublishQueueLevel(*context); lock.unlock(); while (Clock::now() < readAt) std::this_thread::yield(); lock.lock(); diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp index 792d1a525c..2df977b0ee 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBSCEAUDIOOUT_SRC_AUDIOOUT2INTERNAL_HPP #include +#include #include #include #include @@ -58,6 +59,12 @@ struct AudioOut2CapturedPort { using AudioOut2Grain = std::vector; +struct AudioOut2QueuedData { + const void* data = nullptr; + std::vector pcm; + bool reused = false; +}; + struct AudioOut2Port { bool used = false; std::uint64_t generation = 0; @@ -70,9 +77,11 @@ struct AudioOut2Port { bool int16 = false; const AudioOut2StereoFold* fold = nullptr; const void* data = nullptr; - std::deque queuedData; + std::deque queuedData; const void* lastBuffer = nullptr; const void* repeatedData = nullptr; + std::vector repeatedPcm; + bool repeatedReused = false; float volume[AUDIO_OUT2_PORT_CHANNELS_MAX] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; std::uint64_t dataSets = 0; std::uint64_t attributeTraces = 0; @@ -117,6 +126,8 @@ struct AudioOut2Context { // Largest sample magnitude of the grains mixed since the last summary: the mixed signal's level. float summaryPeak = 0.0f; std::chrono::steady_clock::time_point summaryStart; + bool deviceBacked = false; + std::atomic publishedQueueLevel{0}; }; // Mixes every port of the context that carries PCM data into out (stereo float, frames frames), summing diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index 88e948e434..514aa8f661 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -5,6 +5,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "AudioOut2Internal.hpp" namespace { @@ -12,6 +13,7 @@ constexpr int SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM = static_cast(0x80268204); constexpr int SCE_AUDIO_OUT2_ERROR_MASTERING_NOT_INITIALIZED = static_cast(0x80268203); constexpr int SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_FLAG = static_cast(0x80268205); +constexpr int SCE_AUDIO_OUT2_ERROR_NOT_INITIALIZED = static_cast(0x80268006); constexpr int SCE_AUDIO_OUT2_ERROR_INVALID_POINTER = static_cast(0x8026800C); constexpr std::uint32_t AUDIO_OUT2_MASTERING_INIT_EX_MODE = 0x80000000u; constexpr std::uint32_t AUDIO_OUT2_MASTERING_SET_PARAM_FLAGS_MAX = 1; @@ -19,6 +21,7 @@ constexpr std::uint32_t AUDIO_OUT2_MASTERING_SET_PARAM_FLAGS_MAX = 1; std::atomic g_masteringInitialized{false}; int InitializeMastering(uint32_t flags, uint32_t accepted) { + if (!AudioOut2IsInitialized_nid_no_patch()) return SCE_AUDIO_OUT2_ERROR_NOT_INITIALIZED; if (g_masteringInitialized.load()) return 0; if ((flags & ~accepted) != 0) return SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM; g_masteringInitialized.store(true); @@ -88,6 +91,7 @@ int APS5_VABI sceAudioOut2MasteringInitEx(uint32_t flags) { } int APS5_VABI sceAudioOut2MasteringTerm(void) { + if (!AudioOut2IsInitialized_nid_no_patch()) return SCE_AUDIO_OUT2_ERROR_NOT_INITIALIZED; if (!g_masteringInitialized.exchange(false)) return SCE_AUDIO_OUT2_ERROR_MASTERING_NOT_INITIALIZED; return 0; } @@ -95,6 +99,7 @@ int APS5_VABI sceAudioOut2MasteringTerm(void) { int APS5_VABI sceAudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* state, uint32_t output, AudioOut2UserHandle user) { (void)output; (void)user; + if (!AudioOut2IsInitialized_nid_no_patch()) return SCE_AUDIO_OUT2_ERROR_NOT_INITIALIZED; if (!g_masteringInitialized.load()) return SCE_AUDIO_OUT2_ERROR_MASTERING_NOT_INITIALIZED; if (state == nullptr) return SCE_AUDIO_OUT2_ERROR_INVALID_POINTER; switch (state->states_id) { @@ -125,6 +130,7 @@ int APS5_VABI sceAudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* stat int APS5_VABI sceAudioOut2MasteringSetParam(const AudioOut2MasteringParamsHeader* param, uint32_t output, uint32_t flags) { (void)output; + if (!AudioOut2IsInitialized_nid_no_patch()) return SCE_AUDIO_OUT2_ERROR_NOT_INITIALIZED; if (!g_masteringInitialized.load()) return SCE_AUDIO_OUT2_ERROR_MASTERING_NOT_INITIALIZED; if (param == nullptr) return SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM; if (flags > AUDIO_OUT2_MASTERING_SET_PARAM_FLAGS_MAX) return SCE_AUDIO_OUT2_ERROR_MASTERING_INVALID_FLAG; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp index e8a547fefc..bc2b4d7d32 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp @@ -110,13 +110,20 @@ AudioOut2Grain AudioOut2CaptureGrain(const AudioOut2Context& context, bool snaps auto& port = g_ports[index]; if (!port.used || port.context != &context || port.channels == 0) continue; const void* data = port.repeatedData; + const std::vector* setPcm = port.repeatedReused ? &port.repeatedPcm : nullptr; + AudioOut2QueuedData queued; if (!port.queuedData.empty()) { - data = port.queuedData.front(); + queued = std::move(port.queuedData.front()); port.queuedData.pop_front(); + data = queued.data; + setPcm = queued.reused ? &queued.pcm : nullptr; } if (data != nullptr) { AudioOut2CapturedPort captured{index, port.generation, data}; - if (snapshot) { + if (setPcm != nullptr) { + captured.pcm = *setPcm; + std::copy_n(port.volume, port.channels, captured.volume.begin()); + } else if (snapshot) { captured.pcm.resize(static_cast(context.grain) * port.channels * (port.int16 ? sizeof(std::int16_t) : sizeof(float))); std::memcpy(captured.pcm.data(), data, captured.pcm.size()); std::copy_n(port.volume, port.channels, captured.volume.begin()); @@ -160,6 +167,24 @@ void AudioOut2ReleasePorts(const AudioOut2Context& context) { } } +static std::vector CopyGrain(const AudioOut2Port& port) { + if (port.data == nullptr || port.context == nullptr) return {}; + std::vector pcm(static_cast(port.context->grain) * port.channels * (port.int16 ? sizeof(std::int16_t) : sizeof(float))); + std::memcpy(pcm.data(), port.data, pcm.size()); + return pcm; +} + +static void MarkBufferReused(const AudioOut2Port& setter) { + if (setter.data == nullptr) return; + for (auto& port : g_ports) { + if (!port.used || &port == &setter || port.context != setter.context) continue; + for (auto& queued : port.queuedData) { + if (queued.data == setter.data) queued.reused = true; + } + if (port.repeatedData == setter.data) port.repeatedReused = true; + } +} + static void TraceAttribute(AudioOut2PortHandle handle, const AudioOut2Port& port, const AudioOut2Attribute& attribute, const char* verdict) { if (!AudioOut2TraceEnabled()) return; char values[128] = ""; @@ -267,9 +292,13 @@ int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle port, const Audi verdict = "null value"; } else if (attribute.attribute_id == ATTRIBUTE_DATA && attribute.value_size == sizeof(entry->data)) { std::memcpy(&entry->data, attribute.value, sizeof(entry->data)); - entry->queuedData.push_back(entry->data); - if (entry->queuedData.size() > QUEUED_DATA_MAX) entry->queuedData.pop_front(); + MarkBufferReused(*entry); + AudioOut2QueuedData queued{entry->data, CopyGrain(*entry)}; entry->repeatedData = entry->data != nullptr && (entry->lastBuffer == nullptr || entry->lastBuffer == entry->data) ? entry->data : nullptr; + entry->repeatedPcm = queued.pcm; + entry->repeatedReused = false; + entry->queuedData.push_back(std::move(queued)); + if (entry->queuedData.size() > QUEUED_DATA_MAX) entry->queuedData.pop_front(); if (entry->data != nullptr) entry->lastBuffer = entry->data; entry->dataSets++; verdict = "pcm data pointer"; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp b/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp index 45f41f5a43..4bec9dd7b6 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOutSdl.cpp @@ -2,9 +2,30 @@ #include "SDL.h" #include +#ifdef _WIN32 +#include +#endif int InitializeSdlAudio_nid_no_patch() { static std::mutex initializationMutex; std::lock_guard lock(initializationMutex); return SDL_InitSubSystem(SDL_INIT_AUDIO); } + +bool WasapiDefaultOutputAbsent_nid_no_patch() { +#ifdef _WIN32 + const HRESULT initialized = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + bool absent = false; + IMMDeviceEnumerator* enumerator = nullptr; + if (SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)))) { + IMMDevice* device = nullptr; + absent = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device) == HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + if (device != nullptr) device->Release(); + enumerator->Release(); + } + if (SUCCEEDED(initialized)) CoUninitialize(); + return absent; +#else + return false; +#endif +} diff --git a/core/libs/prx/libSceAudioOut/src/InventoryExports.cpp b/core/libs/prx/libSceAudioOut/src/InventoryExports.cpp new file mode 100644 index 0000000000..f6c3567919 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/src/InventoryExports.cpp @@ -0,0 +1,50 @@ +#include "prx/libSceAudioOut/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAudioOut2GetSystemDebugState() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2LoContextCreateEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2LoContextGetAttributes() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2LoContextQueryMemoryEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2LoPortCreateEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2LoPortGetAttributes() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2LoPortSetAttributesEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2PortSetAttributesEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2SetHrtfIdForCronos() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2SetSpeakerInfo() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOut2SetTvCorrectionInfo() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp index 6fee544dd5..12dbcb3854 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -48,6 +48,7 @@ static_assert(sizeof(AudioOut2MasteringStatesV2) == 400); constexpr int systemUser = 0xFF; constexpr int user = 0x10000000; constexpr int errorInvalidParam = static_cast(0x80268001); +constexpr int errorNotInitialized = static_cast(0x80268006); constexpr int errorInvalidPointer = static_cast(0x8026800C); constexpr int errorMasteringInvalidApiParam = static_cast(0x80268201); constexpr int errorMasteringInvalidStatesId = static_cast(0x80268204); @@ -67,6 +68,23 @@ void TestSet3DLatency() { Require(sceAudioOut2Set3DLatency(systemUser, 0xFFFFFFFFu) == errorInvalidParam, "a large output must be refused"); } +void TestMasteringBeforeInitialize() { + const AudioOut2MasteringParamsHeader params{1u}; + AudioOut2MasteringStates storage{}; + storage.states_header.states_id = 1u; + Require(sceAudioOut2MasteringInit(0) == errorNotInitialized, "mastering initialization before AudioOut2 initialization is refused"); + Require(sceAudioOut2MasteringInit(1) == errorNotInitialized, "the AudioOut2 initialization check comes before the flag check"); + Require(sceAudioOut2MasteringInitEx(0) == errorNotInitialized, "sceAudioOut2MasteringInitEx before AudioOut2 initialization is refused"); + Require(sceAudioOut2MasteringInitEx(0x80000000u) == errorNotInitialized, "sceAudioOut2MasteringInitEx with the mode bit before AudioOut2 initialization is refused"); + Require(sceAudioOut2MasteringInitEx(1) == errorNotInitialized, "the AudioOut2 initialization check comes before the sceAudioOut2MasteringInitEx flag check"); + Require(sceAudioOut2MasteringTerm() == errorNotInitialized, "termination before AudioOut2 initialization is refused"); + Require(sceAudioOut2MasteringGetState(nullptr, 0, 0) == errorNotInitialized, "the AudioOut2 initialization check comes before the null state check"); + Require(sceAudioOut2MasteringGetState(&storage.states_header, 0, 0) == errorNotInitialized, "states before AudioOut2 initialization are refused"); + Require(storage.states_header.states_id == 1u && storage.compressor_states.descriptor.id == 0u, "refused states are not written"); + Require(sceAudioOut2MasteringSetParam(¶ms, 0, 0) == errorNotInitialized, "parameters before AudioOut2 initialization are refused"); + Require(sceAudioOut2MasteringSetParam(nullptr, 0, 2) == errorNotInitialized, "the AudioOut2 initialization check comes before the parameter checks"); +} + void TestMasteringInit() { const AudioOut2MasteringParamsHeader params{1u}; AudioOut2MasteringStates storage{}; @@ -227,6 +245,7 @@ void TestLowLatencyService() { } int main() { + TestMasteringBeforeInitialize(); Require(sceAudioOut2Initialize() == 0, "initialization must succeed"); Require(sceAudioOut2Initialize() == static_cast(0x80268004), "a second AudioOut2 initialization fails"); Require(sceAudioOutInit() == 0, "the first legacy initialization succeeds after AudioOut2's"); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp index fff06c8167..a46559d5ae 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2PortBuffers.cpp @@ -275,6 +275,28 @@ void TestRenderingSizedByLevelStaysWithinQueueDepth() { RequirePlayed(played, order); } +void TestBufferReusedAcrossPortsKeepsEachPortsGrain(bool advanced) { + const auto voice = Grain(0.01f); + const std::vector silence(voice.size(), 0.0f); + auto shared = voice; + const auto played = Play([&](AudioOut2ContextHandle context, AudioOut2PortHandle port) { + AudioOut2PortParam portParams{}; + portParams.port_type = portTypeMain; + portParams.data_format = formatStereoFloat; + portParams.sampling_freq = frequency; + AudioOut2PortHandle other = 0; + Require(sceAudioOut2PortCreate(context, &portParams, &other) == 0); + SetData(port, shared.data()); + shared = silence; + SetData(other, shared.data()); + if (advanced) Require(sceAudioOut2ContextAdvance(context) == 0); + Require(sceAudioOut2ContextPush(context, 1) == 0); + SetData(port, nullptr); + SetData(other, nullptr); + }, advanced); + RequirePlayed(played, {&voice}); +} + } int main() { @@ -288,5 +310,7 @@ int main() { TestAdvancingPortWithoutNewBufferPlaysSilence(); TestRewrittenBufferRepeats(); TestRenderingSizedByLevelStaysWithinQueueDepth(); + TestBufferReusedAcrossPortsKeepsEachPortsGrain(false); + TestBufferReusedAcrossPortsKeepsEachPortsGrain(true); return 0; } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp index 0dfd811378..d0b85c57cc 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Timing.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "prx/libSceAudioOut/src/AudioOut2Internal.hpp" #include "SceTypes.hpp" #include @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +163,44 @@ void TestLevelExcludesCushion() { session.Close(); } +void TestDeviceLevelDoesNotWaitForTheContext(bool low) { + Session session("anyps5_audio_out2_level_poll.raw", low); + auto& context = *reinterpret_cast(session.context); + Require(context.device != 0); + std::promise started; + auto ready = started.get_future(); + std::unique_lock lock(context.lock); + auto poll = std::async(std::launch::async, [&] { + started.set_value(); + std::uint32_t level = 99; + std::uint32_t available = 99; + Require((low ? sceAudioOut2LoContextGetQueueLevel : sceAudioOut2ContextGetQueueLevel)(session.context, &level, &available) == 0); + return std::pair{level, available}; + }); + ready.wait(); + const bool completed = poll.wait_for(std::chrono::seconds(1)) == std::future_status::ready; + lock.unlock(); + const auto result = poll.get(); + session.Close(); + Require(completed); + Require(result.first == 0 && result.second == queueDepth); +} + +void TestLevelWithoutADeviceUsesThePlaybackClock() { + AudioOut2Context context; + context.queueDepth = queueDepth; + context.queued = queueDepth; + context.playHead = std::chrono::steady_clock::now() + std::chrono::seconds(5); + const auto handle = reinterpret_cast(&context); + std::uint32_t level = 99; + std::uint32_t available = 99; + Require(sceAudioOut2ContextGetQueueLevel(handle, &level, &available) == 0); + Require(level == queueDepth && available == 0); + context.playHead = std::chrono::steady_clock::now() - std::chrono::seconds(5); + Require(sceAudioOut2LoContextGetQueueLevel(handle, &level, &available) == 0); + Require(level == 0 && available == queueDepth); +} + void TestPrimingAfterRunningDry() { Session session("anyps5_audio_out2_priming.raw"); session.Fill(0.5f); @@ -316,6 +356,9 @@ int main() { TestReadAtNextPush(); TestRecreatedPort(); TestLevelExcludesCushion(); + TestDeviceLevelDoesNotWaitForTheContext(false); + TestDeviceLevelDoesNotWaitForTheContext(true); + TestLevelWithoutADeviceUsesThePlaybackClock(); TestPrimingAfterRunningDry(); TestLowLatencyCallsRejectBadArguments(); TestLowLatencyReadsAtNextPush(); diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp index 6b2eaaef4e..db9b3df26e 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp @@ -53,6 +53,7 @@ void RunOverflow(std::uint32_t frames, std::uint32_t format, std::uint32_t frame } int InitializeSdlAudio_nid_no_patch() { return 0; } +bool WasapiDefaultOutputAbsent_nid_no_patch() { return false; } extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; } extern "C" int SDLCALL SDL_GetNumAudioDevices(int) { return 1; } extern "C" const char* SDLCALL SDL_GetAudioDeviceName(int, int) { return "Main speakers"; } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutDefaultOpen.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutDefaultOpen.cpp new file mode 100644 index 0000000000..0b54f5b399 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutDefaultOpen.cpp @@ -0,0 +1,86 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "SDL.h" +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { +constexpr SDL_AudioDeviceID device = 7; +const char* driver = "wasapi"; +int outputDevices = 0; +bool endpointAbsent = false; +int probes = 0; +int opened = 0; +int closed = 0; + +void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} +} + +int InitializeSdlAudio_nid_no_patch() { return 0; } +bool WasapiDefaultOutputAbsent_nid_no_patch() { + ++probes; + return endpointAbsent; +} +extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; } +extern "C" int SDLCALL SDL_GetNumAudioDevices(int) { return outputDevices; } +extern "C" const char* SDLCALL SDL_GetAudioDeviceName(int, int) { return "Main speakers"; } +extern "C" int SDLCALL SDL_GetAudioDeviceSpec(int, int, SDL_AudioSpec* spec) { *spec = {}; spec->channels = 2; return 0; } +extern "C" const char* SDLCALL SDL_GetCurrentAudioDriver() { return driver; } +extern "C" const char* SDLCALL SDL_GetError() { return "test error"; } +extern "C" SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char* name, int capture, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int) { + Require(name == nullptr && capture == 0, "the main port did not open the default output"); + ++opened; + *obtained = *desired; + return device; +} +extern "C" void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID id, int) { Require(id == device, "wrong device paused"); } +extern "C" Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID id) { Require(id == device, "wrong queue queried"); return 0; } +extern "C" int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID id, const void*, Uint32) { Require(id == device, "wrong queue written"); return 0; } +extern "C" void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID id) { Require(id == device, "wrong queue cleared"); } +extern "C" void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID id) { Require(id == device, "wrong device closed"); ++closed; } + +int main(int argc, char** argv) { + try { + Require(argc == 2, "invalid test arguments"); + const std::string_view mode = argv[1]; + bool expectOpen = true; + if (mode == "zero_default") { + driver = "alsa"; + outputDevices = 0; + } else if (mode == "wasapi_zero_default") { + driver = "wasapi"; + outputDevices = 0; + } else if (mode == "no_endpoint") { + driver = "wasapi"; + outputDevices = 0; + endpointAbsent = true; + expectOpen = false; + } else if (mode == "positive") { + driver = "wasapi"; + outputDevices = 2; + } else if (mode == "unknown") { + driver = "wasapi"; + outputDevices = -1; + } else { + Require(false, "invalid default open mode"); + } + const int handle = sceAudioOutOpen(0x10000000, 0, 0, 256, 48000, 1); + Require(handle >= 1, "the main port did not open"); + Require(opened == (expectOpen ? 1 : 0), expectOpen ? "the default output was not opened" : "the default output was opened with no WASAPI endpoint"); + Require(sceAudioOutClose(handle) == 0, "the main port did not close"); + Require(closed == opened, "the default output was not closed with its port"); + Require(probes == (driver == std::string_view("wasapi") && outputDevices == 0 ? 1 : 0), "the default endpoint was probed outside an empty WASAPI list"); + probes = 0; + Require(SdlAudioReportsNoOutputEndpoint(driver, outputDevices) == !expectOpen, "the no-endpoint predicate disagrees with the mode"); + Require(!SdlAudioReportsNoOutputEndpoint(nullptr, 0), "a missing driver name skipped the default open"); + std::puts("default output open 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/AudioOutVibrationDevice.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp index 79681dff96..505cbf878c 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp @@ -186,6 +186,7 @@ void RunCadence() { } int InitializeSdlAudio_nid_no_patch() { return failInitialize ? -1 : 0; } +bool WasapiDefaultOutputAbsent_nid_no_patch() { return false; } 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); diff --git a/core/libs/prx/libSceAutoMounterClient/Export.cpp b/core/libs/prx/libSceAutoMounterClient/Export.cpp index ab7eed9165..63b11a74c2 100644 --- a/core/libs/prx/libSceAutoMounterClient/Export.cpp +++ b/core/libs/prx/libSceAutoMounterClient/Export.cpp @@ -5,12 +5,17 @@ #include "prx/libc/include/General.hpp" static_assert(sizeof(AutoMounterUsbDeviceInfo) == 944); -static_assert(offsetof(AutoMounterUsbDeviceInfo, data0) == 150); -static_assert(offsetof(AutoMounterUsbDeviceInfo, data0Size) == 408); -static_assert(offsetof(AutoMounterUsbDeviceInfo, data1) == 416); -static_assert(offsetof(AutoMounterUsbDeviceInfo, data1Size) == 672); -static_assert(offsetof(AutoMounterUsbDeviceInfo, data2) == 680); -static_assert(offsetof(AutoMounterUsbDeviceInfo, data2Size) == 936); +static_assert(offsetof(AutoMounterUsbDeviceInfo, totalSpace) == 128); +static_assert(offsetof(AutoMounterUsbDeviceInfo, availableSpace) == 136); +static_assert(offsetof(AutoMounterUsbDeviceInfo, vendorId) == 144); +static_assert(offsetof(AutoMounterUsbDeviceInfo, productId) == 146); +static_assert(offsetof(AutoMounterUsbDeviceInfo, bcdDevice) == 148); +static_assert(offsetof(AutoMounterUsbDeviceInfo, manufacturer) == 150); +static_assert(offsetof(AutoMounterUsbDeviceInfo, manufacturerLength) == 408); +static_assert(offsetof(AutoMounterUsbDeviceInfo, product) == 416); +static_assert(offsetof(AutoMounterUsbDeviceInfo, productLength) == 672); +static_assert(offsetof(AutoMounterUsbDeviceInfo, serialNumber) == 680); +static_assert(offsetof(AutoMounterUsbDeviceInfo, serialNumberLength) == 936); extern "C" { diff --git a/core/libs/prx/libSceAutoMounterClient/include/Api.hpp b/core/libs/prx/libSceAutoMounterClient/include/Api.hpp index 766f754f18..fc0dc320cc 100644 --- a/core/libs/prx/libSceAutoMounterClient/include/Api.hpp +++ b/core/libs/prx/libSceAutoMounterClient/include/Api.hpp @@ -4,14 +4,18 @@ struct AutoMounterUsbDeviceInfo { char deviceName[128]; - std::byte metadata[22]; - std::byte data0[256]; - std::byte padding[2]; - std::uint64_t data0Size; - std::byte data1[256]; - std::uint64_t data1Size; - std::byte data2[256]; - std::uint64_t data2Size; + std::uint64_t totalSpace; + std::uint64_t availableSpace; + std::uint16_t vendorId; + std::uint16_t productId; + std::uint16_t bcdDevice; + char manufacturer[256]; + std::uint8_t padding[2]; + std::uint64_t manufacturerLength; + char product[256]; + std::uint64_t productLength; + char serialNumber[256]; + std::uint64_t serialNumberLength; }; extern "C" { diff --git a/core/libs/prx/libSceAvcap2/Export.cpp b/core/libs/prx/libSceAvcap2/Export.cpp index d81f4a5e32..2afd1f25de 100644 --- a/core/libs/prx/libSceAvcap2/Export.cpp +++ b/core/libs/prx/libSceAvcap2/Export.cpp @@ -1,10 +1,44 @@ -#include +#include "prx/libSceAvcap2/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceAvcap2Initialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAvcap2Terminate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAvcap2OpenAudio(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAvcap2Close(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAvcap2Start(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAvcap2Stop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAvcap2ReadAudio(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceAvcap2/include/Api.hpp b/core/libs/prx/libSceAvcap2/include/Api.hpp new file mode 100644 index 0000000000..66afd355f3 --- /dev/null +++ b/core/libs/prx/libSceAvcap2/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAvcap2Initialize(void); +int APS5_VABI sceAvcap2Terminate(void); +int APS5_VABI sceAvcap2OpenAudio(void); +int APS5_VABI sceAvcap2Close(void); +int APS5_VABI sceAvcap2Start(void); +int APS5_VABI sceAvcap2Stop(void); +int APS5_VABI sceAvcap2ReadAudio(void); + +} diff --git a/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp b/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp index 6f70f09bee..7ca2e6f1de 100644 --- a/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp +++ b/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceAvcap2/include/Api.hpp" diff --git a/core/libs/prx/libSceCamera/CMakeLists.txt b/core/libs/prx/libSceCamera/CMakeLists.txt index 8591dce44f..f7c5e9f985 100644 --- a/core/libs/prx/libSceCamera/CMakeLists.txt +++ b/core/libs/prx/libSceCamera/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(libSceCamera SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/InventoryExports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Handles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Attributes.cpp diff --git a/core/libs/prx/libSceCamera/InventoryExports.cpp b/core/libs/prx/libSceCamera/InventoryExports.cpp new file mode 100644 index 0000000000..5251fcf228 --- /dev/null +++ b/core/libs/prx/libSceCamera/InventoryExports.cpp @@ -0,0 +1,22 @@ +#include "prx/libSceCamera/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceCameraGetUsageInfo() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCameraGetUsageInfoWithoutCountProcess() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCameraOpenByModuleIdWithMode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCameraSetForceDriverMode() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceCamera/include/InventoryExports.hpp b/core/libs/prx/libSceCamera/include/InventoryExports.hpp new file mode 100644 index 0000000000..7b4ea82928 --- /dev/null +++ b/core/libs/prx/libSceCamera/include/InventoryExports.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCameraGetUsageInfo(); +int APS5_VABI sceCameraGetUsageInfoWithoutCountProcess(); +int APS5_VABI sceCameraOpenByModuleIdWithMode(); +int APS5_VABI sceCameraSetForceDriverMode(); + +} diff --git a/core/libs/prx/libSceCamera/libSceCamera.hpp b/core/libs/prx/libSceCamera/libSceCamera.hpp index f11c2f4973..c457e42108 100644 --- a/core/libs/prx/libSceCamera/libSceCamera.hpp +++ b/core/libs/prx/libSceCamera/libSceCamera.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceCamera/include/Api.hpp" +#include "prx/libSceCamera/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceCommonDialog/CMakeLists.txt b/core/libs/prx/libSceCommonDialog/CMakeLists.txt index d770a3c871..d3e8c4b64e 100644 --- a/core/libs/prx/libSceCommonDialog/CMakeLists.txt +++ b/core/libs/prx/libSceCommonDialog/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceCommonDialog SHARED EXCLUDE_FROM_ALL Export.cpp) +add_library(libSceCommonDialog SHARED EXCLUDE_FROM_ALL Export.cpp InventoryExports.cpp) target_include_directories(libSceCommonDialog PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceCommonDialog) diff --git a/core/libs/prx/libSceCommonDialog/InventoryExports.cpp b/core/libs/prx/libSceCommonDialog/InventoryExports.cpp new file mode 100644 index 0000000000..d0ad42de0e --- /dev/null +++ b/core/libs/prx/libSceCommonDialog/InventoryExports.cpp @@ -0,0 +1,92 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceCommonDialogPending_001_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2RdicdHhtGA", sceCommonDialogPending_001_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_002_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("I+tdxsCap08", sceCommonDialogPending_002_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_003_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("v4+gzuTkv6k", sceCommonDialogPending_003_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_004_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("CwCzG0nnLg8", sceCommonDialogPending_004_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_005_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Ib1SMmbr07k", sceCommonDialogPending_005_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_006_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6TIMpGvsrC4", sceCommonDialogPending_006_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_007_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("+UyKxWAnqIU", sceCommonDialogPending_007_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_008_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("bUCx72-9f0g", sceCommonDialogPending_008_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_009_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xZtXq554Lbg", sceCommonDialogPending_009_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_010_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("C-EZ3PkhibQ", sceCommonDialogPending_010_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_011_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("70niEKUAnZ0", sceCommonDialogPending_011_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_012_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("mdJgdwoM0Mo", sceCommonDialogPending_012_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_013_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("87GekE1nowg", sceCommonDialogPending_013_nid_no_patch); + +int APS5_VABI sceCommonDialogPending_014_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6ljeTSi+fjs", sceCommonDialogPending_014_nid_no_patch); + +} diff --git a/core/libs/prx/libSceContentSearch/Export.cpp b/core/libs/prx/libSceContentSearch/Export.cpp index 10c13bfbe1..3b3229ac06 100644 --- a/core/libs/prx/libSceContentSearch/Export.cpp +++ b/core/libs/prx/libSceContentSearch/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceContentSearch/include/InventoryExports.hpp" #include "prx/libSceContentSearch/include/Api.hpp" #include #include @@ -62,6 +63,23 @@ void ValidateOrderBy(const char* funcName, const ContentSearchContentOrderByCond extern "C" { +int APS5_VABI sceContentSearchAddAvContentCreatedEventCondition() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceContentSearchDisableAvContentCreatedEvent() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceContentSearchEnableAvContentCreatedEvent() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceContentSearchGetContentIdByAvContentCreatedEvent() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceContentSearchInit(const ContentSearchInitParam* init_param) { if (g_initialized.load()) return ErrorAlreadyInitialized; if (init_param == nullptr || init_param->memory_size == 0 || init_param->memory_size % 16384 != 0) return ErrorInvalidArgument; diff --git a/core/libs/prx/libSceContentSearch/include/InventoryExports.hpp b/core/libs/prx/libSceContentSearch/include/InventoryExports.hpp new file mode 100644 index 0000000000..b5a22ec35d --- /dev/null +++ b/core/libs/prx/libSceContentSearch/include/InventoryExports.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceContentSearchAddAvContentCreatedEventCondition(); +int APS5_VABI sceContentSearchDisableAvContentCreatedEvent(); +int APS5_VABI sceContentSearchEnableAvContentCreatedEvent(); +int APS5_VABI sceContentSearchGetContentIdByAvContentCreatedEvent(); + +} diff --git a/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp b/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp index 79d8740064..1398ef35d4 100644 --- a/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp +++ b/core/libs/prx/libSceContentSearch/libSceContentSearch.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceContentSearch/include/Api.hpp" +#include "prx/libSceContentSearch/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceFont/CMakeLists.txt b/core/libs/prx/libSceFont/CMakeLists.txt index dfa37bf121..5af35fd6b8 100644 --- a/core/libs/prx/libSceFont/CMakeLists.txt +++ b/core/libs/prx/libSceFont/CMakeLists.txt @@ -7,6 +7,16 @@ if(BUILD_TESTING) target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc) configure_windows_unwind(guest_font_tests) add_test(NAME guest_font COMMAND guest_font_tests) + add_test_executable(guest_font_scissor_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontScissor.cpp) + target_include_directories(guest_font_scissor_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_scissor_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(guest_font_scissor_tests) + add_test(NAME guest_font_scissor COMMAND guest_font_scissor_tests) + set_tests_properties(guest_font_scissor PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_font_scissor PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_system_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemFont.cpp) target_include_directories(guest_system_font_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_font_tests PRIVATE libSceFont libSceFontFt libc) diff --git a/core/libs/prx/libSceFont/src/Text.cpp b/core/libs/prx/libSceFont/src/Text.cpp index 0b6823f3b3..21c7596e18 100644 --- a/core/libs/prx/libSceFont/src/Text.cpp +++ b/core/libs/prx/libSceFont/src/Text.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include #include #include @@ -31,11 +30,6 @@ constexpr std::int32_t WRITING_MASK_NONE = 0; constexpr std::int32_t WRITING_MASK_FORMAT_CHARACTERS = 1; constexpr std::uint64_t CHARACTER_FLAG_FORMAT = 1ull << 42; -struct CharacterStorage { - std::atomic refCount{1}; - std::vector characters; -}; - struct FontStringData { std::uint16_t magic = FONT_STRING_MAGIC; std::uint8_t flags = 0; @@ -48,7 +42,7 @@ struct FontStringData { void* terminateOrder = nullptr; std::uint32_t textCount = 0; std::uint32_t renderCount = 0; - CharacterStorage* storage = nullptr; + FontTextCharacter* characters = nullptr; }; struct FontWritingData { @@ -56,7 +50,6 @@ struct FontWritingData { std::uint16_t reserved0 = 0; std::uint32_t writingForm = WRITING_FORM_HORIZONTAL; FontString string = nullptr; - CharacterStorage* storage = nullptr; FontHandle defaultFont = nullptr; std::size_t nextIndex = 0; std::size_t currentIndex = 0; @@ -66,7 +59,6 @@ struct FontWritingData { bool hasCurrentStep = false; bool hasCurrentCharacter = false; bool maskFormatCharacters = true; - bool ownsStorage = false; bool hasRawGlyphMetrics = false; FontWritingStep step{}; FontWritingMetrics metrics{}; @@ -87,18 +79,6 @@ FontWritingData* GetWritingData(FontWriting* fontWriting) { return data->magic == FONT_WRITING_MAGIC ? data : nullptr; } -void ReleaseStorage(CharacterStorage*& storage) { - if (!storage) return; - if (storage->refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) delete storage; - storage = nullptr; -} - -void ReleaseWritingStorage(FontWritingData& writing) { - if (!writing.ownsStorage) return; - ReleaseStorage(writing.storage); - writing.ownsStorage = false; -} - std::uint16_t TextSourceMagic(const FontTextSource* source) { return static_cast(source->systemUse0 & 0xFFFFu); } @@ -135,15 +115,15 @@ FontTextCharacter MakeCharacter(FontHandle font, std::uint32_t code, void* textO return character; } -void LinkCharacters(std::vector& characters) { - for (std::size_t i = 0; i < characters.size(); ++i) { +void LinkCharacters(FontTextCharacter* characters, std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { characters[i].prev = i == 0 ? nullptr : &characters[i - 1]; - characters[i].next = i + 1 < characters.size() ? &characters[i + 1] : nullptr; + characters[i].next = i + 1 < count ? &characters[i + 1] : nullptr; } } FontTextCharacter* TextCharacters(FontStringData* data) { - return data && data->storage && !data->storage->characters.empty() ? data->storage->characters.data() : nullptr; + return data && data->textCount != 0 ? data->characters : nullptr; } bool IsRightToLeftLetter(std::uint32_t code) { @@ -194,8 +174,9 @@ void FillWritingMetricsFromGlyph(FontWritingData& writing, const FontGlyphMetric } bool PopulateWritingStep(FontWritingData& writing, std::size_t index) { - if (!writing.storage || index >= writing.storage->characters.size()) return false; - const FontTextCharacter& character = writing.storage->characters[index]; + const FontStringData* data = GetStringData(writing.string); + if (!data || index >= data->textCount) return false; + const FontTextCharacter& character = data->characters[index]; const FontHandle font = character.font ? character.font : writing.defaultFont; writing.step.x = writing.penX; writing.step.y = writing.penY; @@ -328,9 +309,9 @@ int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* data->memory = fontMemory; data->source = textSource; data->defaultFont = textSource->defaultFont; - data->storage = new CharacterStorage{}; if (!data->defaultFont && detail) data->defaultFont = detail->defaultFont; data->writingForm = static_cast(textSource->systemUse0 >> 32); + std::vector characters; std::int32_t state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; void* textOrder = nullptr; for (std::size_t parsed = 0; parsed < MAX_PARSED_CHARACTERS; ++parsed) { @@ -342,7 +323,7 @@ int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; break; } - data->storage->characters.push_back(MakeCharacter(font, result.FontCode.code, textOrder)); + characters.push_back(MakeCharacter(font, result.FontCode.code, textOrder)); continue; } if (state == PARSER_RESULT_TERMINATE) { @@ -357,15 +338,19 @@ int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* break; } if (state == PARSER_RESULT_FONT_CODE) state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + if (state == SCE_FONT_OK && !characters.empty()) { + data->characters = static_cast(fontMemory->iface->alloc(fontMemory->mspace_handle, static_cast(characters.size() * sizeof(FontTextCharacter)))); + if (!data->characters) state = SCE_FONT_ERROR_ALLOCATION_FAILED; + } if (state != SCE_FONT_OK) { - ReleaseStorage(data->storage); - data->~FontStringData(); + data->magic = 0; fontMemory->iface->dealloc(fontMemory->mspace_handle, raw); return state; } - if (data->writingForm == WRITING_FORM_HORIZONTAL) data->writingForm = DefaultStringForm(data->storage->characters); - LinkCharacters(data->storage->characters); - data->textCount = static_cast(data->storage->characters.size()); + if (data->writingForm == WRITING_FORM_HORIZONTAL) data->writingForm = DefaultStringForm(characters); + std::copy(characters.begin(), characters.end(), data->characters); + LinkCharacters(data->characters, characters.size()); + data->textCount = static_cast(characters.size()); data->renderCount = data->textCount; *pFontString = reinterpret_cast(data); return SCE_FONT_OK; @@ -379,8 +364,8 @@ int APS5_VABI sceFontDestroyString(FontString* pFontString) { return SCE_FONT_ERROR_INVALID_STRING; } const FontMemory* memory = data->memory; - ReleaseStorage(data->storage); - data->~FontStringData(); + data->magic = 0; + if (data->characters) memory->iface->dealloc(memory->mspace_handle, data->characters); memory->iface->dealloc(memory->mspace_handle, data); *pFontString = nullptr; return SCE_FONT_OK; @@ -640,25 +625,21 @@ int APS5_VABI sceFontWritingInit(FontWriting* fontWriting, FontString fontString if (!fontWriting || !fontString || !fontCharacter) return SCE_FONT_ERROR_INVALID_PARAMETER; auto* data = GetStringData(fontString); if (!data) return SCE_FONT_ERROR_INVALID_STRING; - if (auto* previous = GetWritingData(fontWriting)) ReleaseWritingStorage(*previous); std::memset(fontWriting, 0, sizeof(*fontWriting)); auto* writing = new (fontWriting->systemUse) FontWritingData{}; writing->string = fontString; - writing->storage = data->storage; writing->defaultFont = data->defaultFont; - if (writing->storage) writing->storage->refCount.fetch_add(1, std::memory_order_relaxed); - writing->ownsStorage = true; writing->writingForm = data->writingForm; const FontTextCharacter* characters = TextCharacters(data); - if (!characters || fontCharacter < characters || fontCharacter >= characters + data->storage->characters.size()) { - ReleaseWritingStorage(*writing); + if (!characters || fontCharacter < characters || fontCharacter >= characters + data->textCount) { + writing->string = nullptr; return SCE_FONT_ERROR_INVALID_PARAMETER; } writing->nextIndex = static_cast(fontCharacter - characters); writing->currentIndex = writing->nextIndex; writing->hasCurrentStep = false; if (!PopulateWritingStep(*writing, writing->nextIndex)) { - ReleaseWritingStorage(*writing); + writing->string = nullptr; return SCE_FONT_ERROR_INVALID_PARAMETER; } writing->penX = 0.0f; @@ -684,8 +665,7 @@ int APS5_VABI sceFontWritingGetRenderMetrics(FontWriting* fontWriting, FontWriti const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fontWriting) { auto* writing = GetWritingData(fontWriting); - if (!writing || !writing->storage || writing->nextIndex >= writing->storage->characters.size()) return nullptr; - if (!PopulateWritingStep(*writing, writing->nextIndex)) return nullptr; + if (!writing || !PopulateWritingStep(*writing, writing->nextIndex)) return nullptr; writing->currentIndex = writing->nextIndex; writing->hasCurrentStep = true; writing->hasCurrentCharacter = true; @@ -697,7 +677,9 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fon FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, const FontWritingLetterStep** pLetterStep) { if (pLetterStep) *pLetterStep = nullptr; auto* writing = GetWritingData(fontWriting); - if (!writing || !writing->hasCurrentStep || !writing->hasCurrentCharacter || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr; + if (!writing || !writing->hasCurrentStep || !writing->hasCurrentCharacter) return nullptr; + auto* data = GetStringData(writing->string); + if (!data || writing->currentIndex >= data->textCount) return nullptr; writing->hasCurrentCharacter = false; auto& letterStep = writing->letterStep; letterStep = {}; @@ -711,7 +693,7 @@ FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting letterStep.Components.glyphsIndex = static_cast(writing->currentIndex); letterStep.Components.characterTextCount = static_cast(std::max(writing->step.Profile.characterCount, 1)); if (pLetterStep) *pLetterStep = &letterStep; - return &writing->storage->characters[writing->currentIndex]; + return &data->characters[writing->currentIndex]; } int APS5_VABI sceFontWritingSetMaskInvisible(FontWriting* fontWriting, std::int32_t mask) { diff --git a/core/libs/prx/libSceFreeType/CMakeLists.txt b/core/libs/prx/libSceFreeType/CMakeLists.txt index 9682b1e0b3..b9810d2a6e 100644 --- a/core/libs/prx/libSceFreeType/CMakeLists.txt +++ b/core/libs/prx/libSceFreeType/CMakeLists.txt @@ -4,12 +4,27 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL src/Outline.cpp src/Charmap.cpp src/FaceInfo.cpp + src/Stroker.cpp src/GuestObjects.cpp src/Names.cpp src/Trigonometry.cpp src/OutlineDecompose.cpp ) target_include_directories(libSceFreeType PRIVATE ${LIBS_INCLUDE_DIR}) +target_sources(libSceFreeType PRIVATE src/Advances.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_freetype_advances_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeAdvances.cpp) + target_include_directories(guest_freetype_advances_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_advances_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_advances_tests) + add_test(NAME guest_freetype_advances COMMAND guest_freetype_advances_tests) + if(WIN32) + set_tests_properties(guest_freetype_advances PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + target_link_libraries(libSceFreeType PRIVATE freetype) target_sources(libSceFreeType PRIVATE src/SizeRequest.cpp) @@ -88,6 +103,15 @@ if(BUILD_TESTING) set_tests_properties(guest_freetype_bitmap_init PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freetype_stroker_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeStroker.cpp) + target_include_directories(guest_freetype_stroker_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_stroker_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_stroker_tests) + add_test(NAME guest_freetype_stroker COMMAND guest_freetype_stroker_tests) + if(WIN32) + set_tests_properties(guest_freetype_stroker PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_freetype_charmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCharmap.cpp) target_include_directories(guest_freetype_charmap_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc) diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index 048fa59647..38dd6befc3 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -27,6 +27,8 @@ int APS5_VABI FT_Get_PFR_Advance_nid_postfix(FreeType::GuestFaceRec* face, std:: int APS5_VABI FT_Get_PFR_Kerning_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t left, std::uint32_t right, FreeType::GuestVector* vector); int APS5_VABI FT_Get_PFR_Metrics_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t* outline, std::uint32_t* metrics, std::int64_t* xScale, std::int64_t* yScale); int APS5_VABI FT_Load_Glyph_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t glyphIndex, std::int32_t loadFlags); +int APS5_VABI FT_Get_Advance_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t glyphIndex, std::int32_t flags, std::int64_t* advance); +int APS5_VABI FT_Get_Advances_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t start, std::uint32_t count, std::int32_t flags, std::int64_t* advances); int APS5_VABI FT_Get_Gasp_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t ppem); int APS5_VABI FT_Get_CID_Registry_Ordering_Supplement_nid_postfix(FreeType::GuestFaceRec* face, const char** registry, const char** ordering, std::int32_t* supplement); int APS5_VABI FT_Get_CID_Is_Internally_CID_Keyed_nid_postfix(FreeType::GuestFaceRec* face, std::uint8_t* isCid); @@ -82,5 +84,16 @@ std::int64_t APS5_VABI FT_Sin_nid_postfix(std::int64_t angle); std::int64_t APS5_VABI FT_Tan_nid_postfix(std::int64_t angle); void APS5_VABI FT_Vector_Unit_nid_postfix(FreeType::GuestVector* vector, std::int64_t angle); std::int64_t APS5_VABI FT_Angle_Diff_nid_postfix(std::int64_t first, std::int64_t second); +int APS5_VABI FT_Stroker_GetCounts_nid_postfix(void* stroker, std::uint32_t* points, std::uint32_t* contours); +int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const FreeType::GuestVector* control1, const FreeType::GuestVector* control2, const FreeType::GuestVector* to); +void APS5_VABI FT_Stroker_Done_nid_postfix(void* stroker); +int APS5_VABI FT_Stroker_GetBorderCounts_nid_postfix(void* stroker, std::int32_t border, std::uint32_t* points, std::uint32_t* contours); +int APS5_VABI FT_Stroker_EndSubPath_nid_postfix(void* stroker); +int APS5_VABI FT_Stroker_LineTo_nid_postfix(void* stroker, const FreeType::GuestVector* to); +int APS5_VABI FT_Stroker_New_nid_postfix(FreeType::GuestLibrary* library, void** stroker); +void APS5_VABI FT_Stroker_Set_nid_postfix(void* stroker, std::int64_t radius, std::int32_t lineCap, std::int32_t lineJoin, std::int64_t miterLimit); +void APS5_VABI FT_Stroker_Rewind_nid_postfix(void* stroker); +int APS5_VABI FT_Stroker_BeginSubPath_nid_postfix(void* stroker, const FreeType::GuestVector* to, std::uint8_t open); +int APS5_VABI FT_Stroker_ConicTo_nid_postfix(void* stroker, const FreeType::GuestVector* control, const FreeType::GuestVector* to); } diff --git a/core/libs/prx/libSceFreeType/src/Advances.cpp b/core/libs/prx/libSceFreeType/src/Advances.cpp new file mode 100644 index 0000000000..a4e1cae36c --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Advances.cpp @@ -0,0 +1,75 @@ +#include "prx/libSceFreeType/libSceFreeType.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/General.hpp" + +#include FT_ADVANCES_H +#include FT_FONT_FORMATS_H +#include FT_TRUETYPE_TABLES_H + +#include +#include +#include + +using namespace FreeType; + +namespace { + +bool FastQueryLoadsGlyph(FT_Face face, std::int32_t flags) { + const char* format = FT_Get_Font_Format(face); + if (!format || std::strcmp(format, "CFF") != 0) return false; + if (!FT_IS_SFNT(face)) return true; + if (flags & FT_LOAD_VERTICAL_LAYOUT) return !FT_HAS_VERTICAL(face); + const auto* header = static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_hhea)); + return !header || header->number_Of_HMetrics == 0; +} + +int GetAdvances(GuestFaceRec* face, std::uint32_t start, std::uint32_t count, std::int32_t flags, std::int64_t* advances, bool single) { + GuestFace* owner = FaceOf(face); + if (!owner) return FT_Err_Invalid_Face_Handle; + if (!advances) return FT_Err_Invalid_Argument; + const auto numGlyphs = static_cast(owner->host->num_glyphs); + const std::uint32_t end = start + count; + if (start >= numGlyphs || end < start || end > numGlyphs) return FT_Err_Invalid_Glyph_Index; + if (count == 0) return 0; + + std::vector values(count); + if constexpr (sizeof(FT_Fixed) == sizeof(std::int64_t)) { + std::memcpy(values.data(), advances, values.size() * sizeof(FT_Fixed)); + } + const auto query = [&](std::int32_t loadFlags) { + return single ? FT_Get_Advance(owner->host, start, loadFlags, values.data()) + : FT_Get_Advances(owner->host, start, count, loadFlags, values.data()); + }; + FT_Error error = query(flags | FT_ADVANCE_FLAG_FAST_ONLY); + bool syncSlot = error != FT_Err_Unimplemented_Feature && FastQueryLoadsGlyph(owner->host, flags); + if (error == FT_Err_Unimplemented_Feature && !(flags & FT_ADVANCE_FLAG_FAST_ONLY)) { + error = query(flags); + syncSlot = true; + } + if (syncSlot) SyncSlot(*owner); + if constexpr (sizeof(FT_Fixed) == sizeof(std::int64_t)) { + std::memcpy(advances, values.data(), values.size() * sizeof(FT_Fixed)); + } else { + if (error != 0 && error != FT_Err_Unimplemented_Feature) { + NotImplemented_nid_no_patch("FreeType advance query: partial output after a native backend error with 32-bit FT_Fixed"); + } + if (error == 0) { + for (std::uint32_t index = 0; index < count; ++index) advances[index] = values[index]; + } + } + return error; +} + +} + +extern "C" { + +int APS5_VABI FT_Get_Advance_nid_postfix(GuestFaceRec* face, std::uint32_t glyphIndex, std::int32_t flags, std::int64_t* advance) { + return GetAdvances(face, glyphIndex, 1, flags, advance, true); +} + +int APS5_VABI FT_Get_Advances_nid_postfix(GuestFaceRec* face, std::uint32_t start, std::uint32_t count, std::int32_t flags, std::int64_t* advances) { + return GetAdvances(face, start, count, flags, advances, false); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Stroker.cpp b/core/libs/prx/libSceFreeType/src/Stroker.cpp new file mode 100644 index 0000000000..ca2fe64191 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Stroker.cpp @@ -0,0 +1,111 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_STROKER_H + +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +FT_Pos HostCoordinate(std::int64_t value, const char* function) { + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + throw std::runtime_error(std::string(function) + ": a value outside the host FT_Pos range is not implemented"); + } + return static_cast(value); +} + +struct NativeVector { + FT_Vector value{}; + FT_Vector* pointer = nullptr; + + NativeVector(const GuestVector* guest, const char* function) { + if (!guest) return; + value = {HostCoordinate(guest->x, function), HostCoordinate(guest->y, function)}; + pointer = &value; + } +}; + +void WriteCount(std::uint32_t* target, FT_UInt value) { + if (!target) return; + GuestArena::HostWrite write(target, sizeof(*target)); + *target = value; +} + +} + +extern "C" { + +int APS5_VABI FT_Stroker_New_nid_postfix(GuestLibrary* library, void** stroker) { + FT_Stroker native = nullptr; + const FT_Error error = FT_Stroker_New(library ? library->host : nullptr, stroker ? &native : nullptr); + if (stroker) *stroker = native; + return error; +} + +void APS5_VABI FT_Stroker_Done_nid_postfix(void* stroker) { + FT_Stroker_Done(static_cast(stroker)); +} + +int APS5_VABI FT_Stroker_GetCounts_nid_postfix(void* stroker, std::uint32_t* points, std::uint32_t* contours) { + FT_UInt nativePoints = 0; + FT_UInt nativeContours = 0; + const FT_Error error = FT_Stroker_GetCounts(static_cast(stroker), points ? &nativePoints : nullptr, contours ? &nativeContours : nullptr); + WriteCount(points, nativePoints); + WriteCount(contours, nativeContours); + return error; +} + +int APS5_VABI FT_Stroker_BeginSubPath_nid_postfix(void* stroker, const GuestVector* to, std::uint8_t open) { + NativeVector vector(to, __func__); + return FT_Stroker_BeginSubPath(static_cast(stroker), vector.pointer, open); +} + +int APS5_VABI FT_Stroker_LineTo_nid_postfix(void* stroker, const GuestVector* to) { + NativeVector vector(to, __func__); + return FT_Stroker_LineTo(static_cast(stroker), vector.pointer); +} + +int APS5_VABI FT_Stroker_EndSubPath_nid_postfix(void* stroker) { + return FT_Stroker_EndSubPath(static_cast(stroker)); +} + +void APS5_VABI FT_Stroker_Set_nid_postfix(void* stroker, std::int64_t radius, std::int32_t lineCap, std::int32_t lineJoin, std::int64_t miterLimit) { + FT_Stroker_Set(static_cast(stroker), HostCoordinate(radius, __func__), static_cast(lineCap), + static_cast(lineJoin), HostCoordinate(miterLimit, __func__)); +} + +void APS5_VABI FT_Stroker_Rewind_nid_postfix(void* stroker) { + FT_Stroker_Rewind(static_cast(stroker)); +} + +int APS5_VABI FT_Stroker_GetBorderCounts_nid_postfix(void* stroker, std::int32_t border, std::uint32_t* points, std::uint32_t* contours) { + FT_UInt nativePoints = 0; + FT_UInt nativeContours = 0; + const FT_Error error = FT_Stroker_GetBorderCounts(static_cast(stroker), static_cast(border), + points ? &nativePoints : nullptr, contours ? &nativeContours : nullptr); + WriteCount(points, nativePoints); + WriteCount(contours, nativeContours); + return error; +} + +int APS5_VABI FT_Stroker_ConicTo_nid_postfix(void* stroker, const GuestVector* control, const GuestVector* to) { + NativeVector nativeControl(control, __func__); + NativeVector nativeTo(to, __func__); + return FT_Stroker_ConicTo(static_cast(stroker), nativeControl.pointer, nativeTo.pointer); +} + +int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const GuestVector* control1, const GuestVector* control2, const GuestVector* to) { + NativeVector first(control1, __func__); + NativeVector second(control2, __func__); + NativeVector end(to, __func__); + return FT_Stroker_CubicTo(static_cast(stroker), first.pointer, second.pointer, end.pointer); +} + +} diff --git a/core/libs/prx/libSceFreeTypeOl/Export.cpp b/core/libs/prx/libSceFreeTypeOl/Export.cpp index d81f4a5e32..68dd03089b 100644 --- a/core/libs/prx/libSceFreeTypeOl/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOl/Export.cpp @@ -5,6 +5,464 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceFreeTypeOlUnknown_mCIXittfvrw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mCIXittfvrw", sceFreeTypeOlUnknown_mCIXittfvrw_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_r2C1PcTDYxk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("r2C1PcTDYxk", sceFreeTypeOlUnknown_r2C1PcTDYxk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_1gAJb1uKyGo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1gAJb1uKyGo", sceFreeTypeOlUnknown_1gAJb1uKyGo_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_IjquEAbfizc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IjquEAbfizc", sceFreeTypeOlUnknown_IjquEAbfizc_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_L_qInx9Dldk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("L-qInx9Dldk", sceFreeTypeOlUnknown_L_qInx9Dldk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_mhK_E1i3VuE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mhK+E1i3VuE", sceFreeTypeOlUnknown_mhK_E1i3VuE_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_bAbUJvsiVdI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bAbUJvsiVdI", sceFreeTypeOlUnknown_bAbUJvsiVdI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_1GYsjUC_HLM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1GYsjUC-HLM", sceFreeTypeOlUnknown_1GYsjUC_HLM_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_KLaWCpRxYWo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KLaWCpRxYWo", sceFreeTypeOlUnknown_KLaWCpRxYWo_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_qodr9TJuFds_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qodr9TJuFds", sceFreeTypeOlUnknown_qodr9TJuFds_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_Ic8JMhUFe88_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Ic8JMhUFe88", sceFreeTypeOlUnknown_Ic8JMhUFe88_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_CIrV8Sq2Uic_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CIrV8Sq2Uic", sceFreeTypeOlUnknown_CIrV8Sq2Uic_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_wbyY8kfNfaI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wbyY8kfNfaI", sceFreeTypeOlUnknown_wbyY8kfNfaI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_eS_4GhL28gs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("eS+4GhL28gs", sceFreeTypeOlUnknown_eS_4GhL28gs_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_XpA3dnvjl8w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XpA3dnvjl8w", sceFreeTypeOlUnknown_XpA3dnvjl8w_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_NLA7bmDS1jc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NLA7bmDS1jc", sceFreeTypeOlUnknown_NLA7bmDS1jc_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_xcJCXHqBrjE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xcJCXHqBrjE", sceFreeTypeOlUnknown_xcJCXHqBrjE_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_oKG_LjaBzzs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("oKG-LjaBzzs", sceFreeTypeOlUnknown_oKG_LjaBzzs_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_zxsyxyiLthM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zxsyxyiLthM", sceFreeTypeOlUnknown_zxsyxyiLthM_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown__v66TBg7tzA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+v66TBg7tzA", sceFreeTypeOlUnknown__v66TBg7tzA_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_ZYpIYZg3Jf4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZYpIYZg3Jf4", sceFreeTypeOlUnknown_ZYpIYZg3Jf4_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_JSi_hWkA9pI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JSi+hWkA9pI", sceFreeTypeOlUnknown_JSi_hWkA9pI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_bZYqFlUVPCY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bZYqFlUVPCY", sceFreeTypeOlUnknown_bZYqFlUVPCY_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_ZhVdSRyrpUI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZhVdSRyrpUI", sceFreeTypeOlUnknown_ZhVdSRyrpUI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_phi4au_2t04_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("phi4au-2t04", sceFreeTypeOlUnknown_phi4au_2t04_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_qC_tiGwcW7E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qC+tiGwcW7E", sceFreeTypeOlUnknown_qC_tiGwcW7E_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_rl7aW17qQb8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rl7aW17qQb8", sceFreeTypeOlUnknown_rl7aW17qQb8_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown__qwk1RxUuDA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-qwk1RxUuDA", sceFreeTypeOlUnknown__qwk1RxUuDA_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_mQYQAi8B0Yo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mQYQAi8B0Yo", sceFreeTypeOlUnknown_mQYQAi8B0Yo_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_4TJ1qJGXAV8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4TJ1qJGXAV8", sceFreeTypeOlUnknown_4TJ1qJGXAV8_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_IewcaRR3F8I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IewcaRR3F8I", sceFreeTypeOlUnknown_IewcaRR3F8I_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_vuuFugcw5ss_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("vuuFugcw5ss", sceFreeTypeOlUnknown_vuuFugcw5ss_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_xaswa_ndZS0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xaswa+ndZS0", sceFreeTypeOlUnknown_xaswa_ndZS0_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_6zDeyzjWz_8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6zDeyzjWz-8", sceFreeTypeOlUnknown_6zDeyzjWz_8_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_5_z1quqcDYo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5-z1quqcDYo", sceFreeTypeOlUnknown_5_z1quqcDYo_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_VVfxa65sWF0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VVfxa65sWF0", sceFreeTypeOlUnknown_VVfxa65sWF0_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown__bRRhPeBqZI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+bRRhPeBqZI", sceFreeTypeOlUnknown__bRRhPeBqZI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_q7kTOFaQrW4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("q7kTOFaQrW4", sceFreeTypeOlUnknown_q7kTOFaQrW4_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_aX8PI_h3_dE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("aX8PI-h3+dE", sceFreeTypeOlUnknown_aX8PI_h3_dE_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_KIKL0rz6o_A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KIKL0rz6o-A", sceFreeTypeOlUnknown_KIKL0rz6o_A_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_QPh_yb2Y8AQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QPh+yb2Y8AQ", sceFreeTypeOlUnknown_QPh_yb2Y8AQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_u36SHyuWQCA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("u36SHyuWQCA", sceFreeTypeOlUnknown_u36SHyuWQCA_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_ZHsmiChQLTk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZHsmiChQLTk", sceFreeTypeOlUnknown_ZHsmiChQLTk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_q2ESpjCIk7w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("q2ESpjCIk7w", sceFreeTypeOlUnknown_q2ESpjCIk7w_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_C0nVvG6_x_s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("C0nVvG6+x-s", sceFreeTypeOlUnknown_C0nVvG6_x_s_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_DIIRFBxRfMQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DIIRFBxRfMQ", sceFreeTypeOlUnknown_DIIRFBxRfMQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_s3qCnsS3ATE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("s3qCnsS3ATE", sceFreeTypeOlUnknown_s3qCnsS3ATE_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_OutzOzZp6_w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OutzOzZp6-w", sceFreeTypeOlUnknown_OutzOzZp6_w_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_E9y7_AmuziU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("E9y7-AmuziU", sceFreeTypeOlUnknown_E9y7_AmuziU_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown__whG27_m_C0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-whG27+m+C0", sceFreeTypeOlUnknown__whG27_m_C0_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_wGGhfabJAbs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wGGhfabJAbs", sceFreeTypeOlUnknown_wGGhfabJAbs_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_KuJkc0qDvOQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KuJkc0qDvOQ", sceFreeTypeOlUnknown_KuJkc0qDvOQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_jqMurvieLBk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jqMurvieLBk", sceFreeTypeOlUnknown_jqMurvieLBk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_z9YQIyuTMV4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("z9YQIyuTMV4", sceFreeTypeOlUnknown_z9YQIyuTMV4_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_SKUldaN3e2A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SKUldaN3e2A", sceFreeTypeOlUnknown_SKUldaN3e2A_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_50HTRXvbW_k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("50HTRXvbW-k", sceFreeTypeOlUnknown_50HTRXvbW_k_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_M5dtVv0ln5Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("M5dtVv0ln5Q", sceFreeTypeOlUnknown_M5dtVv0ln5Q_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_xtb9fOR6ilA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xtb9fOR6ilA", sceFreeTypeOlUnknown_xtb9fOR6ilA_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_6z2P2V2XCiI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6z2P2V2XCiI", sceFreeTypeOlUnknown_6z2P2V2XCiI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_MJVrwM6aTTk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("MJVrwM6aTTk", sceFreeTypeOlUnknown_MJVrwM6aTTk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_EveUtaCYQgk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EveUtaCYQgk", sceFreeTypeOlUnknown_EveUtaCYQgk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_B7Sw_iUs7e4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("B7Sw-iUs7e4", sceFreeTypeOlUnknown_B7Sw_iUs7e4_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_RM21Fgm0ZvQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RM21Fgm0ZvQ", sceFreeTypeOlUnknown_RM21Fgm0ZvQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_OmxJF0FvwWc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OmxJF0FvwWc", sceFreeTypeOlUnknown_OmxJF0FvwWc_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_4to3tThESn0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4to3tThESn0", sceFreeTypeOlUnknown_4to3tThESn0_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_JuzrPZClLew_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JuzrPZClLew", sceFreeTypeOlUnknown_JuzrPZClLew_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_sylevnx_fqk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sylevnx-fqk", sceFreeTypeOlUnknown_sylevnx_fqk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_0RSuGozMl9o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0RSuGozMl9o", sceFreeTypeOlUnknown_0RSuGozMl9o_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_J3HnvW8FJV8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("J3HnvW8FJV8", sceFreeTypeOlUnknown_J3HnvW8FJV8_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_2D3H_1nhwqQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2D3H+1nhwqQ", sceFreeTypeOlUnknown_2D3H_1nhwqQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_upeKra1FaQk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("upeKra1FaQk", sceFreeTypeOlUnknown_upeKra1FaQk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_YpxR8CqGMIM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("YpxR8CqGMIM", sceFreeTypeOlUnknown_YpxR8CqGMIM_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_83qtpn8_XnA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("83qtpn8+XnA", sceFreeTypeOlUnknown_83qtpn8_XnA_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_g2p7fkJpuC0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("g2p7fkJpuC0", sceFreeTypeOlUnknown_g2p7fkJpuC0_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_xqjAbn8VNGo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xqjAbn8VNGo", sceFreeTypeOlUnknown_xqjAbn8VNGo_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_Yu7mmGCjSYM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Yu7mmGCjSYM", sceFreeTypeOlUnknown_Yu7mmGCjSYM_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_SDPzjFN7y4E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SDPzjFN7y4E", sceFreeTypeOlUnknown_SDPzjFN7y4E_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_oM0zMNCfXKM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("oM0zMNCfXKM", sceFreeTypeOlUnknown_oM0zMNCfXKM_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_3dTNNBoujn0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3dTNNBoujn0", sceFreeTypeOlUnknown_3dTNNBoujn0_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_Tzm_z36UYKg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Tzm-z36UYKg", sceFreeTypeOlUnknown_Tzm_z36UYKg_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_b7S96aD8dkE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("b7S96aD8dkE", sceFreeTypeOlUnknown_b7S96aD8dkE_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_M94SjXo_UVA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("M94SjXo-UVA", sceFreeTypeOlUnknown_M94SjXo_UVA_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_Ns9a_LjOcDw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Ns9a-LjOcDw", sceFreeTypeOlUnknown_Ns9a_LjOcDw_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_LonHHN98tMI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LonHHN98tMI", sceFreeTypeOlUnknown_LonHHN98tMI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_Uw_BZJpflxk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Uw+BZJpflxk", sceFreeTypeOlUnknown_Uw_BZJpflxk_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_ZqvTX8u1bEc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZqvTX8u1bEc", sceFreeTypeOlUnknown_ZqvTX8u1bEc_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_h0n4Fb9j2R8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("h0n4Fb9j2R8", sceFreeTypeOlUnknown_h0n4Fb9j2R8_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_wLqQQWyOdpc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wLqQQWyOdpc", sceFreeTypeOlUnknown_wLqQQWyOdpc_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_oJm1lThZ3iQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("oJm1lThZ3iQ", sceFreeTypeOlUnknown_oJm1lThZ3iQ_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_UTumVjeIQtg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UTumVjeIQtg", sceFreeTypeOlUnknown_UTumVjeIQtg_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_fHMsRluEMfI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fHMsRluEMfI", sceFreeTypeOlUnknown_fHMsRluEMfI_nid_no_patch); + +int APS5_VABI sceFreeTypeOlUnknown_5NYsP564wwY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5NYsP564wwY", sceFreeTypeOlUnknown_5NYsP564wwY_nid_no_patch); } diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index e17ac66491..ca6a6a1164 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -547,30 +547,30 @@ int APS5_VABI sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch() { } APS5_EXPORT("Az4H9FlfxCA", sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerBeginSubPath_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("xj3tNJptfLI", sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch); +APS5_EXPORT("xj3tNJptfLI", sceFreeTypeOtStrokerBeginSubPath_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerConicTo_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("TPKhRt1QIeE", sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch); +APS5_EXPORT("TPKhRt1QIeE", sceFreeTypeOtStrokerConicTo_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerCubicTo_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("6+yXRlVrjbc", sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch); +APS5_EXPORT("6+yXRlVrjbc", sceFreeTypeOtStrokerCubicTo_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerDone_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("+RLa-6fbshA", sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch); +APS5_EXPORT("+RLa-6fbshA", sceFreeTypeOtStrokerDone_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerEndSubPath_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch); +APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtStrokerEndSubPath_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -582,40 +582,40 @@ int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() { } APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerGetBorderCounts_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("LfyUi-LkdCk", sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch); +APS5_EXPORT("LfyUi-LkdCk", sceFreeTypeOtStrokerGetBorderCounts_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerGetCounts_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("rzIWh7FJnmU", sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch); +APS5_EXPORT("rzIWh7FJnmU", sceFreeTypeOtStrokerGetCounts_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerLineTo_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("cKndiZnt0EA", sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch); +APS5_EXPORT("cKndiZnt0EA", sceFreeTypeOtStrokerLineTo_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerNew_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch); +APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtStrokerNew_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerRewind_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Po8kyd3DlIw", sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch); +APS5_EXPORT("Po8kyd3DlIw", sceFreeTypeOtStrokerRewind_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerSet_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch); +APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtStrokerSet_nid_no_patch); int APS5_VABI sceFreeTypeOtTan_nid_no_patch() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp b/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp index b09d2a8a87..d3f9dffc0e 100644 --- a/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp +++ b/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp @@ -154,7 +154,7 @@ int APS5_VABI sceGnmDestroyWorkloadStream(void) { return 0; } -int APS5_VABI sceGnmDingDongForWorkload(void) { +int APS5_VABI sceGnmDingDongForWorkload(std::uint32_t vqueueId, std::uint32_t nextOffsetDw, std::uint64_t workloadId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -329,7 +329,7 @@ int APS5_VABI sceGnmDrawInitToDefaultContextState400(std::uint32_t* cmdbuf, std: return WriteContextState(cmdbuf, size, 0x100, GnmNeo::ContextState400); } -int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalCommand(void) { +int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalCommand(std::uint32_t* cmdbuf, std::uint32_t size) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -552,7 +552,7 @@ int APS5_VABI sceGnmRegisterGnmLiveCallbackConfig(void) { return 0; } -int APS5_VABI sceGnmRegisterOwner(void) { +int APS5_VABI sceGnmRegisterOwner(void* handle, const char* name) { return Failure; } diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp b/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp index f9ecf8db4c..eb8f0bfa36 100644 --- a/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp +++ b/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp @@ -11,7 +11,7 @@ int APS5_VABI sceGnmCreateWorkloadStream(std::uint64_t name, std::uint32_t* stre int APS5_VABI sceGnmDebugModuleReset(void); int APS5_VABI sceGnmDebugReset(void); int APS5_VABI sceGnmDestroyWorkloadStream(void); -int APS5_VABI sceGnmDingDongForWorkload(void); +int APS5_VABI sceGnmDingDongForWorkload(std::uint32_t vqueueId, std::uint32_t nextOffsetDw, std::uint64_t workloadId); int APS5_VABI sceGnmDispatchDirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t threadsX, std::uint32_t threadsY, std::uint32_t threadsZ, std::uint32_t flags); int APS5_VABI sceGnmDispatchIndirect(std::uint32_t* cmdbuf, std::uint32_t size, std::uint32_t dataOffset, std::uint32_t flags); int APS5_VABI sceGnmDispatchIndirectOnMec(std::uint32_t* cmdbuf, std::uint32_t size, std::uint64_t args, std::uint32_t modifier); @@ -30,7 +30,7 @@ int APS5_VABI sceGnmDrawInitDefaultHardwareState200(std::uint32_t* cmdbuf, std:: int APS5_VABI sceGnmDrawInitDefaultHardwareState350(std::uint32_t* cmdbuf, std::uint32_t size); int APS5_VABI sceGnmDrawInitToDefaultContextState(std::uint32_t* cmdbuf, std::uint32_t size); int APS5_VABI sceGnmDrawInitToDefaultContextState400(std::uint32_t* cmdbuf, std::uint32_t size); -int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalCommand(void); +int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalCommand(std::uint32_t* cmdbuf, std::uint32_t size); int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalSize(void); int APS5_VABI sceGnmDrawOpaqueAuto(void); int APS5_VABI sceGnmDriverInternalRetrieveGnmInterface(void); @@ -78,7 +78,7 @@ int APS5_VABI sceGnmQueryResourceRegistrationUserMemoryRequirements(void); int APS5_VABI sceGnmRaiseUserExceptionEvent(void); int APS5_VABI sceGnmRegisterGdsResource(void); int APS5_VABI sceGnmRegisterGnmLiveCallbackConfig(void); -int APS5_VABI sceGnmRegisterOwner(void); +int APS5_VABI sceGnmRegisterOwner(void* handle, const char* name); int APS5_VABI sceGnmRegisterOwnerForSystem(void); int APS5_VABI sceGnmRegisterResource(void); int APS5_VABI sceGnmRequestFlipAndSubmitDoneForWorkload(void); diff --git a/core/libs/prx/libSceHttp2/CMakeLists.txt b/core/libs/prx/libSceHttp2/CMakeLists.txt index 94c68154da..fd767bc0f8 100644 --- a/core/libs/prx/libSceHttp2/CMakeLists.txt +++ b/core/libs/prx/libSceHttp2/CMakeLists.txt @@ -22,7 +22,7 @@ set_target_properties(anyps5_http2_transport PROPERTIES POSITION_INDEPENDENT_COD if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_options(anyps5_http2_transport INTERFACE LINKER:--exclude-libs,libanyps5_http2_transport.a) endif() -add_library(libSceHttp2 SHARED EXCLUDE_FROM_ALL Export.cpp Cookies.cpp) +add_library(libSceHttp2 SHARED EXCLUDE_FROM_ALL Export.cpp Cookies.cpp InventoryExports.cpp) target_include_directories(libSceHttp2 PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceHttp2 PRIVATE libc libkernel anyps5_http2_transport) if(WIN32) @@ -52,12 +52,29 @@ if(BUILD_TESTING) configure_windows_unwind(guest_http2_transport_tests) add_test(NAME guest_http2_transport COMMAND guest_http2_transport_tests) add_test(NAME guest_http2_early_response COMMAND guest_http2_transport_tests --early) - set_tests_properties(guest_http2_transport guest_http2_early_response PROPERTIES TIMEOUT 30) + set(http2SystemRootsRun ${CMAKE_CURRENT_BINARY_DIR}/guest_http2_system_roots) + file(MAKE_DIRECTORY ${http2SystemRootsRun}) + add_test(NAME guest_http2_system_roots COMMAND guest_http2_transport_tests --system-roots WORKING_DIRECTORY ${http2SystemRootsRun}) + set_tests_properties(guest_http2_transport guest_http2_early_response guest_http2_system_roots PROPERTIES TIMEOUT 30) if(WIN32) target_link_libraries(guest_http2_transport_tests PRIVATE ws2_32 bcrypt) add_test(NAME guest_http2_dns_first COMMAND guest_http2_transport_tests --dns-first) set_tests_properties(guest_http2_dns_first PROPERTIES TIMEOUT 30) - set_tests_properties(guest_http2 guest_http2_cookies guest_http2_options guest_http2_transport guest_http2_early_response guest_http2_dns_first PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_http2 guest_http2_cookies guest_http2_options guest_http2_transport guest_http2_early_response guest_http2_dns_first guest_http2_system_roots PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_http2_teardown_tests ${LIBS_INCLUDE_DIR}/tests/Http2TeardownFailure.cpp Export.cpp Cookies.cpp) + target_include_directories(guest_http2_teardown_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_http2_teardown_tests PRIVATE libkernel libc anyps5_http2_transport) + configure_windows_unwind(guest_http2_teardown_tests) + add_test(NAME guest_http2_teardown COMMAND guest_http2_teardown_tests) + set_tests_properties(guest_http2_teardown PROPERTIES TIMEOUT 30) + if(WIN32) + target_link_libraries(guest_http2_teardown_tests PRIVATE ws2_32) + set_tests_properties(guest_http2_teardown PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceHttp2/InventoryExports.cpp b/core/libs/prx/libSceHttp2/InventoryExports.cpp new file mode 100644 index 0000000000..f39f53289e --- /dev/null +++ b/core/libs/prx/libSceHttp2/InventoryExports.cpp @@ -0,0 +1,38 @@ +#include "prx/libSceHttp2/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceHttp2GetAllTrailingHeaders() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2SetProxyWithURL() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2SetRequestServerName() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2SetStripGZIPHeadersEnabled() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2WebSocketClose() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2WebSocketSendDataMessage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2WebSocketSendTextMessage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceHttp2WebSocketSetCloseTimeout() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceHttp2/Transport.cpp b/core/libs/prx/libSceHttp2/Transport.cpp index ab7389458f..a8a9ac6b71 100644 --- a/core/libs/prx/libSceHttp2/Transport.cpp +++ b/core/libs/prx/libSceHttp2/Transport.cpp @@ -27,6 +27,7 @@ #include extern "C" int SslRead_nid_no_patch(int, void*, std::size_t, int); +extern "C" int SslTrustSystemRoots_nid_no_patch(int); namespace Http2 { namespace { @@ -359,6 +360,7 @@ struct Request::State { if (result < 0) return result; ssl = sceSslCreateConnection(sslContext, socket, url.host.c_str()); Require(ssl > 0, "sceHttp2: SSL context or connection unavailable"); + Require(SslTrustSystemRoots_nid_no_patch(ssl) == 0, "sceHttp2: system CA list unavailable"); Require(sceSslDisableVerifyOption(ssl, 0x3d) == 0 && sceSslEnableVerifyOption(ssl, settings.verifyFlags) == 0, "sceHttp2: SSL verification options rejected"); Require(sceSslSetAlpn(ssl, "h2") == 0, "sceHttp2: ALPN setup failed"); diff --git a/core/libs/prx/libSceHttp2/include/InventoryExports.hpp b/core/libs/prx/libSceHttp2/include/InventoryExports.hpp new file mode 100644 index 0000000000..7666182edb --- /dev/null +++ b/core/libs/prx/libSceHttp2/include/InventoryExports.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceHttp2GetAllTrailingHeaders(); +int APS5_VABI sceHttp2SetProxyWithURL(); +int APS5_VABI sceHttp2SetRequestServerName(); +int APS5_VABI sceHttp2SetStripGZIPHeadersEnabled(); +int APS5_VABI sceHttp2WebSocketClose(); +int APS5_VABI sceHttp2WebSocketSendDataMessage(); +int APS5_VABI sceHttp2WebSocketSendTextMessage(); +int APS5_VABI sceHttp2WebSocketSetCloseTimeout(); + +} diff --git a/core/libs/prx/libSceHttp2/libSceHttp2.hpp b/core/libs/prx/libSceHttp2/libSceHttp2.hpp index d6ec538a09..68e4c08f71 100644 --- a/core/libs/prx/libSceHttp2/libSceHttp2.hpp +++ b/core/libs/prx/libSceHttp2/libSceHttp2.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceHttp2/include/Http2.hpp" +#include "prx/libSceHttp2/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index 14fb9a0761..f7f80b1cae 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -1,6 +1,20 @@ add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Export.cpp Invariant.cpp Version.cpp Unescape.cpp) target_include_directories(libSceIcu PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceIcu) +target_sources(libSceIcu PRIVATE CString.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_icu_cstring_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuCString.cpp) + target_include_directories(guest_icu_cstring_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_cstring_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_cstring_tests) + add_test(NAME guest_icu_cstring COMMAND guest_icu_cstring_tests) + set_tests_properties(guest_icu_cstring PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_icu_cstring PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() if(BUILD_TESTING) add_test_executable(guest_icu_version_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuVersion.cpp) diff --git a/core/libs/prx/libSceIcu/CString.cpp b/core/libs/prx/libSceIcu/CString.cpp new file mode 100644 index 0000000000..386e443fc8 --- /dev/null +++ b/core/libs/prx/libSceIcu/CString.cpp @@ -0,0 +1,68 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include + +namespace { + +template +std::int32_t FormatInteger(char* buffer, TValue value, std::uint32_t radix) { + using TUnsigned = std::make_unsigned_t; + auto magnitude = static_cast(value); + const bool negative = value < 0 && radix == 10; + std::int32_t length = 0; + if (negative) { + magnitude = TUnsigned{0} - magnitude; + buffer[length++] = '-'; + } + const std::int32_t firstDigit = length; + do { + buffer[length++] = "0123456789ABCDEF"[magnitude % radix]; + magnitude /= radix; + } while (magnitude); + std::reverse(buffer + firstDigit, buffer + length); + buffer[length] = 0; + return length; +} + +char* ConvertCase(char* text, char first, char last, int offset) { + if (!text) return nullptr; + for (char* cursor = text;; ++cursor) { + const char value = *cursor; + *cursor = value >= first && value <= last ? static_cast(value + offset) : value; + if (!value) return text; + } +} + +} + +extern "C" { + +std::int32_t APS5_VABI T_CString_integerToString_nid_postfix(char* buffer, std::int32_t value, std::int32_t radix) { + if (!buffer || radix < 2 || radix > 16) APS5_INVALID_ARG_EX; + return FormatInteger(buffer, value, static_cast(radix)); +} + +std::int32_t APS5_VABI T_CString_int64ToString_nid_postfix(char* buffer, std::int64_t value, std::uint32_t radix) { + if (!buffer || radix < 2 || radix > 16) APS5_INVALID_ARG_EX; + return FormatInteger(buffer, value, radix); +} + +std::int32_t APS5_VABI T_CString_stringToInteger_nid_postfix(const char* text, std::int32_t radix) { + if (!text) APS5_INVALID_ARG_EX; + const auto value = static_cast(strtoul_nid_postfix(text, nullptr, radix)); + return std::bit_cast(value); +} + +char* APS5_VABI T_CString_toLowerCase_nid_postfix(char* text) { + return ConvertCase(text, 'A', 'Z', 'a' - 'A'); +} + +char* APS5_VABI T_CString_toUpperCase_nid_postfix(char* text) { + return ConvertCase(text, 'a', 'z', 'A' - 'a'); +} + +} diff --git a/core/libs/prx/libSceIcu/CString.hpp b/core/libs/prx/libSceIcu/CString.hpp new file mode 100644 index 0000000000..ce1984aaae --- /dev/null +++ b/core/libs/prx/libSceIcu/CString.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +std::int32_t APS5_VABI T_CString_integerToString_nid_postfix(char* buffer, std::int32_t value, std::int32_t radix); +std::int32_t APS5_VABI T_CString_int64ToString_nid_postfix(char* buffer, std::int64_t value, std::uint32_t radix); +std::int32_t APS5_VABI T_CString_stringToInteger_nid_postfix(const char* text, std::int32_t radix); +char* APS5_VABI T_CString_toLowerCase_nid_postfix(char* text); +char* APS5_VABI T_CString_toUpperCase_nid_postfix(char* text); +} diff --git a/core/libs/prx/libSceIcu/Export.cpp b/core/libs/prx/libSceIcu/Export.cpp index d81f4a5e32..1b37f59c57 100644 --- a/core/libs/prx/libSceIcu/Export.cpp +++ b/core/libs/prx/libSceIcu/Export.cpp @@ -5,6 +5,540 @@ extern "C" { -APS5_DUMMY_FUN + +int APS5_VABI sceIcuUnknown_VZFw_xSe6Mo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VZFw+xSe6Mo", sceIcuUnknown_VZFw_xSe6Mo_nid_no_patch); + +int APS5_VABI sceIcuUnknown_uKBkvN84hbw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uKBkvN84hbw", sceIcuUnknown_uKBkvN84hbw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_JKRf_2yrIaE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JKRf+2yrIaE", sceIcuUnknown_JKRf_2yrIaE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_4Gv3tXZ2WKI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4Gv3tXZ2WKI", sceIcuUnknown_4Gv3tXZ2WKI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_RFnKlYsiIb0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RFnKlYsiIb0", sceIcuUnknown_RFnKlYsiIb0_nid_no_patch); + +int APS5_VABI sceIcuUnknown_BZY5mpL0Syk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BZY5mpL0Syk", sceIcuUnknown_BZY5mpL0Syk_nid_no_patch); + +int APS5_VABI sceIcuUnknown_WLoOrEfsob4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WLoOrEfsob4", sceIcuUnknown_WLoOrEfsob4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_H33ge_SDjlI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("H33ge+SDjlI", sceIcuUnknown_H33ge_SDjlI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_wlK146pHnQ4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wlK146pHnQ4", sceIcuUnknown_wlK146pHnQ4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_QO5iuHMTu2o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QO5iuHMTu2o", sceIcuUnknown_QO5iuHMTu2o_nid_no_patch); + +int APS5_VABI sceIcuUnknown_9TndABWg5Ds_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("9TndABWg5Ds", sceIcuUnknown_9TndABWg5Ds_nid_no_patch); + +int APS5_VABI sceIcuUnknown_tcf9HUwNaX0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tcf9HUwNaX0", sceIcuUnknown_tcf9HUwNaX0_nid_no_patch); + +int APS5_VABI sceIcuUnknown_rbyBjvpNuME_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rbyBjvpNuME", sceIcuUnknown_rbyBjvpNuME_nid_no_patch); + +int APS5_VABI sceIcuUnknown_4JJDz6r_ghE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4JJDz6r-ghE", sceIcuUnknown_4JJDz6r_ghE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_WPrktCwd_iw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WPrktCwd+iw", sceIcuUnknown_WPrktCwd_iw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_sP5rfVNdvWM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sP5rfVNdvWM", sceIcuUnknown_sP5rfVNdvWM_nid_no_patch); + +int APS5_VABI sceIcuUnknown_c6eD2eDESGk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c6eD2eDESGk", sceIcuUnknown_c6eD2eDESGk_nid_no_patch); + +int APS5_VABI sceIcuUnknown_8y7DjDBJ6Nw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8y7DjDBJ6Nw", sceIcuUnknown_8y7DjDBJ6Nw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_b7p2qTtZPno_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("b7p2qTtZPno", sceIcuUnknown_b7p2qTtZPno_nid_no_patch); + +int APS5_VABI sceIcuUnknown_RlZwfEuRDx8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RlZwfEuRDx8", sceIcuUnknown_RlZwfEuRDx8_nid_no_patch); + +int APS5_VABI sceIcuUnknown__xsbcqOIJmM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+xsbcqOIJmM", sceIcuUnknown__xsbcqOIJmM_nid_no_patch); + +int APS5_VABI sceIcuUnknown_od0otGJ2Z3A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("od0otGJ2Z3A", sceIcuUnknown_od0otGJ2Z3A_nid_no_patch); + +int APS5_VABI sceIcuUnknown_vXu991uXNUo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("vXu991uXNUo", sceIcuUnknown_vXu991uXNUo_nid_no_patch); + +int APS5_VABI sceIcuUnknown_aF_oEFPzEsY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("aF+oEFPzEsY", sceIcuUnknown_aF_oEFPzEsY_nid_no_patch); + +int APS5_VABI sceIcuUnknown_lNPpoUVElSA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lNPpoUVElSA", sceIcuUnknown_lNPpoUVElSA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_BTFjoSWI5_Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BTFjoSWI5-Y", sceIcuUnknown_BTFjoSWI5_Y_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Q8P3U9F8p_Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Q8P3U9F8p-Y", sceIcuUnknown_Q8P3U9F8p_Y_nid_no_patch); + +int APS5_VABI sceIcuUnknown_4Kf0Hy8xJrI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4Kf0Hy8xJrI", sceIcuUnknown_4Kf0Hy8xJrI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_JGkExZI3fFI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JGkExZI3fFI", sceIcuUnknown_JGkExZI3fFI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_0cYEtBUJ5zQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0cYEtBUJ5zQ", sceIcuUnknown_0cYEtBUJ5zQ_nid_no_patch); + +int APS5_VABI sceIcuUnknown_1SsridDRBYQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1SsridDRBYQ", sceIcuUnknown_1SsridDRBYQ_nid_no_patch); + +int APS5_VABI sceIcuUnknown_a25spvWYEOA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("a25spvWYEOA", sceIcuUnknown_a25spvWYEOA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_EuFvqF73SwU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EuFvqF73SwU", sceIcuUnknown_EuFvqF73SwU_nid_no_patch); + +int APS5_VABI sceIcuUnknown_wybPkXuWnoE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wybPkXuWnoE", sceIcuUnknown_wybPkXuWnoE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_2qAOcgRJZ3M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2qAOcgRJZ3M", sceIcuUnknown_2qAOcgRJZ3M_nid_no_patch); + +int APS5_VABI sceIcuUnknown_n4yiKNo1yCs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("n4yiKNo1yCs", sceIcuUnknown_n4yiKNo1yCs_nid_no_patch); + +int APS5_VABI sceIcuUnknown_hI8JVrcGWhc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hI8JVrcGWhc", sceIcuUnknown_hI8JVrcGWhc_nid_no_patch); + +int APS5_VABI sceIcuUnknown_h16q7HjVyTE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("h16q7HjVyTE", sceIcuUnknown_h16q7HjVyTE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Q8Y0ntVp_GY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Q8Y0ntVp-GY", sceIcuUnknown_Q8Y0ntVp_GY_nid_no_patch); + +int APS5_VABI sceIcuUnknown_TXEHKpCJ_aE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("TXEHKpCJ-aE", sceIcuUnknown_TXEHKpCJ_aE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_UmNS0P0_ovs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UmNS0P0-ovs", sceIcuUnknown_UmNS0P0_ovs_nid_no_patch); + +int APS5_VABI sceIcuUnknown_ndOe3Wc7xjA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ndOe3Wc7xjA", sceIcuUnknown_ndOe3Wc7xjA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_r6Wuu1Tc438_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("r6Wuu1Tc438", sceIcuUnknown_r6Wuu1Tc438_nid_no_patch); + +int APS5_VABI sceIcuUnknown__kd2qcTMvHE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+kd2qcTMvHE", sceIcuUnknown__kd2qcTMvHE_nid_no_patch); + +int APS5_VABI sceIcuUnknown__aK6d418l4c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-aK6d418l4c", sceIcuUnknown__aK6d418l4c_nid_no_patch); + +int APS5_VABI sceIcuUnknown_5V2WIPOZcaA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5V2WIPOZcaA", sceIcuUnknown_5V2WIPOZcaA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_FHSNfxQFEdA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FHSNfxQFEdA", sceIcuUnknown_FHSNfxQFEdA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_eD76mHY99fM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("eD76mHY99fM", sceIcuUnknown_eD76mHY99fM_nid_no_patch); + +int APS5_VABI sceIcuUnknown_F_2mXPmh_7s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("F+2mXPmh-7s", sceIcuUnknown_F_2mXPmh_7s_nid_no_patch); + +int APS5_VABI sceIcuUnknown_BQtUj7CVrC0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BQtUj7CVrC0", sceIcuUnknown_BQtUj7CVrC0_nid_no_patch); + +int APS5_VABI sceIcuUnknown_pCh4kIKMRhY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pCh4kIKMRhY", sceIcuUnknown_pCh4kIKMRhY_nid_no_patch); + +int APS5_VABI sceIcuUnknown_3Z22ZB8mHq8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3Z22ZB8mHq8", sceIcuUnknown_3Z22ZB8mHq8_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Hka9gz6FuDs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Hka9gz6FuDs", sceIcuUnknown_Hka9gz6FuDs_nid_no_patch); + +int APS5_VABI sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5+-ZFSRQ0+k", sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch); + +int APS5_VABI sceIcuUnknown_zGuhKwXM9I0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zGuhKwXM9I0", sceIcuUnknown_zGuhKwXM9I0_nid_no_patch); + +int APS5_VABI sceIcuUnknown_6y_NITWLmmE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6y+NITWLmmE", sceIcuUnknown_6y_NITWLmmE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_qXSNbAnlsKg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qXSNbAnlsKg", sceIcuUnknown_qXSNbAnlsKg_nid_no_patch); + +int APS5_VABI sceIcuUnknown_u8WyPl4r3Oc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("u8WyPl4r3Oc", sceIcuUnknown_u8WyPl4r3Oc_nid_no_patch); + +int APS5_VABI sceIcuUnknown_7E_h3yiZjKw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("7E+h3yiZjKw", sceIcuUnknown_7E_h3yiZjKw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_dzbKmf3Ycog_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dzbKmf3Ycog", sceIcuUnknown_dzbKmf3Ycog_nid_no_patch); + +int APS5_VABI sceIcuUnknown_AvrA9K2lvC4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AvrA9K2lvC4", sceIcuUnknown_AvrA9K2lvC4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_58CIUaViW_k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("58CIUaViW+k", sceIcuUnknown_58CIUaViW_k_nid_no_patch); + +int APS5_VABI sceIcuUnknown_mY5RbYVLzr4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mY5RbYVLzr4", sceIcuUnknown_mY5RbYVLzr4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_1GkIKiuqgBg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1GkIKiuqgBg", sceIcuUnknown_1GkIKiuqgBg_nid_no_patch); + +int APS5_VABI sceIcuUnknown_qGIX2VTcNJo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qGIX2VTcNJo", sceIcuUnknown_qGIX2VTcNJo_nid_no_patch); + +int APS5_VABI sceIcuUnknown_OJ_13SCOxgI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OJ+13SCOxgI", sceIcuUnknown_OJ_13SCOxgI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_wxirhbbuv84_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wxirhbbuv84", sceIcuUnknown_wxirhbbuv84_nid_no_patch); + +int APS5_VABI sceIcuUnknown_U089pStD8Bw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("U089pStD8Bw", sceIcuUnknown_U089pStD8Bw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_O3zT1gO1ZNM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("O3zT1gO1ZNM", sceIcuUnknown_O3zT1gO1ZNM_nid_no_patch); + +int APS5_VABI sceIcuUnknown__L1K53Ku3mE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+L1K53Ku3mE", sceIcuUnknown__L1K53Ku3mE_nid_no_patch); + +int APS5_VABI sceIcuUnknown_LtBBDJEZXhY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LtBBDJEZXhY", sceIcuUnknown_LtBBDJEZXhY_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Sv7TbVAEqeQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Sv7TbVAEqeQ", sceIcuUnknown_Sv7TbVAEqeQ_nid_no_patch); + +int APS5_VABI sceIcuUnknown_twrAIl4N_EM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("twrAIl4N+EM", sceIcuUnknown_twrAIl4N_EM_nid_no_patch); + +int APS5_VABI sceIcuUnknown_n4o8mgXtF5s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("n4o8mgXtF5s", sceIcuUnknown_n4o8mgXtF5s_nid_no_patch); + +int APS5_VABI sceIcuUnknown__P5TDFU2_fc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+P5TDFU2-fc", sceIcuUnknown__P5TDFU2_fc_nid_no_patch); + +int APS5_VABI sceIcuUnknown_XVQ3cf0BdQ0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XVQ3cf0BdQ0", sceIcuUnknown_XVQ3cf0BdQ0_nid_no_patch); + +int APS5_VABI sceIcuUnknown_HneyXNQpPhQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HneyXNQpPhQ", sceIcuUnknown_HneyXNQpPhQ_nid_no_patch); + +int APS5_VABI sceIcuUnknown_bgnHFMqxYag_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bgnHFMqxYag", sceIcuUnknown_bgnHFMqxYag_nid_no_patch); + +int APS5_VABI sceIcuUnknown_LXxEsOsIa5g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LXxEsOsIa5g", sceIcuUnknown_LXxEsOsIa5g_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Z5_W18jAIH4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z5+W18jAIH4", sceIcuUnknown_Z5_W18jAIH4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_tcSjvPP2_4s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tcSjvPP2-4s", sceIcuUnknown_tcSjvPP2_4s_nid_no_patch); + +int APS5_VABI sceIcuUnknown_BhCcIlyHMrk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BhCcIlyHMrk", sceIcuUnknown_BhCcIlyHMrk_nid_no_patch); + +int APS5_VABI sceIcuUnknown_S0WDjcZLdqI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("S0WDjcZLdqI", sceIcuUnknown_S0WDjcZLdqI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_udAPjod3tpU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("udAPjod3tpU", sceIcuUnknown_udAPjod3tpU_nid_no_patch); + +int APS5_VABI sceIcuUnknown_CfP4Q2Yjm1U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CfP4Q2Yjm1U", sceIcuUnknown_CfP4Q2Yjm1U_nid_no_patch); + +int APS5_VABI sceIcuUnknown_sQ0avkeBLo4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sQ0avkeBLo4", sceIcuUnknown_sQ0avkeBLo4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_yVobQd4t_iA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yVobQd4t+iA", sceIcuUnknown_yVobQd4t_iA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_FNfP9dKTd5Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FNfP9dKTd5Q", sceIcuUnknown_FNfP9dKTd5Q_nid_no_patch); + +int APS5_VABI sceIcuUnknown_TU6Yl8_mT3c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("TU6Yl8+mT3c", sceIcuUnknown_TU6Yl8_mT3c_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Wm0y_9IRE8I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Wm0y-9IRE8I", sceIcuUnknown_Wm0y_9IRE8I_nid_no_patch); + +int APS5_VABI sceIcuUnknown_xAo5lvA_W3o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xAo5lvA+W3o", sceIcuUnknown_xAo5lvA_W3o_nid_no_patch); + +int APS5_VABI sceIcuUnknown_KzcrFzKpYE8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KzcrFzKpYE8", sceIcuUnknown_KzcrFzKpYE8_nid_no_patch); + +int APS5_VABI sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pgvyZZ+d1ck", sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch); + +int APS5_VABI sceIcuUnknown_TNZrG2iXikw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("TNZrG2iXikw", sceIcuUnknown_TNZrG2iXikw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_Z5ljxAg3thw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z5ljxAg3thw", sceIcuUnknown_Z5ljxAg3thw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_OAFz3KoG_gA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OAFz3KoG-gA", sceIcuUnknown_OAFz3KoG_gA_nid_no_patch); + +int APS5_VABI sceIcuUnknown_6_NsFXHpWXc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6+NsFXHpWXc", sceIcuUnknown_6_NsFXHpWXc_nid_no_patch); + +int APS5_VABI sceIcuUnknown_q3mX36AQeC4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("q3mX36AQeC4", sceIcuUnknown_q3mX36AQeC4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_RIcNJWDYKhM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RIcNJWDYKhM", sceIcuUnknown_RIcNJWDYKhM_nid_no_patch); + +int APS5_VABI sceIcuUnknown_4FrZfvvYHCI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4FrZfvvYHCI", sceIcuUnknown_4FrZfvvYHCI_nid_no_patch); + +int APS5_VABI sceIcuUnknown_ZxnyBy8reV4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZxnyBy8reV4", sceIcuUnknown_ZxnyBy8reV4_nid_no_patch); + +int APS5_VABI sceIcuUnknown_X7gbPHVoNEw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("X7gbPHVoNEw", sceIcuUnknown_X7gbPHVoNEw_nid_no_patch); + +int APS5_VABI sceIcuUnknown_aXUj3BT58vY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("aXUj3BT58vY", sceIcuUnknown_aXUj3BT58vY_nid_no_patch); + +int APS5_VABI sceIcuUnknown_FCx2DacLDWs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FCx2DacLDWs", sceIcuUnknown_FCx2DacLDWs_nid_no_patch); + +int APS5_VABI sceIcuUnknown_ekxUEjhuk3Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ekxUEjhuk3Q", sceIcuUnknown_ekxUEjhuk3Q_nid_no_patch); + +int APS5_VABI sceIcuUnknown_BsjbIVU27jQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BsjbIVU27jQ", sceIcuUnknown_BsjbIVU27jQ_nid_no_patch); + +int APS5_VABI sceIcuUnknown_wezXBwKdjAY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wezXBwKdjAY", sceIcuUnknown_wezXBwKdjAY_nid_no_patch); } diff --git a/core/libs/prx/libSceIcu/libSceIcu.hpp b/core/libs/prx/libSceIcu/libSceIcu.hpp index d8c66857ef..eb7f63dc9c 100644 --- a/core/libs/prx/libSceIcu/libSceIcu.hpp +++ b/core/libs/prx/libSceIcu/libSceIcu.hpp @@ -1,4 +1,5 @@ #pragma once #include "Invariant.hpp" #include "Version.hpp" +#include "CString.hpp" #include "Unescape.hpp" diff --git a/core/libs/prx/libSceJsc/Export.cpp b/core/libs/prx/libSceJsc/Export.cpp index 05794b5e68..145a12b86a 100644 --- a/core/libs/prx/libSceJsc/Export.cpp +++ b/core/libs/prx/libSceJsc/Export.cpp @@ -151,4 +151,754 @@ bool APS5_VABI JSStringIsEqualToUTF8CString(const GuestJSString* string, const c return Equal(string, other.get()); } +int APS5_VABI sceJscUnknown__0qGCrVTCcs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0qGCrVTCcs", sceJscUnknown__0qGCrVTCcs_nid_no_patch); + +int APS5_VABI sceJscUnknown__14hrhyqXkw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+14hrhyqXkw", sceJscUnknown__14hrhyqXkw_nid_no_patch); + +int APS5_VABI sceJscUnknown__3jAVQxPMOc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+3jAVQxPMOc", sceJscUnknown__3jAVQxPMOc_nid_no_patch); + +int APS5_VABI sceJscUnknown__i_vXbqYQMo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+i+vXbqYQMo", sceJscUnknown__i_vXbqYQMo_nid_no_patch); + +int APS5_VABI sceJscUnknown__jBdpELLGNA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+jBdpELLGNA", sceJscUnknown__jBdpELLGNA_nid_no_patch); + +int APS5_VABI sceJscUnknown__tn6ODC5FvQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+tn6ODC5FvQ", sceJscUnknown__tn6ODC5FvQ_nid_no_patch); + +int APS5_VABI sceJscUnknown__tuS_migTvI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+tuS+migTvI", sceJscUnknown__tuS_migTvI_nid_no_patch); + +int APS5_VABI sceJscUnknown__uKktfh_zD0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+uKktfh+zD0", sceJscUnknown__uKktfh_zD0_nid_no_patch); + +int APS5_VABI sceJscUnknown__uKqBYnAudU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+uKqBYnAudU", sceJscUnknown__uKqBYnAudU_nid_no_patch); + +int APS5_VABI sceJscUnknown__ugzaqpDezk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ugzaqpDezk", sceJscUnknown__ugzaqpDezk_nid_no_patch); + +int APS5_VABI sceJscUnknown__wd7ITlDmEM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+wd7ITlDmEM", sceJscUnknown__wd7ITlDmEM_nid_no_patch); + +int APS5_VABI sceJscUnknown__ww_pcpOFNc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ww+pcpOFNc", sceJscUnknown__ww_pcpOFNc_nid_no_patch); + +int APS5_VABI sceJscUnknown__x_ywEbOrJo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+x-ywEbOrJo", sceJscUnknown__x_ywEbOrJo_nid_no_patch); + +int APS5_VABI sceJscUnknown__yRvWsESIkM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+yRvWsESIkM", sceJscUnknown__yRvWsESIkM_nid_no_patch); + +int APS5_VABI sceJscUnknown__ypzWWOJMwI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ypzWWOJMwI", sceJscUnknown__ypzWWOJMwI_nid_no_patch); + +int APS5_VABI sceJscUnknown__z3kfOo91Pg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+z3kfOo91Pg", sceJscUnknown__z3kfOo91Pg_nid_no_patch); + +int APS5_VABI sceJscUnknown__zL3nHNv5GU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+zL3nHNv5GU", sceJscUnknown__zL3nHNv5GU_nid_no_patch); + +int APS5_VABI sceJscUnknown__zPRztSZLTg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+zPRztSZLTg", sceJscUnknown__zPRztSZLTg_nid_no_patch); + +int APS5_VABI sceJscUnknown__09a6Ceb6Y0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-09a6Ceb6Y0", sceJscUnknown__09a6Ceb6Y0_nid_no_patch); + +int APS5_VABI sceJscUnknown__1Qgg3W9FVM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-1Qgg3W9FVM", sceJscUnknown__1Qgg3W9FVM_nid_no_patch); + +int APS5_VABI sceJscUnknown__3x7_wyZpEU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-3x7+wyZpEU", sceJscUnknown__3x7_wyZpEU_nid_no_patch); + +int APS5_VABI sceJscUnknown__4M0K8nGdfk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-4M0K8nGdfk", sceJscUnknown__4M0K8nGdfk_nid_no_patch); + +int APS5_VABI sceJscUnknown__4jsoWj40_g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-4jsoWj40-g", sceJscUnknown__4jsoWj40_g_nid_no_patch); + +int APS5_VABI sceJscUnknown__4sVkp5_H_M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-4sVkp5-H-M", sceJscUnknown__4sVkp5_H_M_nid_no_patch); + +int APS5_VABI sceJscUnknown__5budpPgP8o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-5budpPgP8o", sceJscUnknown__5budpPgP8o_nid_no_patch); + +int APS5_VABI sceJscUnknown__69D4t3LLHE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-69D4t3LLHE", sceJscUnknown__69D4t3LLHE_nid_no_patch); + +int APS5_VABI sceJscUnknown__7g_5LVkStM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-7g-5LVkStM", sceJscUnknown__7g_5LVkStM_nid_no_patch); + +int APS5_VABI sceJscUnknown__7u_WG3kX3U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-7u-WG3kX3U", sceJscUnknown__7u_WG3kX3U_nid_no_patch); + +int APS5_VABI sceJscUnknown__9SPyZbLwdc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-9SPyZbLwdc", sceJscUnknown__9SPyZbLwdc_nid_no_patch); + +int APS5_VABI sceJscUnknown__F3z0DF32Fw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-F3z0DF32Fw", sceJscUnknown__F3z0DF32Fw_nid_no_patch); + +int APS5_VABI sceJscUnknown__G1ivTvs2mU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-G1ivTvs2mU", sceJscUnknown__G1ivTvs2mU_nid_no_patch); + +int APS5_VABI sceJscUnknown__HYD6ysDUts_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-HYD6ysDUts", sceJscUnknown__HYD6ysDUts_nid_no_patch); + +int APS5_VABI sceJscUnknown__KKlTIIqZrk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-KKlTIIqZrk", sceJscUnknown__KKlTIIqZrk_nid_no_patch); + +int APS5_VABI sceJscUnknown__LSSffiQqPg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-LSSffiQqPg", sceJscUnknown__LSSffiQqPg_nid_no_patch); + +int APS5_VABI sceJscUnknown__MSv8GTlzww_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-MSv8GTlzww", sceJscUnknown__MSv8GTlzww_nid_no_patch); + +int APS5_VABI sceJscUnknown__MicizMIiag_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-MicizMIiag", sceJscUnknown__MicizMIiag_nid_no_patch); + +int APS5_VABI sceJscUnknown__MnzCChBb5c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-MnzCChBb5c", sceJscUnknown__MnzCChBb5c_nid_no_patch); + +int APS5_VABI sceJscUnknown__N79h7XJG8A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-N79h7XJG8A", sceJscUnknown__N79h7XJG8A_nid_no_patch); + +int APS5_VABI sceJscUnknown__NAYLFFi9n8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-NAYLFFi9n8", sceJscUnknown__NAYLFFi9n8_nid_no_patch); + +int APS5_VABI sceJscUnknown__NOpB57jCtM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-NOpB57jCtM", sceJscUnknown__NOpB57jCtM_nid_no_patch); + +int APS5_VABI sceJscUnknown__Ru0nzM2cJ4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-Ru0nzM2cJ4", sceJscUnknown__Ru0nzM2cJ4_nid_no_patch); + +int APS5_VABI sceJscUnknown__SS6hQt_3dQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-SS6hQt+3dQ", sceJscUnknown__SS6hQt_3dQ_nid_no_patch); + +int APS5_VABI sceJscUnknown__T_6YbvQ3W4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-T+6YbvQ3W4", sceJscUnknown__T_6YbvQ3W4_nid_no_patch); + +int APS5_VABI sceJscUnknown__UWXejaXycI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-UWXejaXycI", sceJscUnknown__UWXejaXycI_nid_no_patch); + +int APS5_VABI sceJscUnknown__W0cyz1wnug_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-W0cyz1wnug", sceJscUnknown__W0cyz1wnug_nid_no_patch); + +int APS5_VABI sceJscUnknown__W6XjwwK8Bo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-W6XjwwK8Bo", sceJscUnknown__W6XjwwK8Bo_nid_no_patch); + +int APS5_VABI sceJscUnknown__W9fbd6kB_8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-W9fbd6kB-8", sceJscUnknown__W9fbd6kB_8_nid_no_patch); + +int APS5_VABI sceJscUnknown__WAFtiljCdo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-WAFtiljCdo", sceJscUnknown__WAFtiljCdo_nid_no_patch); + +int APS5_VABI sceJscUnknown__WzxDM_yTwY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-WzxDM-yTwY", sceJscUnknown__WzxDM_yTwY_nid_no_patch); + +int APS5_VABI sceJscUnknown__XCn1Pwn7NU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-XCn1Pwn7NU", sceJscUnknown__XCn1Pwn7NU_nid_no_patch); + +int APS5_VABI sceJscUnknown__XHaGqZr_PE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-XHaGqZr+PE", sceJscUnknown__XHaGqZr_PE_nid_no_patch); + +int APS5_VABI sceJscUnknown__Xia6SzTPnQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-Xia6SzTPnQ", sceJscUnknown__Xia6SzTPnQ_nid_no_patch); + +int APS5_VABI sceJscUnknown__Y_WZBvAu3M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-Y-WZBvAu3M", sceJscUnknown__Y_WZBvAu3M_nid_no_patch); + +int APS5_VABI sceJscUnknown__YDX6TPLnro_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-YDX6TPLnro", sceJscUnknown__YDX6TPLnro_nid_no_patch); + +int APS5_VABI sceJscUnknown__am9z4xyweE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-am9z4xyweE", sceJscUnknown__am9z4xyweE_nid_no_patch); + +int APS5_VABI sceJscUnknown__e_i2gtmhdc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-e-i2gtmhdc", sceJscUnknown__e_i2gtmhdc_nid_no_patch); + +int APS5_VABI sceJscUnknown__eUZ8xel7b0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-eUZ8xel7b0", sceJscUnknown__eUZ8xel7b0_nid_no_patch); + +int APS5_VABI sceJscUnknown__fAFmad5Tnk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-fAFmad5Tnk", sceJscUnknown__fAFmad5Tnk_nid_no_patch); + +int APS5_VABI sceJscUnknown__fKQRtsYUSc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-fKQRtsYUSc", sceJscUnknown__fKQRtsYUSc_nid_no_patch); + +int APS5_VABI sceJscUnknown__fwHkWlm_wc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-fwHkWlm-wc", sceJscUnknown__fwHkWlm_wc_nid_no_patch); + +int APS5_VABI sceJscUnknown__g6RLhTGOeg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-g6RLhTGOeg", sceJscUnknown__g6RLhTGOeg_nid_no_patch); + +int APS5_VABI sceJscUnknown__gE3O_NCHS0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-gE3O+NCHS0", sceJscUnknown__gE3O_NCHS0_nid_no_patch); + +int APS5_VABI sceJscUnknown__hdGlIfGY5g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-hdGlIfGY5g", sceJscUnknown__hdGlIfGY5g_nid_no_patch); + +int APS5_VABI sceJscUnknown__hk9wmuusRU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-hk9wmuusRU", sceJscUnknown__hk9wmuusRU_nid_no_patch); + +int APS5_VABI sceJscUnknown__md9AgyWZkw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-md9AgyWZkw", sceJscUnknown__md9AgyWZkw_nid_no_patch); + +int APS5_VABI sceJscUnknown__n_GMwKBaD0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-n+GMwKBaD0", sceJscUnknown__n_GMwKBaD0_nid_no_patch); + +int APS5_VABI sceJscUnknown__n_x6ZBExPE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-n+x6ZBExPE", sceJscUnknown__n_x6ZBExPE_nid_no_patch); + +int APS5_VABI sceJscUnknown__oW_aAeCPs4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-oW-aAeCPs4", sceJscUnknown__oW_aAeCPs4_nid_no_patch); + +int APS5_VABI sceJscUnknown__qMXl9DNcac_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-qMXl9DNcac", sceJscUnknown__qMXl9DNcac_nid_no_patch); + +int APS5_VABI sceJscUnknown__qYqo3TC_8I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-qYqo3TC+8I", sceJscUnknown__qYqo3TC_8I_nid_no_patch); + +int APS5_VABI sceJscUnknown__rEvUxwc4xY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-rEvUxwc4xY", sceJscUnknown__rEvUxwc4xY_nid_no_patch); + +int APS5_VABI sceJscUnknown__rtzyTjX8AM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-rtzyTjX8AM", sceJscUnknown__rtzyTjX8AM_nid_no_patch); + +int APS5_VABI sceJscUnknown__s4y23rdI0Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-s4y23rdI0Y", sceJscUnknown__s4y23rdI0Y_nid_no_patch); + +int APS5_VABI sceJscUnknown__sQ3aB_rrVw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-sQ3aB-rrVw", sceJscUnknown__sQ3aB_rrVw_nid_no_patch); + +int APS5_VABI sceJscUnknown__sjd__qKqaU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-sjd++qKqaU", sceJscUnknown__sjd__qKqaU_nid_no_patch); + +int APS5_VABI sceJscUnknown__tPqziuAbt0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-tPqziuAbt0", sceJscUnknown__tPqziuAbt0_nid_no_patch); + +int APS5_VABI sceJscUnknown__uLGa2Nmdg8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-uLGa2Nmdg8", sceJscUnknown__uLGa2Nmdg8_nid_no_patch); + +int APS5_VABI sceJscUnknown__xFvxRPfTng_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-xFvxRPfTng", sceJscUnknown__xFvxRPfTng_nid_no_patch); + +int APS5_VABI sceJscUnknown__zEvjr6MBaE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-zEvjr6MBaE", sceJscUnknown__zEvjr6MBaE_nid_no_patch); + +int APS5_VABI sceJscUnknown__zr4F1xDgGA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-zr4F1xDgGA", sceJscUnknown__zr4F1xDgGA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0_mWELhv81k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0-mWELhv81k", sceJscUnknown_0_mWELhv81k_nid_no_patch); + +int APS5_VABI sceJscUnknown_01IKkgXrFxs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("01IKkgXrFxs", sceJscUnknown_01IKkgXrFxs_nid_no_patch); + +int APS5_VABI sceJscUnknown_01nXitlGn8k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("01nXitlGn8k", sceJscUnknown_01nXitlGn8k_nid_no_patch); + +int APS5_VABI sceJscUnknown_04XvCW_DZys_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("04XvCW-DZys", sceJscUnknown_04XvCW_DZys_nid_no_patch); + +int APS5_VABI sceJscUnknown_0AEWR2mf0dA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0AEWR2mf0dA", sceJscUnknown_0AEWR2mf0dA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0BNiqtvNqTA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0BNiqtvNqTA", sceJscUnknown_0BNiqtvNqTA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0BU_NMgvLhU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0BU+NMgvLhU", sceJscUnknown_0BU_NMgvLhU_nid_no_patch); + +int APS5_VABI sceJscUnknown_0BtcoCh5nbg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0BtcoCh5nbg", sceJscUnknown_0BtcoCh5nbg_nid_no_patch); + +int APS5_VABI sceJscUnknown_0CB7Dz8pEuA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0CB7Dz8pEuA", sceJscUnknown_0CB7Dz8pEuA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0CGMc99sJQc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0CGMc99sJQc", sceJscUnknown_0CGMc99sJQc_nid_no_patch); + +int APS5_VABI sceJscUnknown_0CnwqNph95c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0CnwqNph95c", sceJscUnknown_0CnwqNph95c_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Cu5IQZsCWA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Cu5IQZsCWA", sceJscUnknown_0Cu5IQZsCWA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0DY9Snf59Wk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0DY9Snf59Wk", sceJscUnknown_0DY9Snf59Wk_nid_no_patch); + +int APS5_VABI sceJscUnknown_0DkchTKnu8Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0DkchTKnu8Y", sceJscUnknown_0DkchTKnu8Y_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Eqn40ngE5M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Eqn40ngE5M", sceJscUnknown_0Eqn40ngE5M_nid_no_patch); + +int APS5_VABI sceJscUnknown_0FlXpxjRjOA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0FlXpxjRjOA", sceJscUnknown_0FlXpxjRjOA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0FmMcDDxnAY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0FmMcDDxnAY", sceJscUnknown_0FmMcDDxnAY_nid_no_patch); + +int APS5_VABI sceJscUnknown_0GxlvzlC15A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0GxlvzlC15A", sceJscUnknown_0GxlvzlC15A_nid_no_patch); + +int APS5_VABI sceJscUnknown_0HkylFfb_Ig_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0HkylFfb+Ig", sceJscUnknown_0HkylFfb_Ig_nid_no_patch); + +int APS5_VABI sceJscUnknown_0I6apGOYhtM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0I6apGOYhtM", sceJscUnknown_0I6apGOYhtM_nid_no_patch); + +int APS5_VABI sceJscUnknown_0ICMatq6_6g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0ICMatq6+6g", sceJscUnknown_0ICMatq6_6g_nid_no_patch); + +int APS5_VABI sceJscUnknown_0KClQgT2ckA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0KClQgT2ckA", sceJscUnknown_0KClQgT2ckA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0KqxIyH6DiE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0KqxIyH6DiE", sceJscUnknown_0KqxIyH6DiE_nid_no_patch); + +int APS5_VABI sceJscUnknown_0KsRLd9dTVU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0KsRLd9dTVU", sceJscUnknown_0KsRLd9dTVU_nid_no_patch); + +int APS5_VABI sceJscUnknown_0KynCQXu9XQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0KynCQXu9XQ", sceJscUnknown_0KynCQXu9XQ_nid_no_patch); + +int APS5_VABI sceJscUnknown_0LSRs8rB_A8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0LSRs8rB-A8", sceJscUnknown_0LSRs8rB_A8_nid_no_patch); + +int APS5_VABI sceJscUnknown_0MHM5pZ54E4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0MHM5pZ54E4", sceJscUnknown_0MHM5pZ54E4_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Mo4EsA5wVo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Mo4EsA5wVo", sceJscUnknown_0Mo4EsA5wVo_nid_no_patch); + +int APS5_VABI sceJscUnknown_0MwjtlWeGGg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0MwjtlWeGGg", sceJscUnknown_0MwjtlWeGGg_nid_no_patch); + +int APS5_VABI sceJscUnknown_0OI3DXKmR9w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0OI3DXKmR9w", sceJscUnknown_0OI3DXKmR9w_nid_no_patch); + +int APS5_VABI sceJscUnknown_0OLbLK3fZlE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0OLbLK3fZlE", sceJscUnknown_0OLbLK3fZlE_nid_no_patch); + +int APS5_VABI sceJscUnknown_0QVx_6Ffzjc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0QVx-6Ffzjc", sceJscUnknown_0QVx_6Ffzjc_nid_no_patch); + +int APS5_VABI sceJscUnknown_0QbSxsg9i1M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0QbSxsg9i1M", sceJscUnknown_0QbSxsg9i1M_nid_no_patch); + +int APS5_VABI sceJscUnknown_0QjiF9xEFQA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0QjiF9xEFQA", sceJscUnknown_0QjiF9xEFQA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0R0tdvM7fFs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0R0tdvM7fFs", sceJscUnknown_0R0tdvM7fFs_nid_no_patch); + +int APS5_VABI sceJscUnknown_0RqgTKckJBs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0RqgTKckJBs", sceJscUnknown_0RqgTKckJBs_nid_no_patch); + +int APS5_VABI sceJscUnknown_0SoDdOYaQE4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0SoDdOYaQE4", sceJscUnknown_0SoDdOYaQE4_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Uk_3H5HdEo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Uk-3H5HdEo", sceJscUnknown_0Uk_3H5HdEo_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Ukd6tvlVhs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Ukd6tvlVhs", sceJscUnknown_0Ukd6tvlVhs_nid_no_patch); + +int APS5_VABI sceJscUnknown_0W_mECnGicE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0W-mECnGicE", sceJscUnknown_0W_mECnGicE_nid_no_patch); + +int APS5_VABI sceJscUnknown_0XH9Pu6dPdk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0XH9Pu6dPdk", sceJscUnknown_0XH9Pu6dPdk_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Y_ebXGv50I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Y-ebXGv50I", sceJscUnknown_0Y_ebXGv50I_nid_no_patch); + +int APS5_VABI sceJscUnknown_0YfwqGopI4c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0YfwqGopI4c", sceJscUnknown_0YfwqGopI4c_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Za34L_PPDg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Za34L-PPDg", sceJscUnknown_0Za34L_PPDg_nid_no_patch); + +int APS5_VABI sceJscUnknown_0Zix0_Zcx2k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Zix0+Zcx2k", sceJscUnknown_0Zix0_Zcx2k_nid_no_patch); + +int APS5_VABI sceJscUnknown_0bI_VPZXmzg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0bI-VPZXmzg", sceJscUnknown_0bI_VPZXmzg_nid_no_patch); + +int APS5_VABI sceJscUnknown_0bq1LixdHAY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0bq1LixdHAY", sceJscUnknown_0bq1LixdHAY_nid_no_patch); + +int APS5_VABI sceJscUnknown_0c_07mr9xP8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0c-07mr9xP8", sceJscUnknown_0c_07mr9xP8_nid_no_patch); + +int APS5_VABI sceJscUnknown_0cIPqIrPhvg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0cIPqIrPhvg", sceJscUnknown_0cIPqIrPhvg_nid_no_patch); + +int APS5_VABI sceJscUnknown_0dTfcPHH3dg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0dTfcPHH3dg", sceJscUnknown_0dTfcPHH3dg_nid_no_patch); + +int APS5_VABI sceJscUnknown_0earDv9xPN8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0earDv9xPN8", sceJscUnknown_0earDv9xPN8_nid_no_patch); + +int APS5_VABI sceJscUnknown_0f06o3_XgV0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0f06o3-XgV0", sceJscUnknown_0f06o3_XgV0_nid_no_patch); + +int APS5_VABI sceJscUnknown_0g_jtYCdtQw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0g+jtYCdtQw", sceJscUnknown_0g_jtYCdtQw_nid_no_patch); + +int APS5_VABI sceJscUnknown_0g66of16pvY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0g66of16pvY", sceJscUnknown_0g66of16pvY_nid_no_patch); + +int APS5_VABI sceJscUnknown_0gQImk3C0sA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0gQImk3C0sA", sceJscUnknown_0gQImk3C0sA_nid_no_patch); + +int APS5_VABI sceJscUnknown_0ggQR1NjDCY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0ggQR1NjDCY", sceJscUnknown_0ggQR1NjDCY_nid_no_patch); + +int APS5_VABI sceJscUnknown_0ixkCjQDH2I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0ixkCjQDH2I", sceJscUnknown_0ixkCjQDH2I_nid_no_patch); + +int APS5_VABI sceJscUnknown_0lR2YBKJMIE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0lR2YBKJMIE", sceJscUnknown_0lR2YBKJMIE_nid_no_patch); + +int APS5_VABI sceJscUnknown_0nI81nsrl_A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0nI81nsrl+A", sceJscUnknown_0nI81nsrl_A_nid_no_patch); + +int APS5_VABI sceJscUnknown_0pp8T8yX1BU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0pp8T8yX1BU", sceJscUnknown_0pp8T8yX1BU_nid_no_patch); + +int APS5_VABI sceJscUnknown_0q3Pf6miAts_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0q3Pf6miAts", sceJscUnknown_0q3Pf6miAts_nid_no_patch); + +int APS5_VABI sceJscUnknown_0qmAOS00O2g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0qmAOS00O2g", sceJscUnknown_0qmAOS00O2g_nid_no_patch); + +int APS5_VABI sceJscUnknown_0qr5jdWMC3E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0qr5jdWMC3E", sceJscUnknown_0qr5jdWMC3E_nid_no_patch); + +int APS5_VABI sceJscUnknown_0rj2akkxaO0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0rj2akkxaO0", sceJscUnknown_0rj2akkxaO0_nid_no_patch); + +int APS5_VABI sceJscUnknown_0ro1gFmJat8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0ro1gFmJat8", sceJscUnknown_0ro1gFmJat8_nid_no_patch); + +int APS5_VABI sceJscUnknown_0v8_cEQL93A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0v8-cEQL93A", sceJscUnknown_0v8_cEQL93A_nid_no_patch); + +int APS5_VABI sceJscUnknown_0vAcIjIOiEE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0vAcIjIOiEE", sceJscUnknown_0vAcIjIOiEE_nid_no_patch); + +int APS5_VABI sceJscUnknown_0wxHtqo7dew_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0wxHtqo7dew", sceJscUnknown_0wxHtqo7dew_nid_no_patch); + +int APS5_VABI sceJscUnknown_0wzoLEfYeJo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0wzoLEfYeJo", sceJscUnknown_0wzoLEfYeJo_nid_no_patch); + +int APS5_VABI sceJscUnknown_0xH4JecjF2Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0xH4JecjF2Q", sceJscUnknown_0xH4JecjF2Q_nid_no_patch); + } diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 477b3d020f..18ddc3103b 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -464,7 +464,7 @@ private: std::istringstream stream(text); stream.imbue(std::locale::classic()); stream >> real; - converted = !stream.fail() && stream.eof(); + converted = stream.eof() && (!stream.fail() || real == 0); } if (!converted || !std::isfinite(real)) throw std::runtime_error("sce::Json::Parser::parse: number " + text + " overflows a double; the console's behaviour is unverified"); out.type = TypeReal; diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index f17760d179..571bde9b04 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -89,6 +89,15 @@ if(BUILD_TESTING) configure_windows_unwind(guest_program_name_tests) add_test(NAME guest_program_name COMMAND guest_program_name_tests) set_tests_properties(guest_program_name PROPERTIES TIMEOUT 20) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test_executable(guest_posix_spawn_flags_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSpawnFlags.cpp) + target_include_directories(guest_posix_spawn_flags_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_spawn_flags_tests PRIVATE libSceLibcInternal libc libkernel ${CMAKE_DL_LIBS}) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/spawn-flags) + add_test(NAME guest_posix_spawn_flags COMMAND guest_posix_spawn_flags_tests) + set_tests_properties(guest_posix_spawn_flags PROPERTIES TIMEOUT 20 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/spawn-flags) + endif() if(WIN32) set_tests_properties(guest_program_name PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceLibcInternal/Spawn.cpp b/core/libs/prx/libSceLibcInternal/Spawn.cpp index 4c7bd815b2..3d26b19519 100644 --- a/core/libs/prx/libSceLibcInternal/Spawn.cpp +++ b/core/libs/prx/libSceLibcInternal/Spawn.cpp @@ -393,11 +393,13 @@ int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileAct if (flags != 0) { if (posix_spawnattr_init(&hostAttributes) != 0) return Fault; usedAttributes = &hostAttributes; - if ((flags & SpawnFlagResetIds) != 0) posix_spawnattr_setflags(usedAttributes, POSIX_SPAWN_RESETIDS); + short hostFlags = 0; + if ((flags & SpawnFlagResetIds) != 0) hostFlags |= POSIX_SPAWN_RESETIDS; if ((flags & SpawnFlagSetGroup) != 0) { - posix_spawnattr_setflags(usedAttributes, POSIX_SPAWN_SETPGROUP); + hostFlags |= POSIX_SPAWN_SETPGROUP; posix_spawnattr_setpgroup(usedAttributes, (*attributes)->processGroup); } + posix_spawnattr_setflags(usedAttributes, hostFlags); } posix_spawn_file_actions_t hostActions; posix_spawn_file_actions_t* usedActions = nullptr; diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 7f27debea6..0033e0157c 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,8 +1,8 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) -target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp) if(BUILD_TESTING) add_test_executable(guest_libressl_x509_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Errors.cpp) @@ -29,8 +29,14 @@ if(BUILD_TESTING) add_test(NAME guest_libressl_asn1 COMMAND guest_libressl_asn1_tests) add_test(NAME guest_libressl_asn1_allocator COMMAND guest_libressl_asn1_tests allocator) set_tests_properties(guest_libressl_asn1 guest_libressl_asn1_allocator PROPERTIES TIMEOUT 30) + add_test_executable(guest_libressl_ex_data_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/ExData.cpp) + target_include_directories(guest_libressl_ex_data_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ex_data_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ex_data_tests) + add_test(NAME guest_libressl_ex_data COMMAND guest_libressl_ex_data_tests) + set_tests_properties(guest_libressl_ex_data PROPERTIES TIMEOUT 30) if(WIN32) - set_tests_properties(guest_libressl_digest guest_libressl_asn1 guest_libressl_asn1_allocator PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_libressl_digest guest_libressl_asn1 guest_libressl_asn1_allocator guest_libressl_ex_data PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() @@ -122,6 +128,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libressl_x509_store_context_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509StoreContext.cpp) + target_include_directories(guest_libressl_x509_store_context_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_store_context_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_store_context_tests) + add_test(NAME guest_libressl_x509_store_context COMMAND guest_libressl_x509_store_context_tests) + set_tests_properties(guest_libressl_x509_store_context PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_store_context PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_libressl_pkey_type_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslPkeyType.cpp) target_include_directories(guest_libressl_pkey_type_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libressl_pkey_type_tests PRIVATE libSceLibreSsl libc) @@ -189,3 +205,32 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ObjectTable.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ObjectText.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_object_text_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/ObjectText.cpp) + target_include_directories(guest_libressl_object_text_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_object_text_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_object_text_tests) + add_test(NAME guest_libressl_object_text COMMAND guest_libressl_object_text_tests) + set_tests_properties(guest_libressl_object_text PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_object_text PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_libressl_object_allocation_tests + tests/ObjectTextAllocation.cpp src/ObjectText.cpp src/ObjectTable.cpp src/Error.cpp) + target_include_directories(guest_libressl_object_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_object_allocation_tests PRIVATE libc) + configure_windows_unwind(guest_libressl_object_allocation_tests) + add_test(NAME guest_libressl_object_allocation COMMAND guest_libressl_object_allocation_tests) + set_tests_properties(guest_libressl_object_allocation PROPERTIES TIMEOUT 8) + if(WIN32) + set_tests_properties(guest_libressl_object_allocation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 4ac6e5f647..6e5339c459 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -223,11 +223,6 @@ void APS5_VABI CRYPTO_THREADID_set_numeric_nid_postfix(CryptoThreadId* id, std:: id->val = value; } -int APS5_VABI CRYPTO_cleanup_all_ex_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI CRYPTO_num_locks_nid_postfix(void) { return CryptoNumLocks; } @@ -994,11 +989,6 @@ int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1209,16 +1199,6 @@ int APS5_VABI SSL_get_ex_data_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_ex_new_index_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get_fd_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1473,36 +1453,11 @@ int APS5_VABI X509_STORE_CTX_get1_chain_nid_postfix(void) { return 0; } -int APS5_VABI X509_STORE_CTX_get_chain_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_STORE_CTX_get_current_cert_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_STORE_CTX_get_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_STORE_CTX_get_error_depth_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_STORE_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI X509_STORE_CTX_set_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_STORE_add_cert_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1698,11 +1653,6 @@ int APS5_VABI i2d_X509_PUBKEY_nid_postfix(void) { return 0; } -int APS5_VABI i2t_ASN1_OBJECT_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceLibreSslGetMemoryPoolStats(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 1cbfc7d759..2669f58912 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -9,6 +9,14 @@ struct Asn1String { std::int64_t flags; }; +struct LibreSslExData { + void* stack; +}; + +using LibreSslExNew = int (APS5_VABI*)(void* parent, void* pointer, LibreSslExData* data, int index, std::int64_t argl, void* argp); +using LibreSslExFree = void (APS5_VABI*)(void* parent, void* pointer, LibreSslExData* data, int index, std::int64_t argl, void* argp); +using LibreSslExDup = int (APS5_VABI*)(LibreSslExData* to, LibreSslExData* from, void* fromData, int index, std::int64_t argl, void* argp); + inline constexpr int MaxRounds = 14; struct AesKey { @@ -21,6 +29,15 @@ struct CryptoThreadId { std::uint64_t val; }; +struct LibreSslAsn1Object { + const char* shortName; + const char* longName; + std::int32_t nid; + std::int32_t length; + const unsigned char* data; + std::int32_t flags; +}; + struct Md4Context { std::uint32_t A, B, C, D; std::uint32_t Nl, Nh; @@ -114,6 +131,31 @@ struct LibreSslRsa { void* mtBlinding; }; +struct LibreSslX509StoreContext { + void* store; + int currentMethod; + void* cert; + void* untrusted; + void* crls; + void* param; + void* otherContext; + void* callbacks[12]; + int valid; + int lastUntrusted; + void* chain; + void* tree; + int explicitPolicy; + int errorDepth; + int error; + void* currentCert; + void* currentIssuer; + void* currentCrl; + int currentCrlScore; + unsigned int currentReasons; + LibreSslX509StoreContext* parent; + void* exData; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -160,7 +202,7 @@ int APS5_VABI CONF_modules_free_nid_postfix(void); int APS5_VABI CONF_modules_load_file_nid_postfix(void); int APS5_VABI CRYPTO_THREADID_set_callback_nid_postfix(void* callback); void APS5_VABI CRYPTO_THREADID_set_numeric_nid_postfix(CryptoThreadId* id, std::uint64_t value); -int APS5_VABI CRYPTO_cleanup_all_ex_data_nid_postfix(void); +void APS5_VABI CRYPTO_cleanup_all_ex_data_nid_postfix(void); int APS5_VABI CRYPTO_num_locks_nid_postfix(void); void APS5_VABI CRYPTO_set_add_lock_callback_nid_postfix(void* callback); void APS5_VABI CRYPTO_set_locking_callback_nid_postfix(void* callback); @@ -326,7 +368,7 @@ int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); int APS5_VABI SSL_CTX_free_nid_postfix(void); int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); -int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(void); +int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction); int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); @@ -371,7 +413,7 @@ int APS5_VABI SSL_get_current_cipher_nid_postfix(void); int APS5_VABI SSL_get_error_nid_postfix(void); int APS5_VABI SSL_get_ex_data_nid_postfix(void); int APS5_VABI SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix(void); -int APS5_VABI SSL_get_ex_new_index_nid_postfix(void); +int APS5_VABI SSL_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction); int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); @@ -434,12 +476,12 @@ int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); int APS5_VABI X509_NAME_oneline_nid_postfix(void); int APS5_VABI X509_NAME_print_ex_nid_postfix(void); int APS5_VABI X509_STORE_CTX_get1_chain_nid_postfix(void); -int APS5_VABI X509_STORE_CTX_get_chain_nid_postfix(void); -int APS5_VABI X509_STORE_CTX_get_current_cert_nid_postfix(void); -int APS5_VABI X509_STORE_CTX_get_error_nid_postfix(void); -int APS5_VABI X509_STORE_CTX_get_error_depth_nid_postfix(void); +void* APS5_VABI X509_STORE_CTX_get_chain_nid_postfix(LibreSslX509StoreContext* context); +void* APS5_VABI X509_STORE_CTX_get_current_cert_nid_postfix(LibreSslX509StoreContext* context); +int APS5_VABI X509_STORE_CTX_get_error_nid_postfix(LibreSslX509StoreContext* context); +int APS5_VABI X509_STORE_CTX_get_error_depth_nid_postfix(LibreSslX509StoreContext* context); int APS5_VABI X509_STORE_CTX_get_ex_data_nid_postfix(void); -int APS5_VABI X509_STORE_CTX_set_error_nid_postfix(void); +void APS5_VABI X509_STORE_CTX_set_error_nid_postfix(LibreSslX509StoreContext* context, int error); int APS5_VABI X509_STORE_add_cert_nid_postfix(void); int APS5_VABI X509_STORE_add_crl_nid_postfix(void); int APS5_VABI X509_STORE_add_lookup_nid_postfix(void); @@ -467,7 +509,7 @@ int APS5_VABI d2i_RSA_PUBKEY_nid_postfix(void); int APS5_VABI d2i_X509_nid_postfix(void); int APS5_VABI i2a_ASN1_OBJECT_nid_postfix(void); int APS5_VABI i2d_X509_PUBKEY_nid_postfix(void); -int APS5_VABI i2t_ASN1_OBJECT_nid_postfix(void); +int APS5_VABI i2t_ASN1_OBJECT_nid_postfix(char* buffer, int length, const LibreSslAsn1Object* object); int APS5_VABI sceLibreSslGetMemoryPoolStats(void); int APS5_VABI sceLibreSslInit(void); int APS5_VABI sceLibreSslInitEx(void); diff --git a/core/libs/prx/libSceLibreSsl/include/ObjectTable.hpp b/core/libs/prx/libSceLibreSsl/include/ObjectTable.hpp new file mode 100644 index 0000000000..fa770930f3 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/include/ObjectTable.hpp @@ -0,0 +1,20 @@ +#ifndef CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_OBJECTTABLE_HPP +#define CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_OBJECTTABLE_HPP + +#include "prx/libSceLibreSsl/include/Api.hpp" + +inline constexpr int LibreSslObjectCount = 956; +inline constexpr int LibreSslObjectOrderCount = 890; + +extern "C" { + +extern const LibreSslAsn1Object LibreSslObjects_nid_no_patch[LibreSslObjectCount]; +extern const unsigned short LibreSslObjectOrder_nid_no_patch[LibreSslObjectOrderCount]; + +int LibreSslObjectNid_nid_no_patch(const LibreSslAsn1Object* object); +const char* LibreSslObjectShortName_nid_no_patch(int nid); +const char* LibreSslObjectLongName_nid_no_patch(int nid); + +} + +#endif diff --git a/core/libs/prx/libSceLibreSsl/src/ExData.cpp b/core/libs/prx/libSceLibreSsl/src/ExData.cpp new file mode 100644 index 0000000000..da6d14cf51 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/ExData.cpp @@ -0,0 +1,66 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include +#include +#include +#include +#include + +namespace { +constexpr int CryptoLibrary = 15; +constexpr int AnyFunction = 0xfff; +constexpr int MallocFailure = 65; +constexpr int SslClass = 1; +constexpr int SslContextClass = 2; +constexpr int StoreContextClass = 5; +constexpr int ClassCount = 16; + +struct ExDataFunctions { + std::int64_t argl; + void* argp; + LibreSslExNew newFunction; + LibreSslExDup dupFunction; + LibreSslExFree freeFunction; +}; + +std::mutex classesLock; +std::array, ClassCount> classes; +std::mutex storeContextLock; +int storeContextIndex = -1; + +int NewIndex(int classIndex, const ExDataFunctions& functions) { + std::lock_guard lock(classesLock); + auto& registered = classes[classIndex]; + try { + registered.push_back(functions); + } catch (const std::bad_alloc&) { + LibreSslPutError_nid_no_patch(CryptoLibrary, AnyFunction, MallocFailure); + return -1; + } + return static_cast(registered.size() - 1); +} +} + +extern "C" { + +int APS5_VABI SSL_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction) { + return NewIndex(SslClass, {argl, argp, newFunction, dupFunction, freeFunction}); +} + +int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction) { + return NewIndex(SslContextClass, {argl, argp, newFunction, dupFunction, freeFunction}); +} + +int APS5_VABI SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix(void) { + static char verifyCallback[] = "SSL for verify callback"; + std::lock_guard lock(storeContextLock); + if (storeContextIndex < 0) storeContextIndex = NewIndex(StoreContextClass, {0, verifyCallback, nullptr, nullptr, nullptr}); + return storeContextIndex; +} + +void APS5_VABI CRYPTO_cleanup_all_ex_data_nid_postfix(void) { + std::lock_guard lock(classesLock); + for (auto& registered : classes) std::vector().swap(registered); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/ObjectTable.cpp b/core/libs/prx/libSceLibreSsl/src/ObjectTable.cpp new file mode 100644 index 0000000000..3b7e145615 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/ObjectTable.cpp @@ -0,0 +1,1419 @@ +#include "prx/libSceLibreSsl/include/ObjectTable.hpp" + +namespace { + +constexpr unsigned char objectData[6216] = { + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x02, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x03, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x05, 0x03, 0x55, 0x55, 0x04, 0x55, 0x04, 0x03, 0x55, 0x04, 0x06, 0x55, 0x04, 0x07, 0x55, 0x04, + 0x08, 0x55, 0x04, 0x0a, 0x55, 0x04, 0x0b, 0x55, 0x08, 0x01, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x07, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x07, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x03, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x05, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x03, 0x01, 0x2b, 0x0e, 0x03, 0x02, 0x06, 0x2b, + 0x0e, 0x03, 0x02, 0x09, 0x2b, 0x0e, 0x03, 0x02, 0x07, 0x2b, 0x0e, 0x03, 0x02, 0x11, 0x2b, 0x06, + 0x01, 0x04, 0x01, 0x81, 0x3c, 0x07, 0x01, 0x01, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, + 0x02, 0x2b, 0x0e, 0x03, 0x02, 0x12, 0x2b, 0x0e, 0x03, 0x02, 0x0f, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x03, 0x07, 0x2b, 0x0e, 0x03, 0x02, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x05, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x07, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, + 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x02, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x0d, 0x2b, 0x0e, 0x03, 0x02, 0x0c, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x05, 0x0c, 0x2b, 0x0e, 0x03, 0x02, 0x1b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, + 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x02, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, + 0x01, 0x03, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x04, 0x60, 0x86, 0x48, 0x01, 0x86, + 0xf8, 0x42, 0x01, 0x07, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x08, 0x60, 0x86, 0x48, + 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0c, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d, 0x60, + 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x02, 0x05, 0x55, 0x1d, 0x55, 0x1d, 0x0e, 0x55, 0x1d, 0x0f, + 0x55, 0x1d, 0x10, 0x55, 0x1d, 0x11, 0x55, 0x1d, 0x12, 0x55, 0x1d, 0x13, 0x55, 0x1d, 0x14, 0x55, + 0x1d, 0x20, 0x55, 0x1d, 0x23, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x02, 0x55, 0x08, + 0x03, 0x65, 0x55, 0x08, 0x03, 0x64, 0x55, 0x04, 0x2a, 0x55, 0x04, 0x04, 0x55, 0x04, 0x2b, 0x55, + 0x1d, 0x1f, 0x2b, 0x0e, 0x03, 0x02, 0x03, 0x55, 0x04, 0x05, 0x55, 0x04, 0x0c, 0x55, 0x04, 0x0d, + 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x42, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, + 0x42, 0x0c, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03, 0x2b, 0x0e, 0x03, 0x02, 0x1d, 0x2a, 0x86, + 0x48, 0xce, 0x38, 0x04, 0x01, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x2b, 0x24, 0x03, 0x03, 0x01, 0x02, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x08, 0x29, 0x01, 0x01, 0x85, 0x1a, 0x01, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x08, 0x55, 0x1d, 0x25, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, + 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, + 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, + 0x08, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x15, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0x82, 0x37, 0x02, 0x01, 0x16, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x01, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, + 0x0a, 0x03, 0x04, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x55, 0x1d, 0x1b, 0x55, + 0x1d, 0x15, 0x55, 0x1d, 0x18, 0x2b, 0x65, 0x01, 0x04, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x0c, 0x01, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x02, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, + 0x01, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x05, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, + 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, + 0x01, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x05, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x14, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x16, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x16, 0x02, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x17, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, + 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0e, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x02, 0x07, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x02, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x0f, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x05, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x06, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x0e, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x0e, 0x55, 0x04, 0x29, 0x55, 0x04, 0x2e, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x01, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x30, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09, 0x2a, 0x2a, 0x86, + 0x48, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x00, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x10, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, + 0x05, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x10, 0x00, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x00, + 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x00, 0x03, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x00, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, + 0x00, 0x05, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x00, 0x06, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x00, 0x07, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x10, 0x00, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x01, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x10, 0x01, 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x04, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x05, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x10, 0x01, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x07, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x10, 0x02, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, + 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x03, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, + 0x02, 0x05, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x06, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x07, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x10, 0x02, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x10, 0x02, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x0c, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x10, 0x02, 0x0e, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x0f, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x10, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x10, 0x02, 0x11, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, + 0x12, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x13, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x14, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, + 0x02, 0x15, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x16, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x17, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x10, 0x02, 0x18, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x19, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x1a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x10, 0x02, 0x1b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x1c, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x1d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x10, 0x03, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x02, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x10, 0x03, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, + 0x05, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x06, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x03, 0x07, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, + 0x04, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x05, 0x01, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x05, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, + 0x10, 0x06, 0x01, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x06, 0x02, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x06, 0x03, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x10, 0x06, 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x06, 0x05, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x06, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x02, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x0a, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0b, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0c, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x02, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x04, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x05, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x06, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x07, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x0a, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x0b, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x0c, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x0d, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x0e, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x0f, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x00, 0x10, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x03, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x05, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x07, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x09, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x05, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x07, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x0a, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x01, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x03, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x05, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x07, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x09, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x0a, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x0b, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x0c, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x0d, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x0e, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x0f, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x02, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x01, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x01, + 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x01, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x05, 0x01, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x01, 0x05, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x05, 0x01, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x05, 0x02, 0x01, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x05, 0x02, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x06, 0x01, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x06, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x06, 0x03, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x06, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x01, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x03, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x05, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x07, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x09, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x0a, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x0b, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x0f, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x10, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x11, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x12, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x13, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x15, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x16, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x17, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x07, 0x18, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x01, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x09, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x09, 0x02, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x09, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x09, 0x04, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x09, 0x05, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0a, 0x01, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x0a, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0a, 0x03, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x0a, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0a, 0x05, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x0b, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0c, 0x01, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x0c, 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0c, 0x03, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x04, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, + 0x02, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x30, 0x01, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x30, 0x01, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x07, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x09, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x0a, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, + 0x01, 0x0b, 0x2b, 0x0e, 0x03, 0x02, 0x2b, 0x0e, 0x03, 0x02, 0x0b, 0x55, 0x08, 0x2b, 0x2b, 0x06, + 0x2b, 0x06, 0x01, 0x2b, 0x06, 0x01, 0x01, 0x2b, 0x06, 0x01, 0x02, 0x2b, 0x06, 0x01, 0x03, 0x2b, + 0x06, 0x01, 0x04, 0x2b, 0x06, 0x01, 0x05, 0x2b, 0x06, 0x01, 0x06, 0x2b, 0x06, 0x01, 0x07, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x8b, 0x3a, 0x82, 0x58, 0x09, 0x92, 0x26, + 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, + 0x0d, 0x55, 0x01, 0x05, 0x55, 0x01, 0x05, 0x37, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x03, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0a, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, + 0x0b, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0a, 0x06, 0x55, 0x04, 0x48, 0x55, 0x1d, 0x24, 0x55, + 0x1d, 0x37, 0x55, 0x1d, 0x38, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, + 0x01, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x02, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x01, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x02, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x03, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x04, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x05, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x06, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01, 0x2b, 0x06, + 0x01, 0x04, 0x01, 0x82, 0x37, 0x11, 0x01, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x01, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, + 0x01, 0x03, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x04, 0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x01, 0x15, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x16, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x01, 0x17, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x18, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x29, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, + 0x2a, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2b, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x01, 0x2c, 0x55, 0x1d, 0x17, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x02, 0x01, 0x2a, 0x86, 0x48, + 0xce, 0x38, 0x02, 0x02, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x02, 0x03, 0x09, 0x09, 0x92, 0x26, 0x09, + 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x03, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, + 0x64, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x03, 0x04, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x03, 0x05, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x03, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x04, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x04, 0x05, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x06, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x07, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x04, 0x09, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x0e, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x0f, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x11, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x12, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x04, 0x13, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x14, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x04, 0x15, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x04, 0x16, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x01, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x02, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x03, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x04, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x01, 0x05, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x06, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x07, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x01, 0x08, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x09, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x0b, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x0c, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x01, 0x0d, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x0e, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x0f, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x01, 0x14, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x15, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x16, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x17, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x18, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x01, 0x1a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x1b, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x1c, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x01, 0x1d, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x1e, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x1f, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x25, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x26, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x01, 0x27, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x28, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x29, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x01, 0x2a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x2b, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x2d, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x2e, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x2f, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x01, 0x30, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x31, 0x09, 0x92, + 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x32, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, + 0x01, 0x33, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x34, 0x09, 0x92, 0x26, 0x89, + 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x35, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x36, + 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x37, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, + 0x2c, 0x64, 0x01, 0x38, 0x55, 0x04, 0x2d, 0x2b, 0x06, 0x01, 0x07, 0x01, 0x2b, 0x06, 0x01, 0x07, + 0x01, 0x01, 0x2b, 0x06, 0x01, 0x07, 0x01, 0x02, 0x2b, 0x06, 0x01, 0x07, 0x01, 0x01, 0x01, 0x2b, + 0x06, 0x01, 0x07, 0x01, 0x01, 0x02, 0x55, 0x04, 0x2c, 0x55, 0x04, 0x41, 0x67, 0x2a, 0x67, 0x2a, + 0x00, 0x67, 0x2a, 0x01, 0x67, 0x2a, 0x03, 0x67, 0x2a, 0x05, 0x67, 0x2a, 0x07, 0x67, 0x2a, 0x08, + 0x67, 0x2a, 0x00, 0x00, 0x67, 0x2a, 0x00, 0x01, 0x67, 0x2a, 0x00, 0x02, 0x67, 0x2a, 0x00, 0x03, + 0x67, 0x2a, 0x00, 0x04, 0x67, 0x2a, 0x00, 0x05, 0x67, 0x2a, 0x00, 0x06, 0x67, 0x2a, 0x00, 0x07, + 0x67, 0x2a, 0x00, 0x08, 0x67, 0x2a, 0x00, 0x09, 0x67, 0x2a, 0x00, 0x0a, 0x67, 0x2a, 0x00, 0x0b, + 0x67, 0x2a, 0x00, 0x0c, 0x67, 0x2a, 0x00, 0x0d, 0x67, 0x2a, 0x00, 0x0e, 0x67, 0x2a, 0x00, 0x10, + 0x67, 0x2a, 0x00, 0x11, 0x67, 0x2a, 0x00, 0x12, 0x67, 0x2a, 0x00, 0x13, 0x67, 0x2a, 0x00, 0x14, + 0x67, 0x2a, 0x00, 0x15, 0x67, 0x2a, 0x00, 0x16, 0x67, 0x2a, 0x00, 0x17, 0x67, 0x2a, 0x00, 0x18, + 0x67, 0x2a, 0x00, 0x19, 0x67, 0x2a, 0x00, 0x1a, 0x67, 0x2a, 0x00, 0x1b, 0x67, 0x2a, 0x00, 0x1c, + 0x67, 0x2a, 0x00, 0x1d, 0x67, 0x2a, 0x00, 0x1e, 0x67, 0x2a, 0x00, 0x1f, 0x67, 0x2a, 0x00, 0x20, + 0x67, 0x2a, 0x00, 0x21, 0x67, 0x2a, 0x00, 0x22, 0x67, 0x2a, 0x00, 0x23, 0x67, 0x2a, 0x00, 0x24, + 0x67, 0x2a, 0x00, 0x25, 0x67, 0x2a, 0x00, 0x26, 0x67, 0x2a, 0x00, 0x27, 0x67, 0x2a, 0x00, 0x28, + 0x67, 0x2a, 0x00, 0x29, 0x67, 0x2a, 0x00, 0x2a, 0x67, 0x2a, 0x00, 0x2b, 0x67, 0x2a, 0x00, 0x2c, + 0x67, 0x2a, 0x00, 0x2d, 0x67, 0x2a, 0x00, 0x2e, 0x67, 0x2a, 0x00, 0x2f, 0x67, 0x2a, 0x00, 0x30, + 0x67, 0x2a, 0x00, 0x31, 0x67, 0x2a, 0x00, 0x32, 0x67, 0x2a, 0x00, 0x33, 0x67, 0x2a, 0x00, 0x34, + 0x67, 0x2a, 0x00, 0x35, 0x67, 0x2a, 0x00, 0x36, 0x67, 0x2a, 0x00, 0x37, 0x67, 0x2a, 0x00, 0x38, + 0x67, 0x2a, 0x00, 0x39, 0x67, 0x2a, 0x00, 0x3a, 0x67, 0x2a, 0x00, 0x3b, 0x67, 0x2a, 0x00, 0x3c, + 0x67, 0x2a, 0x00, 0x3d, 0x67, 0x2a, 0x00, 0x3e, 0x67, 0x2a, 0x00, 0x3f, 0x67, 0x2a, 0x00, 0x40, + 0x67, 0x2a, 0x00, 0x41, 0x67, 0x2a, 0x00, 0x42, 0x67, 0x2a, 0x00, 0x43, 0x67, 0x2a, 0x00, 0x44, + 0x67, 0x2a, 0x00, 0x45, 0x67, 0x2a, 0x00, 0x46, 0x67, 0x2a, 0x00, 0x47, 0x67, 0x2a, 0x00, 0x48, + 0x67, 0x2a, 0x00, 0x49, 0x67, 0x2a, 0x00, 0x4a, 0x67, 0x2a, 0x00, 0x4b, 0x67, 0x2a, 0x00, 0x4c, + 0x67, 0x2a, 0x00, 0x4d, 0x67, 0x2a, 0x00, 0x4e, 0x67, 0x2a, 0x00, 0x4f, 0x67, 0x2a, 0x00, 0x50, + 0x67, 0x2a, 0x00, 0x51, 0x67, 0x2a, 0x00, 0x52, 0x67, 0x2a, 0x01, 0x01, 0x67, 0x2a, 0x01, 0x03, + 0x67, 0x2a, 0x01, 0x04, 0x67, 0x2a, 0x01, 0x05, 0x67, 0x2a, 0x01, 0x07, 0x67, 0x2a, 0x01, 0x08, + 0x67, 0x2a, 0x05, 0x00, 0x67, 0x2a, 0x07, 0x00, 0x67, 0x2a, 0x07, 0x01, 0x67, 0x2a, 0x07, 0x02, + 0x67, 0x2a, 0x07, 0x03, 0x67, 0x2a, 0x07, 0x04, 0x67, 0x2a, 0x07, 0x05, 0x67, 0x2a, 0x07, 0x06, + 0x67, 0x2a, 0x07, 0x07, 0x67, 0x2a, 0x07, 0x08, 0x67, 0x2a, 0x07, 0x09, 0x67, 0x2a, 0x07, 0x0a, + 0x67, 0x2a, 0x07, 0x0b, 0x67, 0x2a, 0x03, 0x00, 0x67, 0x2a, 0x03, 0x01, 0x67, 0x2a, 0x03, 0x02, + 0x67, 0x2a, 0x03, 0x03, 0x67, 0x2a, 0x03, 0x00, 0x00, 0x67, 0x2a, 0x03, 0x00, 0x01, 0x67, 0x2a, + 0x03, 0x02, 0x01, 0x67, 0x2a, 0x03, 0x02, 0x02, 0x67, 0x2a, 0x03, 0x03, 0x03, 0x67, 0x2a, 0x03, + 0x03, 0x04, 0x67, 0x2a, 0x03, 0x03, 0x05, 0x67, 0x2a, 0x03, 0x03, 0x03, 0x01, 0x67, 0x2a, 0x03, + 0x03, 0x04, 0x01, 0x67, 0x2a, 0x03, 0x03, 0x04, 0x02, 0x67, 0x2a, 0x03, 0x03, 0x05, 0x01, 0x67, + 0x2a, 0x03, 0x03, 0x05, 0x02, 0x67, 0x2a, 0x08, 0x01, 0x67, 0x2a, 0x08, 0x1e, 0x67, 0x2a, 0x08, + 0x22, 0x67, 0x2a, 0x08, 0x23, 0x67, 0x2a, 0x08, 0x04, 0x67, 0x2a, 0x08, 0x05, 0x67, 0x2a, 0x08, + 0xae, 0x7b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x06, 0x67, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x02, 0x2b, 0x06, + 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x03, 0x55, 0x04, 0x09, 0x55, 0x04, 0x11, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x15, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0e, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x15, 0x00, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x15, 0x01, 0x55, 0x1d, 0x1e, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x15, 0x02, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0e, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, + 0x2b, 0x2b, 0x81, 0x04, 0x67, 0x2b, 0x67, 0x2b, 0x01, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x02, + 0x03, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x02, 0x03, 0x01, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, + 0x02, 0x03, 0x02, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x02, 0x03, 0x03, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x01, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x02, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x03, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x04, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x05, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x06, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x09, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x0a, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x0b, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x0c, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x0d, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x0e, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x0f, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x10, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x11, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x12, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x00, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x00, 0x14, 0x2b, 0x81, 0x04, 0x00, + 0x06, 0x2b, 0x81, 0x04, 0x00, 0x07, 0x2b, 0x81, 0x04, 0x00, 0x1c, 0x2b, 0x81, 0x04, 0x00, 0x1d, + 0x2b, 0x81, 0x04, 0x00, 0x09, 0x2b, 0x81, 0x04, 0x00, 0x08, 0x2b, 0x81, 0x04, 0x00, 0x1e, 0x2b, + 0x81, 0x04, 0x00, 0x1f, 0x2b, 0x81, 0x04, 0x00, 0x20, 0x2b, 0x81, 0x04, 0x00, 0x21, 0x2b, 0x81, + 0x04, 0x00, 0x0a, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x2b, 0x81, 0x04, 0x00, 0x23, 0x2b, 0x81, 0x04, + 0x00, 0x04, 0x2b, 0x81, 0x04, 0x00, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x16, 0x2b, 0x81, 0x04, 0x00, + 0x17, 0x2b, 0x81, 0x04, 0x00, 0x01, 0x2b, 0x81, 0x04, 0x00, 0x02, 0x2b, 0x81, 0x04, 0x00, 0x0f, + 0x2b, 0x81, 0x04, 0x00, 0x18, 0x2b, 0x81, 0x04, 0x00, 0x19, 0x2b, 0x81, 0x04, 0x00, 0x1a, 0x2b, + 0x81, 0x04, 0x00, 0x1b, 0x2b, 0x81, 0x04, 0x00, 0x03, 0x2b, 0x81, 0x04, 0x00, 0x10, 0x2b, 0x81, + 0x04, 0x00, 0x11, 0x2b, 0x81, 0x04, 0x00, 0x24, 0x2b, 0x81, 0x04, 0x00, 0x25, 0x2b, 0x81, 0x04, + 0x00, 0x26, 0x2b, 0x81, 0x04, 0x00, 0x27, 0x67, 0x2b, 0x01, 0x04, 0x01, 0x67, 0x2b, 0x01, 0x04, + 0x03, 0x67, 0x2b, 0x01, 0x04, 0x04, 0x67, 0x2b, 0x01, 0x04, 0x05, 0x67, 0x2b, 0x01, 0x04, 0x06, + 0x67, 0x2b, 0x01, 0x04, 0x07, 0x67, 0x2b, 0x01, 0x04, 0x08, 0x67, 0x2b, 0x01, 0x04, 0x09, 0x67, + 0x2b, 0x01, 0x04, 0x0a, 0x67, 0x2b, 0x01, 0x04, 0x0b, 0x67, 0x2b, 0x01, 0x04, 0x0c, 0x55, 0x1d, + 0x20, 0x00, 0x55, 0x1d, 0x21, 0x55, 0x1d, 0x36, 0x2a, 0x83, 0x08, 0x8c, 0x9a, 0x4b, 0x3d, 0x01, + 0x01, 0x01, 0x02, 0x2a, 0x83, 0x08, 0x8c, 0x9a, 0x4b, 0x3d, 0x01, 0x01, 0x01, 0x03, 0x2a, 0x83, + 0x08, 0x8c, 0x9a, 0x4b, 0x3d, 0x01, 0x01, 0x01, 0x04, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, + 0x01, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, 0x15, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, + 0x29, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, 0x04, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, + 0x18, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, 0x2c, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, + 0x03, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, 0x17, 0x03, 0xa2, 0x31, 0x05, 0x03, 0x01, 0x09, + 0x2b, 0x55, 0x1d, 0x09, 0x55, 0x1d, 0x1c, 0x55, 0x1d, 0x1d, 0x2a, 0x83, 0x1a, 0x8c, 0x9a, 0x44, + 0x2a, 0x83, 0x1a, 0x8c, 0x9a, 0x44, 0x01, 0x03, 0x2a, 0x83, 0x1a, 0x8c, 0x9a, 0x44, 0x01, 0x04, + 0x2a, 0x83, 0x1a, 0x8c, 0x9a, 0x44, 0x01, 0x06, 0x2a, 0x83, 0x1a, 0x8c, 0x9a, 0x44, 0x01, 0x05, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x08, 0x01, 0x01, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x08, 0x01, 0x02, + 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x42, 0x0d, 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, + 0x42, 0x1e, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x04, 0x10, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x30, 0x05, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x1b, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, + 0x05, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x19, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x01, 0x2d, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x02, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, + 0x03, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x01, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, + 0x02, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, + 0x04, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, + 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, + 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, + 0x03, 0x01, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02, 0x28, 0xcf, 0x06, 0x03, 0x00, + 0x37, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x2a, 0x85, 0x03, 0x02, 0x09, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x03, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x04, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x09, 0x2a, 0x85, 0x03, + 0x02, 0x02, 0x0a, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x13, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x14, 0x2a, + 0x85, 0x03, 0x02, 0x02, 0x15, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x16, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x17, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x62, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x63, 0x2a, 0x85, 0x03, + 0x02, 0x02, 0x0e, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x0e, 0x00, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x1e, 0x00, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x00, + 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x02, 0x2a, 0x85, + 0x03, 0x02, 0x02, 0x1f, 0x03, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x04, 0x2a, 0x85, 0x03, 0x02, + 0x02, 0x1f, 0x05, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, + 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x20, 0x00, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x20, 0x02, 0x2a, + 0x85, 0x03, 0x02, 0x02, 0x20, 0x03, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x20, 0x04, 0x2a, 0x85, 0x03, + 0x02, 0x02, 0x20, 0x05, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x21, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, + 0x21, 0x02, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x21, 0x03, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x00, + 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x02, 0x2a, 0x85, + 0x03, 0x02, 0x02, 0x23, 0x03, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x24, 0x00, 0x2a, 0x85, 0x03, 0x02, + 0x02, 0x24, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x14, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x14, + 0x02, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x14, 0x03, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x14, 0x04, 0x2a, + 0x85, 0x03, 0x02, 0x09, 0x01, 0x06, 0x01, 0x2a, 0x85, 0x03, 0x02, 0x09, 0x01, 0x05, 0x03, 0x2a, + 0x85, 0x03, 0x02, 0x09, 0x01, 0x05, 0x04, 0x2a, 0x85, 0x03, 0x02, 0x09, 0x01, 0x03, 0x03, 0x2a, + 0x85, 0x03, 0x02, 0x09, 0x01, 0x03, 0x04, 0x2a, 0x85, 0x03, 0x02, 0x09, 0x01, 0x08, 0x01, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x11, 0x02, 0x55, 0x1d, 0x2e, 0x2b, 0x06, 0x01, 0x05, 0x05, + 0x07, 0x08, 0x03, 0x55, 0x04, 0x0e, 0x55, 0x04, 0x0f, 0x55, 0x04, 0x10, 0x55, 0x04, 0x12, 0x55, + 0x04, 0x13, 0x55, 0x04, 0x14, 0x55, 0x04, 0x15, 0x55, 0x04, 0x16, 0x55, 0x04, 0x17, 0x55, 0x04, + 0x18, 0x55, 0x04, 0x19, 0x55, 0x04, 0x1a, 0x55, 0x04, 0x1b, 0x55, 0x04, 0x1c, 0x55, 0x04, 0x1d, + 0x55, 0x04, 0x1e, 0x55, 0x04, 0x1f, 0x55, 0x04, 0x20, 0x55, 0x04, 0x21, 0x55, 0x04, 0x22, 0x55, + 0x04, 0x23, 0x55, 0x04, 0x24, 0x55, 0x04, 0x25, 0x55, 0x04, 0x26, 0x55, 0x04, 0x27, 0x55, 0x04, + 0x28, 0x55, 0x04, 0x2f, 0x55, 0x04, 0x30, 0x55, 0x04, 0x31, 0x55, 0x04, 0x32, 0x55, 0x04, 0x33, + 0x55, 0x04, 0x34, 0x55, 0x04, 0x35, 0x55, 0x04, 0x36, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x09, 0x10, 0x03, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x06, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x01, 0x07, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x08, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x1a, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, + 0x1b, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x1c, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x01, 0x2e, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2f, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x01, 0x30, 0x2a, 0x83, 0x08, 0x8c, 0x9a, 0x4b, 0x3d, 0x01, 0x01, 0x03, 0x02, + 0x2a, 0x83, 0x08, 0x8c, 0x9a, 0x4b, 0x3d, 0x01, 0x01, 0x03, 0x03, 0x2a, 0x83, 0x08, 0x8c, 0x9a, + 0x4b, 0x3d, 0x01, 0x01, 0x03, 0x04, 0x55, 0x1d, 0x25, 0x00, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x07, 0x2b, 0x24, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x2b, 0x24, + 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x01, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x02, + 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x03, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, + 0x01, 0x04, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x05, 0x2b, 0x24, 0x03, 0x03, 0x02, + 0x08, 0x01, 0x01, 0x06, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07, 0x2b, 0x24, 0x03, + 0x03, 0x02, 0x08, 0x01, 0x01, 0x08, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x09, 0x2b, + 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0a, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, + 0x0b, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0c, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, + 0x01, 0x01, 0x0d, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0e, 0x2a, 0x81, 0x7a, 0x01, + 0x81, 0x5f, 0x65, 0x82, 0x00, 0x01, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x2a, 0x85, 0x03, 0x07, 0x01, + 0x01, 0x02, 0x02, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03, 0x2a, 0x85, 0x03, 0x07, 0x01, + 0x02, 0x01, 0x02, 0x01, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x02, 0x01, 0x02, 0x02, 0x2a, 0x85, 0x03, + 0x07, 0x01, 0x02, 0x05, 0x01, 0x01, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x01, 0x01, 0x2a, 0x85, + 0x03, 0x07, 0x01, 0x01, 0x01, 0x02, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x03, 0x02, 0x2a, 0x85, + 0x03, 0x07, 0x01, 0x01, 0x03, 0x03, 0x2b, 0x65, 0x6e, 0x2b, 0x65, 0x6f, 0x2b, 0x65, 0x70, 0x2b, + 0x65, 0x71, 0x2b, 0x65, 0x72, 0x2b, 0x65, 0x73, +}; + +} // namespace + +extern "C" { + +const LibreSslAsn1Object LibreSslObjects_nid_no_patch[LibreSslObjectCount] = { + {"UNDEF", "undefined", 0, 0, nullptr, 0}, + {"rsadsi", "RSA Data Security, Inc.", 1, 6, objectData + 0, 0}, + {"pkcs", "RSA Data Security, Inc. PKCS", 2, 7, objectData + 6, 0}, + {"MD2", "md2", 3, 8, objectData + 13, 0}, + {"MD5", "md5", 4, 8, objectData + 21, 0}, + {"RC4", "rc4", 5, 8, objectData + 29, 0}, + {"rsaEncryption", "rsaEncryption", 6, 9, objectData + 37, 0}, + {"RSA-MD2", "md2WithRSAEncryption", 7, 9, objectData + 46, 0}, + {"RSA-MD5", "md5WithRSAEncryption", 8, 9, objectData + 55, 0}, + {"PBE-MD2-DES", "pbeWithMD2AndDES-CBC", 9, 9, objectData + 64, 0}, + {"PBE-MD5-DES", "pbeWithMD5AndDES-CBC", 10, 9, objectData + 73, 0}, + {"X500", "directory services (X.500)", 11, 1, objectData + 82, 0}, + {"X509", "X509", 12, 2, objectData + 83, 0}, + {"CN", "commonName", 13, 3, objectData + 85, 0}, + {"C", "countryName", 14, 3, objectData + 88, 0}, + {"L", "localityName", 15, 3, objectData + 91, 0}, + {"ST", "stateOrProvinceName", 16, 3, objectData + 94, 0}, + {"O", "organizationName", 17, 3, objectData + 97, 0}, + {"OU", "organizationalUnitName", 18, 3, objectData + 100, 0}, + {"RSA", "rsa", 19, 4, objectData + 103, 0}, + {"pkcs7", "pkcs7", 20, 8, objectData + 107, 0}, + {"pkcs7-data", "pkcs7-data", 21, 9, objectData + 115, 0}, + {"pkcs7-signedData", "pkcs7-signedData", 22, 9, objectData + 124, 0}, + {"pkcs7-envelopedData", "pkcs7-envelopedData", 23, 9, objectData + 133, 0}, + {"pkcs7-signedAndEnvelopedData", "pkcs7-signedAndEnvelopedData", 24, 9, objectData + 142, 0}, + {"pkcs7-digestData", "pkcs7-digestData", 25, 9, objectData + 151, 0}, + {"pkcs7-encryptedData", "pkcs7-encryptedData", 26, 9, objectData + 160, 0}, + {"pkcs3", "pkcs3", 27, 8, objectData + 169, 0}, + {"dhKeyAgreement", "dhKeyAgreement", 28, 9, objectData + 177, 0}, + {"DES-ECB", "des-ecb", 29, 5, objectData + 186, 0}, + {"DES-CFB", "des-cfb", 30, 5, objectData + 191, 0}, + {"DES-CBC", "des-cbc", 31, 5, objectData + 196, 0}, + {"DES-EDE", "des-ede", 32, 5, objectData + 201, 0}, + {"DES-EDE3", "des-ede3", 33, 0, nullptr, 0}, + {"IDEA-CBC", "idea-cbc", 34, 11, objectData + 206, 0}, + {"IDEA-CFB", "idea-cfb", 35, 0, nullptr, 0}, + {"IDEA-ECB", "idea-ecb", 36, 0, nullptr, 0}, + {"RC2-CBC", "rc2-cbc", 37, 8, objectData + 217, 0}, + {"RC2-ECB", "rc2-ecb", 38, 0, nullptr, 0}, + {"RC2-CFB", "rc2-cfb", 39, 0, nullptr, 0}, + {"RC2-OFB", "rc2-ofb", 40, 0, nullptr, 0}, + {"SHA", "sha", 41, 5, objectData + 225, 0}, + {"RSA-SHA", "shaWithRSAEncryption", 42, 5, objectData + 230, 0}, + {"DES-EDE-CBC", "des-ede-cbc", 43, 0, nullptr, 0}, + {"DES-EDE3-CBC", "des-ede3-cbc", 44, 8, objectData + 235, 0}, + {"DES-OFB", "des-ofb", 45, 5, objectData + 243, 0}, + {"IDEA-OFB", "idea-ofb", 46, 0, nullptr, 0}, + {"pkcs9", "pkcs9", 47, 8, objectData + 248, 0}, + {"emailAddress", "emailAddress", 48, 9, objectData + 256, 0}, + {"unstructuredName", "unstructuredName", 49, 9, objectData + 265, 0}, + {"contentType", "contentType", 50, 9, objectData + 274, 0}, + {"messageDigest", "messageDigest", 51, 9, objectData + 283, 0}, + {"signingTime", "signingTime", 52, 9, objectData + 292, 0}, + {"countersignature", "countersignature", 53, 9, objectData + 301, 0}, + {"challengePassword", "challengePassword", 54, 9, objectData + 310, 0}, + {"unstructuredAddress", "unstructuredAddress", 55, 9, objectData + 319, 0}, + {"extendedCertificateAttributes", "extendedCertificateAttributes", 56, 9, objectData + 328, 0}, + {"Netscape", "Netscape Communications Corp.", 57, 7, objectData + 337, 0}, + {"nsCertExt", "Netscape Certificate Extension", 58, 8, objectData + 344, 0}, + {"nsDataType", "Netscape Data Type", 59, 8, objectData + 352, 0}, + {"DES-EDE-CFB", "des-ede-cfb", 60, 0, nullptr, 0}, + {"DES-EDE3-CFB", "des-ede3-cfb", 61, 0, nullptr, 0}, + {"DES-EDE-OFB", "des-ede-ofb", 62, 0, nullptr, 0}, + {"DES-EDE3-OFB", "des-ede3-ofb", 63, 0, nullptr, 0}, + {"SHA1", "sha1", 64, 5, objectData + 360, 0}, + {"RSA-SHA1", "sha1WithRSAEncryption", 65, 9, objectData + 365, 0}, + {"DSA-SHA", "dsaWithSHA", 66, 5, objectData + 374, 0}, + {"DSA-old", "dsaEncryption-old", 67, 5, objectData + 379, 0}, + {"PBE-SHA1-RC2-64", "pbeWithSHA1AndRC2-CBC", 68, 9, objectData + 384, 0}, + {"PBKDF2", "PBKDF2", 69, 9, objectData + 393, 0}, + {"DSA-SHA1-old", "dsaWithSHA1-old", 70, 5, objectData + 402, 0}, + {"nsCertType", "Netscape Cert Type", 71, 9, objectData + 407, 0}, + {"nsBaseUrl", "Netscape Base Url", 72, 9, objectData + 416, 0}, + {"nsRevocationUrl", "Netscape Revocation Url", 73, 9, objectData + 425, 0}, + {"nsCaRevocationUrl", "Netscape CA Revocation Url", 74, 9, objectData + 434, 0}, + {"nsRenewalUrl", "Netscape Renewal Url", 75, 9, objectData + 443, 0}, + {"nsCaPolicyUrl", "Netscape CA Policy Url", 76, 9, objectData + 452, 0}, + {"nsSslServerName", "Netscape SSL Server Name", 77, 9, objectData + 461, 0}, + {"nsComment", "Netscape Comment", 78, 9, objectData + 470, 0}, + {"nsCertSequence", "Netscape Certificate Sequence", 79, 9, objectData + 479, 0}, + {"DESX-CBC", "desx-cbc", 80, 0, nullptr, 0}, + {"id-ce", "id-ce", 81, 2, objectData + 488, 0}, + {"subjectKeyIdentifier", "X509v3 Subject Key Identifier", 82, 3, objectData + 490, 0}, + {"keyUsage", "X509v3 Key Usage", 83, 3, objectData + 493, 0}, + {"privateKeyUsagePeriod", "X509v3 Private Key Usage Period", 84, 3, objectData + 496, 0}, + {"subjectAltName", "X509v3 Subject Alternative Name", 85, 3, objectData + 499, 0}, + {"issuerAltName", "X509v3 Issuer Alternative Name", 86, 3, objectData + 502, 0}, + {"basicConstraints", "X509v3 Basic Constraints", 87, 3, objectData + 505, 0}, + {"crlNumber", "X509v3 CRL Number", 88, 3, objectData + 508, 0}, + {"certificatePolicies", "X509v3 Certificate Policies", 89, 3, objectData + 511, 0}, + {"authorityKeyIdentifier", "X509v3 Authority Key Identifier", 90, 3, objectData + 514, 0}, + {"BF-CBC", "bf-cbc", 91, 9, objectData + 517, 0}, + {"BF-ECB", "bf-ecb", 92, 0, nullptr, 0}, + {"BF-CFB", "bf-cfb", 93, 0, nullptr, 0}, + {"BF-OFB", "bf-ofb", 94, 0, nullptr, 0}, + {"MDC2", "mdc2", 95, 4, objectData + 526, 0}, + {"RSA-MDC2", "mdc2WithRSA", 96, 4, objectData + 530, 0}, + {"RC4-40", "rc4-40", 97, 0, nullptr, 0}, + {"RC2-40-CBC", "rc2-40-cbc", 98, 0, nullptr, 0}, + {"GN", "givenName", 99, 3, objectData + 534, 0}, + {"SN", "surname", 100, 3, objectData + 537, 0}, + {"initials", "initials", 101, 3, objectData + 540, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {"crlDistributionPoints", "X509v3 CRL Distribution Points", 103, 3, objectData + 543, 0}, + {"RSA-NP-MD5", "md5WithRSA", 104, 5, objectData + 546, 0}, + {"serialNumber", "serialNumber", 105, 3, objectData + 551, 0}, + {"title", "title", 106, 3, objectData + 554, 0}, + {"description", "description", 107, 3, objectData + 557, 0}, + {"CAST5-CBC", "cast5-cbc", 108, 9, objectData + 560, 0}, + {"CAST5-ECB", "cast5-ecb", 109, 0, nullptr, 0}, + {"CAST5-CFB", "cast5-cfb", 110, 0, nullptr, 0}, + {"CAST5-OFB", "cast5-ofb", 111, 0, nullptr, 0}, + {"pbeWithMD5AndCast5CBC", "pbeWithMD5AndCast5CBC", 112, 9, objectData + 569, 0}, + {"DSA-SHA1", "dsaWithSHA1", 113, 7, objectData + 578, 0}, + {"MD5-SHA1", "md5-sha1", 114, 0, nullptr, 0}, + {"RSA-SHA1-2", "sha1WithRSA", 115, 5, objectData + 585, 0}, + {"DSA", "dsaEncryption", 116, 7, objectData + 590, 0}, + {"RIPEMD160", "ripemd160", 117, 5, objectData + 597, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {"RSA-RIPEMD160", "ripemd160WithRSA", 119, 6, objectData + 602, 0}, + {"RC5-CBC", "rc5-cbc", 120, 8, objectData + 608, 0}, + {"RC5-ECB", "rc5-ecb", 121, 0, nullptr, 0}, + {"RC5-CFB", "rc5-cfb", 122, 0, nullptr, 0}, + {"RC5-OFB", "rc5-ofb", 123, 0, nullptr, 0}, + {"RLE", "run length compression", 124, 6, objectData + 616, 0}, + {"ZLIB", "zlib compression", 125, 11, objectData + 622, 0}, + {"extendedKeyUsage", "X509v3 Extended Key Usage", 126, 3, objectData + 633, 0}, + {"PKIX", "PKIX", 127, 6, objectData + 636, 0}, + {"id-kp", "id-kp", 128, 7, objectData + 642, 0}, + {"serverAuth", "TLS Web Server Authentication", 129, 8, objectData + 649, 0}, + {"clientAuth", "TLS Web Client Authentication", 130, 8, objectData + 657, 0}, + {"codeSigning", "Code Signing", 131, 8, objectData + 665, 0}, + {"emailProtection", "E-mail Protection", 132, 8, objectData + 673, 0}, + {"timeStamping", "Time Stamping", 133, 8, objectData + 681, 0}, + {"msCodeInd", "Microsoft Individual Code Signing", 134, 10, objectData + 689, 0}, + {"msCodeCom", "Microsoft Commercial Code Signing", 135, 10, objectData + 699, 0}, + {"msCTLSign", "Microsoft Trust List Signing", 136, 10, objectData + 709, 0}, + {"msSGC", "Microsoft Server Gated Crypto", 137, 10, objectData + 719, 0}, + {"msEFS", "Microsoft Encrypted File System", 138, 10, objectData + 729, 0}, + {"nsSGC", "Netscape Server Gated Crypto", 139, 9, objectData + 739, 0}, + {"deltaCRL", "X509v3 Delta CRL Indicator", 140, 3, objectData + 748, 0}, + {"CRLReason", "X509v3 CRL Reason Code", 141, 3, objectData + 751, 0}, + {"invalidityDate", "Invalidity Date", 142, 3, objectData + 754, 0}, + {"SXNetID", "Strong Extranet ID", 143, 5, objectData + 757, 0}, + {"PBE-SHA1-RC4-128", "pbeWithSHA1And128BitRC4", 144, 10, objectData + 762, 0}, + {"PBE-SHA1-RC4-40", "pbeWithSHA1And40BitRC4", 145, 10, objectData + 772, 0}, + {"PBE-SHA1-3DES", "pbeWithSHA1And3-KeyTripleDES-CBC", 146, 10, objectData + 782, 0}, + {"PBE-SHA1-2DES", "pbeWithSHA1And2-KeyTripleDES-CBC", 147, 10, objectData + 792, 0}, + {"PBE-SHA1-RC2-128", "pbeWithSHA1And128BitRC2-CBC", 148, 10, objectData + 802, 0}, + {"PBE-SHA1-RC2-40", "pbeWithSHA1And40BitRC2-CBC", 149, 10, objectData + 812, 0}, + {"keyBag", "keyBag", 150, 11, objectData + 822, 0}, + {"pkcs8ShroudedKeyBag", "pkcs8ShroudedKeyBag", 151, 11, objectData + 833, 0}, + {"certBag", "certBag", 152, 11, objectData + 844, 0}, + {"crlBag", "crlBag", 153, 11, objectData + 855, 0}, + {"secretBag", "secretBag", 154, 11, objectData + 866, 0}, + {"safeContentsBag", "safeContentsBag", 155, 11, objectData + 877, 0}, + {"friendlyName", "friendlyName", 156, 9, objectData + 888, 0}, + {"localKeyID", "localKeyID", 157, 9, objectData + 897, 0}, + {"x509Certificate", "x509Certificate", 158, 10, objectData + 906, 0}, + {"sdsiCertificate", "sdsiCertificate", 159, 10, objectData + 916, 0}, + {"x509Crl", "x509Crl", 160, 10, objectData + 926, 0}, + {"PBES2", "PBES2", 161, 9, objectData + 936, 0}, + {"PBMAC1", "PBMAC1", 162, 9, objectData + 945, 0}, + {"hmacWithSHA1", "hmacWithSHA1", 163, 8, objectData + 954, 0}, + {"id-qt-cps", "Policy Qualifier CPS", 164, 8, objectData + 962, 0}, + {"id-qt-unotice", "Policy Qualifier User Notice", 165, 8, objectData + 970, 0}, + {"RC2-64-CBC", "rc2-64-cbc", 166, 0, nullptr, 0}, + {"SMIME-CAPS", "S/MIME Capabilities", 167, 9, objectData + 978, 0}, + {"PBE-MD2-RC2-64", "pbeWithMD2AndRC2-CBC", 168, 9, objectData + 987, 0}, + {"PBE-MD5-RC2-64", "pbeWithMD5AndRC2-CBC", 169, 9, objectData + 996, 0}, + {"PBE-SHA1-DES", "pbeWithSHA1AndDES-CBC", 170, 9, objectData + 1005, 0}, + {"msExtReq", "Microsoft Extension Request", 171, 10, objectData + 1014, 0}, + {"extReq", "Extension Request", 172, 9, objectData + 1024, 0}, + {"name", "name", 173, 3, objectData + 1033, 0}, + {"dnQualifier", "dnQualifier", 174, 3, objectData + 1036, 0}, + {"id-pe", "id-pe", 175, 7, objectData + 1039, 0}, + {"id-ad", "id-ad", 176, 7, objectData + 1046, 0}, + {"authorityInfoAccess", "Authority Information Access", 177, 8, objectData + 1053, 0}, + {"OCSP", "OCSP", 178, 8, objectData + 1061, 0}, + {"caIssuers", "CA Issuers", 179, 8, objectData + 1069, 0}, + {"OCSPSigning", "OCSP Signing", 180, 8, objectData + 1077, 0}, + {"ISO", "iso", 181, 0, nullptr, 0}, + {"member-body", "ISO Member Body", 182, 1, objectData + 1085, 0}, + {"ISO-US", "ISO US Member Body", 183, 3, objectData + 1086, 0}, + {"X9-57", "X9.57", 184, 5, objectData + 1089, 0}, + {"X9cm", "X9.57 CM ?", 185, 6, objectData + 1094, 0}, + {"pkcs1", "pkcs1", 186, 8, objectData + 1100, 0}, + {"pkcs5", "pkcs5", 187, 8, objectData + 1108, 0}, + {"SMIME", "S/MIME", 188, 9, objectData + 1116, 0}, + {"id-smime-mod", "id-smime-mod", 189, 10, objectData + 1125, 0}, + {"id-smime-ct", "id-smime-ct", 190, 10, objectData + 1135, 0}, + {"id-smime-aa", "id-smime-aa", 191, 10, objectData + 1145, 0}, + {"id-smime-alg", "id-smime-alg", 192, 10, objectData + 1155, 0}, + {"id-smime-cd", "id-smime-cd", 193, 10, objectData + 1165, 0}, + {"id-smime-spq", "id-smime-spq", 194, 10, objectData + 1175, 0}, + {"id-smime-cti", "id-smime-cti", 195, 10, objectData + 1185, 0}, + {"id-smime-mod-cms", "id-smime-mod-cms", 196, 11, objectData + 1195, 0}, + {"id-smime-mod-ess", "id-smime-mod-ess", 197, 11, objectData + 1206, 0}, + {"id-smime-mod-oid", "id-smime-mod-oid", 198, 11, objectData + 1217, 0}, + {"id-smime-mod-msg-v3", "id-smime-mod-msg-v3", 199, 11, objectData + 1228, 0}, + {"id-smime-mod-ets-eSignature-88", "id-smime-mod-ets-eSignature-88", 200, 11, objectData + 1239, 0}, + {"id-smime-mod-ets-eSignature-97", "id-smime-mod-ets-eSignature-97", 201, 11, objectData + 1250, 0}, + {"id-smime-mod-ets-eSigPolicy-88", "id-smime-mod-ets-eSigPolicy-88", 202, 11, objectData + 1261, 0}, + {"id-smime-mod-ets-eSigPolicy-97", "id-smime-mod-ets-eSigPolicy-97", 203, 11, objectData + 1272, 0}, + {"id-smime-ct-receipt", "id-smime-ct-receipt", 204, 11, objectData + 1283, 0}, + {"id-smime-ct-authData", "id-smime-ct-authData", 205, 11, objectData + 1294, 0}, + {"id-smime-ct-publishCert", "id-smime-ct-publishCert", 206, 11, objectData + 1305, 0}, + {"id-smime-ct-TSTInfo", "id-smime-ct-TSTInfo", 207, 11, objectData + 1316, 0}, + {"id-smime-ct-TDTInfo", "id-smime-ct-TDTInfo", 208, 11, objectData + 1327, 0}, + {"id-smime-ct-contentInfo", "id-smime-ct-contentInfo", 209, 11, objectData + 1338, 0}, + {"id-smime-ct-DVCSRequestData", "id-smime-ct-DVCSRequestData", 210, 11, objectData + 1349, 0}, + {"id-smime-ct-DVCSResponseData", "id-smime-ct-DVCSResponseData", 211, 11, objectData + 1360, 0}, + {"id-smime-aa-receiptRequest", "id-smime-aa-receiptRequest", 212, 11, objectData + 1371, 0}, + {"id-smime-aa-securityLabel", "id-smime-aa-securityLabel", 213, 11, objectData + 1382, 0}, + {"id-smime-aa-mlExpandHistory", "id-smime-aa-mlExpandHistory", 214, 11, objectData + 1393, 0}, + {"id-smime-aa-contentHint", "id-smime-aa-contentHint", 215, 11, objectData + 1404, 0}, + {"id-smime-aa-msgSigDigest", "id-smime-aa-msgSigDigest", 216, 11, objectData + 1415, 0}, + {"id-smime-aa-encapContentType", "id-smime-aa-encapContentType", 217, 11, objectData + 1426, 0}, + {"id-smime-aa-contentIdentifier", "id-smime-aa-contentIdentifier", 218, 11, objectData + 1437, 0}, + {"id-smime-aa-macValue", "id-smime-aa-macValue", 219, 11, objectData + 1448, 0}, + {"id-smime-aa-equivalentLabels", "id-smime-aa-equivalentLabels", 220, 11, objectData + 1459, 0}, + {"id-smime-aa-contentReference", "id-smime-aa-contentReference", 221, 11, objectData + 1470, 0}, + {"id-smime-aa-encrypKeyPref", "id-smime-aa-encrypKeyPref", 222, 11, objectData + 1481, 0}, + {"id-smime-aa-signingCertificate", "id-smime-aa-signingCertificate", 223, 11, objectData + 1492, 0}, + {"id-smime-aa-smimeEncryptCerts", "id-smime-aa-smimeEncryptCerts", 224, 11, objectData + 1503, 0}, + {"id-smime-aa-timeStampToken", "id-smime-aa-timeStampToken", 225, 11, objectData + 1514, 0}, + {"id-smime-aa-ets-sigPolicyId", "id-smime-aa-ets-sigPolicyId", 226, 11, objectData + 1525, 0}, + {"id-smime-aa-ets-commitmentType", "id-smime-aa-ets-commitmentType", 227, 11, objectData + 1536, 0}, + {"id-smime-aa-ets-signerLocation", "id-smime-aa-ets-signerLocation", 228, 11, objectData + 1547, 0}, + {"id-smime-aa-ets-signerAttr", "id-smime-aa-ets-signerAttr", 229, 11, objectData + 1558, 0}, + {"id-smime-aa-ets-otherSigCert", "id-smime-aa-ets-otherSigCert", 230, 11, objectData + 1569, 0}, + {"id-smime-aa-ets-contentTimestamp", "id-smime-aa-ets-contentTimestamp", 231, 11, objectData + 1580, 0}, + {"id-smime-aa-ets-CertificateRefs", "id-smime-aa-ets-CertificateRefs", 232, 11, objectData + 1591, 0}, + {"id-smime-aa-ets-RevocationRefs", "id-smime-aa-ets-RevocationRefs", 233, 11, objectData + 1602, 0}, + {"id-smime-aa-ets-certValues", "id-smime-aa-ets-certValues", 234, 11, objectData + 1613, 0}, + {"id-smime-aa-ets-revocationValues", "id-smime-aa-ets-revocationValues", 235, 11, objectData + 1624, 0}, + {"id-smime-aa-ets-escTimeStamp", "id-smime-aa-ets-escTimeStamp", 236, 11, objectData + 1635, 0}, + {"id-smime-aa-ets-certCRLTimestamp", "id-smime-aa-ets-certCRLTimestamp", 237, 11, objectData + 1646, 0}, + {"id-smime-aa-ets-archiveTimeStamp", "id-smime-aa-ets-archiveTimeStamp", 238, 11, objectData + 1657, 0}, + {"id-smime-aa-signatureType", "id-smime-aa-signatureType", 239, 11, objectData + 1668, 0}, + {"id-smime-aa-dvcs-dvc", "id-smime-aa-dvcs-dvc", 240, 11, objectData + 1679, 0}, + {"id-smime-alg-ESDHwith3DES", "id-smime-alg-ESDHwith3DES", 241, 11, objectData + 1690, 0}, + {"id-smime-alg-ESDHwithRC2", "id-smime-alg-ESDHwithRC2", 242, 11, objectData + 1701, 0}, + {"id-smime-alg-3DESwrap", "id-smime-alg-3DESwrap", 243, 11, objectData + 1712, 0}, + {"id-smime-alg-RC2wrap", "id-smime-alg-RC2wrap", 244, 11, objectData + 1723, 0}, + {"id-smime-alg-ESDH", "id-smime-alg-ESDH", 245, 11, objectData + 1734, 0}, + {"id-smime-alg-CMS3DESwrap", "id-smime-alg-CMS3DESwrap", 246, 11, objectData + 1745, 0}, + {"id-smime-alg-CMSRC2wrap", "id-smime-alg-CMSRC2wrap", 247, 11, objectData + 1756, 0}, + {"id-smime-cd-ldap", "id-smime-cd-ldap", 248, 11, objectData + 1767, 0}, + {"id-smime-spq-ets-sqt-uri", "id-smime-spq-ets-sqt-uri", 249, 11, objectData + 1778, 0}, + {"id-smime-spq-ets-sqt-unotice", "id-smime-spq-ets-sqt-unotice", 250, 11, objectData + 1789, 0}, + {"id-smime-cti-ets-proofOfOrigin", "id-smime-cti-ets-proofOfOrigin", 251, 11, objectData + 1800, 0}, + {"id-smime-cti-ets-proofOfReceipt", "id-smime-cti-ets-proofOfReceipt", 252, 11, objectData + 1811, 0}, + {"id-smime-cti-ets-proofOfDelivery", "id-smime-cti-ets-proofOfDelivery", 253, 11, objectData + 1822, 0}, + {"id-smime-cti-ets-proofOfSender", "id-smime-cti-ets-proofOfSender", 254, 11, objectData + 1833, 0}, + {"id-smime-cti-ets-proofOfApproval", "id-smime-cti-ets-proofOfApproval", 255, 11, objectData + 1844, 0}, + {"id-smime-cti-ets-proofOfCreation", "id-smime-cti-ets-proofOfCreation", 256, 11, objectData + 1855, 0}, + {"MD4", "md4", 257, 8, objectData + 1866, 0}, + {"id-pkix-mod", "id-pkix-mod", 258, 7, objectData + 1874, 0}, + {"id-qt", "id-qt", 259, 7, objectData + 1881, 0}, + {"id-it", "id-it", 260, 7, objectData + 1888, 0}, + {"id-pkip", "id-pkip", 261, 7, objectData + 1895, 0}, + {"id-alg", "id-alg", 262, 7, objectData + 1902, 0}, + {"id-cmc", "id-cmc", 263, 7, objectData + 1909, 0}, + {"id-on", "id-on", 264, 7, objectData + 1916, 0}, + {"id-pda", "id-pda", 265, 7, objectData + 1923, 0}, + {"id-aca", "id-aca", 266, 7, objectData + 1930, 0}, + {"id-qcs", "id-qcs", 267, 7, objectData + 1937, 0}, + {"id-cct", "id-cct", 268, 7, objectData + 1944, 0}, + {"id-pkix1-explicit-88", "id-pkix1-explicit-88", 269, 8, objectData + 1951, 0}, + {"id-pkix1-implicit-88", "id-pkix1-implicit-88", 270, 8, objectData + 1959, 0}, + {"id-pkix1-explicit-93", "id-pkix1-explicit-93", 271, 8, objectData + 1967, 0}, + {"id-pkix1-implicit-93", "id-pkix1-implicit-93", 272, 8, objectData + 1975, 0}, + {"id-mod-crmf", "id-mod-crmf", 273, 8, objectData + 1983, 0}, + {"id-mod-cmc", "id-mod-cmc", 274, 8, objectData + 1991, 0}, + {"id-mod-kea-profile-88", "id-mod-kea-profile-88", 275, 8, objectData + 1999, 0}, + {"id-mod-kea-profile-93", "id-mod-kea-profile-93", 276, 8, objectData + 2007, 0}, + {"id-mod-cmp", "id-mod-cmp", 277, 8, objectData + 2015, 0}, + {"id-mod-qualified-cert-88", "id-mod-qualified-cert-88", 278, 8, objectData + 2023, 0}, + {"id-mod-qualified-cert-93", "id-mod-qualified-cert-93", 279, 8, objectData + 2031, 0}, + {"id-mod-attribute-cert", "id-mod-attribute-cert", 280, 8, objectData + 2039, 0}, + {"id-mod-timestamp-protocol", "id-mod-timestamp-protocol", 281, 8, objectData + 2047, 0}, + {"id-mod-ocsp", "id-mod-ocsp", 282, 8, objectData + 2055, 0}, + {"id-mod-dvcs", "id-mod-dvcs", 283, 8, objectData + 2063, 0}, + {"id-mod-cmp2000", "id-mod-cmp2000", 284, 8, objectData + 2071, 0}, + {"biometricInfo", "Biometric Info", 285, 8, objectData + 2079, 0}, + {"qcStatements", "qcStatements", 286, 8, objectData + 2087, 0}, + {"ac-auditEntity", "ac-auditEntity", 287, 8, objectData + 2095, 0}, + {"ac-targeting", "ac-targeting", 288, 8, objectData + 2103, 0}, + {"aaControls", "aaControls", 289, 8, objectData + 2111, 0}, + {"sbgp-ipAddrBlock", "sbgp-ipAddrBlock", 290, 8, objectData + 2119, 0}, + {"sbgp-autonomousSysNum", "sbgp-autonomousSysNum", 291, 8, objectData + 2127, 0}, + {"sbgp-routerIdentifier", "sbgp-routerIdentifier", 292, 8, objectData + 2135, 0}, + {"textNotice", "textNotice", 293, 8, objectData + 2143, 0}, + {"ipsecEndSystem", "IPSec End System", 294, 8, objectData + 2151, 0}, + {"ipsecTunnel", "IPSec Tunnel", 295, 8, objectData + 2159, 0}, + {"ipsecUser", "IPSec User", 296, 8, objectData + 2167, 0}, + {"DVCS", "dvcs", 297, 8, objectData + 2175, 0}, + {"id-it-caProtEncCert", "id-it-caProtEncCert", 298, 8, objectData + 2183, 0}, + {"id-it-signKeyPairTypes", "id-it-signKeyPairTypes", 299, 8, objectData + 2191, 0}, + {"id-it-encKeyPairTypes", "id-it-encKeyPairTypes", 300, 8, objectData + 2199, 0}, + {"id-it-preferredSymmAlg", "id-it-preferredSymmAlg", 301, 8, objectData + 2207, 0}, + {"id-it-caKeyUpdateInfo", "id-it-caKeyUpdateInfo", 302, 8, objectData + 2215, 0}, + {"id-it-currentCRL", "id-it-currentCRL", 303, 8, objectData + 2223, 0}, + {"id-it-unsupportedOIDs", "id-it-unsupportedOIDs", 304, 8, objectData + 2231, 0}, + {"id-it-subscriptionRequest", "id-it-subscriptionRequest", 305, 8, objectData + 2239, 0}, + {"id-it-subscriptionResponse", "id-it-subscriptionResponse", 306, 8, objectData + 2247, 0}, + {"id-it-keyPairParamReq", "id-it-keyPairParamReq", 307, 8, objectData + 2255, 0}, + {"id-it-keyPairParamRep", "id-it-keyPairParamRep", 308, 8, objectData + 2263, 0}, + {"id-it-revPassphrase", "id-it-revPassphrase", 309, 8, objectData + 2271, 0}, + {"id-it-implicitConfirm", "id-it-implicitConfirm", 310, 8, objectData + 2279, 0}, + {"id-it-confirmWaitTime", "id-it-confirmWaitTime", 311, 8, objectData + 2287, 0}, + {"id-it-origPKIMessage", "id-it-origPKIMessage", 312, 8, objectData + 2295, 0}, + {"id-regCtrl", "id-regCtrl", 313, 8, objectData + 2303, 0}, + {"id-regInfo", "id-regInfo", 314, 8, objectData + 2311, 0}, + {"id-regCtrl-regToken", "id-regCtrl-regToken", 315, 9, objectData + 2319, 0}, + {"id-regCtrl-authenticator", "id-regCtrl-authenticator", 316, 9, objectData + 2328, 0}, + {"id-regCtrl-pkiPublicationInfo", "id-regCtrl-pkiPublicationInfo", 317, 9, objectData + 2337, 0}, + {"id-regCtrl-pkiArchiveOptions", "id-regCtrl-pkiArchiveOptions", 318, 9, objectData + 2346, 0}, + {"id-regCtrl-oldCertID", "id-regCtrl-oldCertID", 319, 9, objectData + 2355, 0}, + {"id-regCtrl-protocolEncrKey", "id-regCtrl-protocolEncrKey", 320, 9, objectData + 2364, 0}, + {"id-regInfo-utf8Pairs", "id-regInfo-utf8Pairs", 321, 9, objectData + 2373, 0}, + {"id-regInfo-certReq", "id-regInfo-certReq", 322, 9, objectData + 2382, 0}, + {"id-alg-des40", "id-alg-des40", 323, 8, objectData + 2391, 0}, + {"id-alg-noSignature", "id-alg-noSignature", 324, 8, objectData + 2399, 0}, + {"id-alg-dh-sig-hmac-sha1", "id-alg-dh-sig-hmac-sha1", 325, 8, objectData + 2407, 0}, + {"id-alg-dh-pop", "id-alg-dh-pop", 326, 8, objectData + 2415, 0}, + {"id-cmc-statusInfo", "id-cmc-statusInfo", 327, 8, objectData + 2423, 0}, + {"id-cmc-identification", "id-cmc-identification", 328, 8, objectData + 2431, 0}, + {"id-cmc-identityProof", "id-cmc-identityProof", 329, 8, objectData + 2439, 0}, + {"id-cmc-dataReturn", "id-cmc-dataReturn", 330, 8, objectData + 2447, 0}, + {"id-cmc-transactionId", "id-cmc-transactionId", 331, 8, objectData + 2455, 0}, + {"id-cmc-senderNonce", "id-cmc-senderNonce", 332, 8, objectData + 2463, 0}, + {"id-cmc-recipientNonce", "id-cmc-recipientNonce", 333, 8, objectData + 2471, 0}, + {"id-cmc-addExtensions", "id-cmc-addExtensions", 334, 8, objectData + 2479, 0}, + {"id-cmc-encryptedPOP", "id-cmc-encryptedPOP", 335, 8, objectData + 2487, 0}, + {"id-cmc-decryptedPOP", "id-cmc-decryptedPOP", 336, 8, objectData + 2495, 0}, + {"id-cmc-lraPOPWitness", "id-cmc-lraPOPWitness", 337, 8, objectData + 2503, 0}, + {"id-cmc-getCert", "id-cmc-getCert", 338, 8, objectData + 2511, 0}, + {"id-cmc-getCRL", "id-cmc-getCRL", 339, 8, objectData + 2519, 0}, + {"id-cmc-revokeRequest", "id-cmc-revokeRequest", 340, 8, objectData + 2527, 0}, + {"id-cmc-regInfo", "id-cmc-regInfo", 341, 8, objectData + 2535, 0}, + {"id-cmc-responseInfo", "id-cmc-responseInfo", 342, 8, objectData + 2543, 0}, + {"id-cmc-queryPending", "id-cmc-queryPending", 343, 8, objectData + 2551, 0}, + {"id-cmc-popLinkRandom", "id-cmc-popLinkRandom", 344, 8, objectData + 2559, 0}, + {"id-cmc-popLinkWitness", "id-cmc-popLinkWitness", 345, 8, objectData + 2567, 0}, + {"id-cmc-confirmCertAcceptance", "id-cmc-confirmCertAcceptance", 346, 8, objectData + 2575, 0}, + {"id-on-personalData", "id-on-personalData", 347, 8, objectData + 2583, 0}, + {"id-pda-dateOfBirth", "id-pda-dateOfBirth", 348, 8, objectData + 2591, 0}, + {"id-pda-placeOfBirth", "id-pda-placeOfBirth", 349, 8, objectData + 2599, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {"id-pda-gender", "id-pda-gender", 351, 8, objectData + 2607, 0}, + {"id-pda-countryOfCitizenship", "id-pda-countryOfCitizenship", 352, 8, objectData + 2615, 0}, + {"id-pda-countryOfResidence", "id-pda-countryOfResidence", 353, 8, objectData + 2623, 0}, + {"id-aca-authenticationInfo", "id-aca-authenticationInfo", 354, 8, objectData + 2631, 0}, + {"id-aca-accessIdentity", "id-aca-accessIdentity", 355, 8, objectData + 2639, 0}, + {"id-aca-chargingIdentity", "id-aca-chargingIdentity", 356, 8, objectData + 2647, 0}, + {"id-aca-group", "id-aca-group", 357, 8, objectData + 2655, 0}, + {"id-aca-role", "id-aca-role", 358, 8, objectData + 2663, 0}, + {"id-qcs-pkixQCSyntax-v1", "id-qcs-pkixQCSyntax-v1", 359, 8, objectData + 2671, 0}, + {"id-cct-crs", "id-cct-crs", 360, 8, objectData + 2679, 0}, + {"id-cct-PKIData", "id-cct-PKIData", 361, 8, objectData + 2687, 0}, + {"id-cct-PKIResponse", "id-cct-PKIResponse", 362, 8, objectData + 2695, 0}, + {"ad_timestamping", "AD Time Stamping", 363, 8, objectData + 2703, 0}, + {"AD_DVCS", "ad dvcs", 364, 8, objectData + 2711, 0}, + {"basicOCSPResponse", "Basic OCSP Response", 365, 9, objectData + 2719, 0}, + {"Nonce", "OCSP Nonce", 366, 9, objectData + 2728, 0}, + {"CrlID", "OCSP CRL ID", 367, 9, objectData + 2737, 0}, + {"acceptableResponses", "Acceptable OCSP Responses", 368, 9, objectData + 2746, 0}, + {"noCheck", "OCSP No Check", 369, 9, objectData + 2755, 0}, + {"archiveCutoff", "OCSP Archive Cutoff", 370, 9, objectData + 2764, 0}, + {"serviceLocator", "OCSP Service Locator", 371, 9, objectData + 2773, 0}, + {"extendedStatus", "Extended OCSP Status", 372, 9, objectData + 2782, 0}, + {"valid", "valid", 373, 9, objectData + 2791, 0}, + {"path", "path", 374, 9, objectData + 2800, 0}, + {"trustRoot", "Trust Root", 375, 9, objectData + 2809, 0}, + {"algorithm", "algorithm", 376, 4, objectData + 2818, 0}, + {"rsaSignature", "rsaSignature", 377, 5, objectData + 2822, 0}, + {"X500algorithms", "directory services - algorithms", 378, 2, objectData + 2827, 0}, + {"ORG", "org", 379, 1, objectData + 2829, 0}, + {"DOD", "dod", 380, 2, objectData + 2830, 0}, + {"IANA", "iana", 381, 3, objectData + 2832, 0}, + {"directory", "Directory", 382, 4, objectData + 2835, 0}, + {"mgmt", "Management", 383, 4, objectData + 2839, 0}, + {"experimental", "Experimental", 384, 4, objectData + 2843, 0}, + {"private", "Private", 385, 4, objectData + 2847, 0}, + {"security", "Security", 386, 4, objectData + 2851, 0}, + {"snmpv2", "SNMPv2", 387, 4, objectData + 2855, 0}, + {"Mail", "Mail", 388, 4, objectData + 2859, 0}, + {"enterprises", "Enterprises", 389, 5, objectData + 2863, 0}, + {"dcobject", "dcObject", 390, 9, objectData + 2868, 0}, + {"DC", "domainComponent", 391, 10, objectData + 2877, 0}, + {"domain", "Domain", 392, 10, objectData + 2887, 0}, + {"NULL", "NULL", 393, 0, nullptr, 0}, + {"selected-attribute-types", "Selected Attribute Types", 394, 3, objectData + 2897, 0}, + {"clearance", "clearance", 395, 4, objectData + 2900, 0}, + {"RSA-MD4", "md4WithRSAEncryption", 396, 9, objectData + 2904, 0}, + {"ac-proxying", "ac-proxying", 397, 8, objectData + 2913, 0}, + {"subjectInfoAccess", "Subject Information Access", 398, 8, objectData + 2921, 0}, + {"id-aca-encAttrs", "id-aca-encAttrs", 399, 8, objectData + 2929, 0}, + {"role", "role", 400, 3, objectData + 2937, 0}, + {"policyConstraints", "X509v3 Policy Constraints", 401, 3, objectData + 2940, 0}, + {"targetInformation", "X509v3 AC Targeting", 402, 3, objectData + 2943, 0}, + {"noRevAvail", "X509v3 No Revocation Available", 403, 3, objectData + 2946, 0}, + {"NULL", "NULL", 404, 0, nullptr, 0}, + {"ansi-X9-62", "ANSI X9.62", 405, 5, objectData + 2949, 0}, + {"prime-field", "prime-field", 406, 7, objectData + 2954, 0}, + {"characteristic-two-field", "characteristic-two-field", 407, 7, objectData + 2961, 0}, + {"id-ecPublicKey", "id-ecPublicKey", 408, 7, objectData + 2968, 0}, + {"prime192v1", "prime192v1", 409, 8, objectData + 2975, 0}, + {"prime192v2", "prime192v2", 410, 8, objectData + 2983, 0}, + {"prime192v3", "prime192v3", 411, 8, objectData + 2991, 0}, + {"prime239v1", "prime239v1", 412, 8, objectData + 2999, 0}, + {"prime239v2", "prime239v2", 413, 8, objectData + 3007, 0}, + {"prime239v3", "prime239v3", 414, 8, objectData + 3015, 0}, + {"prime256v1", "prime256v1", 415, 8, objectData + 3023, 0}, + {"ecdsa-with-SHA1", "ecdsa-with-SHA1", 416, 7, objectData + 3031, 0}, + {"CSPName", "Microsoft CSP Name", 417, 9, objectData + 3038, 0}, + {"AES-128-ECB", "aes-128-ecb", 418, 9, objectData + 3047, 0}, + {"AES-128-CBC", "aes-128-cbc", 419, 9, objectData + 3056, 0}, + {"AES-128-OFB", "aes-128-ofb", 420, 9, objectData + 3065, 0}, + {"AES-128-CFB", "aes-128-cfb", 421, 9, objectData + 3074, 0}, + {"AES-192-ECB", "aes-192-ecb", 422, 9, objectData + 3083, 0}, + {"AES-192-CBC", "aes-192-cbc", 423, 9, objectData + 3092, 0}, + {"AES-192-OFB", "aes-192-ofb", 424, 9, objectData + 3101, 0}, + {"AES-192-CFB", "aes-192-cfb", 425, 9, objectData + 3110, 0}, + {"AES-256-ECB", "aes-256-ecb", 426, 9, objectData + 3119, 0}, + {"AES-256-CBC", "aes-256-cbc", 427, 9, objectData + 3128, 0}, + {"AES-256-OFB", "aes-256-ofb", 428, 9, objectData + 3137, 0}, + {"AES-256-CFB", "aes-256-cfb", 429, 9, objectData + 3146, 0}, + {"holdInstructionCode", "Hold Instruction Code", 430, 3, objectData + 3155, 0}, + {"holdInstructionNone", "Hold Instruction None", 431, 7, objectData + 3158, 0}, + {"holdInstructionCallIssuer", "Hold Instruction Call Issuer", 432, 7, objectData + 3165, 0}, + {"holdInstructionReject", "Hold Instruction Reject", 433, 7, objectData + 3172, 0}, + {"data", "data", 434, 1, objectData + 3179, 0}, + {"pss", "pss", 435, 3, objectData + 3180, 0}, + {"ucl", "ucl", 436, 7, objectData + 3183, 0}, + {"pilot", "pilot", 437, 8, objectData + 3190, 0}, + {"pilotAttributeType", "pilotAttributeType", 438, 9, objectData + 3198, 0}, + {"pilotAttributeSyntax", "pilotAttributeSyntax", 439, 9, objectData + 3207, 0}, + {"pilotObjectClass", "pilotObjectClass", 440, 9, objectData + 3216, 0}, + {"pilotGroups", "pilotGroups", 441, 9, objectData + 3225, 0}, + {"iA5StringSyntax", "iA5StringSyntax", 442, 10, objectData + 3234, 0}, + {"caseIgnoreIA5StringSyntax", "caseIgnoreIA5StringSyntax", 443, 10, objectData + 3244, 0}, + {"pilotObject", "pilotObject", 444, 10, objectData + 3254, 0}, + {"pilotPerson", "pilotPerson", 445, 10, objectData + 3264, 0}, + {"account", "account", 446, 10, objectData + 3274, 0}, + {"document", "document", 447, 10, objectData + 3284, 0}, + {"room", "room", 448, 10, objectData + 3294, 0}, + {"documentSeries", "documentSeries", 449, 10, objectData + 3304, 0}, + {"rFC822localPart", "rFC822localPart", 450, 10, objectData + 3314, 0}, + {"dNSDomain", "dNSDomain", 451, 10, objectData + 3324, 0}, + {"domainRelatedObject", "domainRelatedObject", 452, 10, objectData + 3334, 0}, + {"friendlyCountry", "friendlyCountry", 453, 10, objectData + 3344, 0}, + {"simpleSecurityObject", "simpleSecurityObject", 454, 10, objectData + 3354, 0}, + {"pilotOrganization", "pilotOrganization", 455, 10, objectData + 3364, 0}, + {"pilotDSA", "pilotDSA", 456, 10, objectData + 3374, 0}, + {"qualityLabelledData", "qualityLabelledData", 457, 10, objectData + 3384, 0}, + {"UID", "userId", 458, 10, objectData + 3394, 0}, + {"textEncodedORAddress", "textEncodedORAddress", 459, 10, objectData + 3404, 0}, + {"mail", "rfc822Mailbox", 460, 10, objectData + 3414, 0}, + {"info", "info", 461, 10, objectData + 3424, 0}, + {"favouriteDrink", "favouriteDrink", 462, 10, objectData + 3434, 0}, + {"roomNumber", "roomNumber", 463, 10, objectData + 3444, 0}, + {"photo", "photo", 464, 10, objectData + 3454, 0}, + {"userClass", "userClass", 465, 10, objectData + 3464, 0}, + {"host", "host", 466, 10, objectData + 3474, 0}, + {"manager", "manager", 467, 10, objectData + 3484, 0}, + {"documentIdentifier", "documentIdentifier", 468, 10, objectData + 3494, 0}, + {"documentTitle", "documentTitle", 469, 10, objectData + 3504, 0}, + {"documentVersion", "documentVersion", 470, 10, objectData + 3514, 0}, + {"documentAuthor", "documentAuthor", 471, 10, objectData + 3524, 0}, + {"documentLocation", "documentLocation", 472, 10, objectData + 3534, 0}, + {"homeTelephoneNumber", "homeTelephoneNumber", 473, 10, objectData + 3544, 0}, + {"secretary", "secretary", 474, 10, objectData + 3554, 0}, + {"otherMailbox", "otherMailbox", 475, 10, objectData + 3564, 0}, + {"lastModifiedTime", "lastModifiedTime", 476, 10, objectData + 3574, 0}, + {"lastModifiedBy", "lastModifiedBy", 477, 10, objectData + 3584, 0}, + {"aRecord", "aRecord", 478, 10, objectData + 3594, 0}, + {"pilotAttributeType27", "pilotAttributeType27", 479, 10, objectData + 3604, 0}, + {"mXRecord", "mXRecord", 480, 10, objectData + 3614, 0}, + {"nSRecord", "nSRecord", 481, 10, objectData + 3624, 0}, + {"sOARecord", "sOARecord", 482, 10, objectData + 3634, 0}, + {"cNAMERecord", "cNAMERecord", 483, 10, objectData + 3644, 0}, + {"associatedDomain", "associatedDomain", 484, 10, objectData + 3654, 0}, + {"associatedName", "associatedName", 485, 10, objectData + 3664, 0}, + {"homePostalAddress", "homePostalAddress", 486, 10, objectData + 3674, 0}, + {"personalTitle", "personalTitle", 487, 10, objectData + 3684, 0}, + {"mobileTelephoneNumber", "mobileTelephoneNumber", 488, 10, objectData + 3694, 0}, + {"pagerTelephoneNumber", "pagerTelephoneNumber", 489, 10, objectData + 3704, 0}, + {"friendlyCountryName", "friendlyCountryName", 490, 10, objectData + 3714, 0}, + {"organizationalStatus", "organizationalStatus", 491, 10, objectData + 3724, 0}, + {"janetMailbox", "janetMailbox", 492, 10, objectData + 3734, 0}, + {"mailPreferenceOption", "mailPreferenceOption", 493, 10, objectData + 3744, 0}, + {"buildingName", "buildingName", 494, 10, objectData + 3754, 0}, + {"dSAQuality", "dSAQuality", 495, 10, objectData + 3764, 0}, + {"singleLevelQuality", "singleLevelQuality", 496, 10, objectData + 3774, 0}, + {"subtreeMinimumQuality", "subtreeMinimumQuality", 497, 10, objectData + 3784, 0}, + {"subtreeMaximumQuality", "subtreeMaximumQuality", 498, 10, objectData + 3794, 0}, + {"personalSignature", "personalSignature", 499, 10, objectData + 3804, 0}, + {"dITRedirect", "dITRedirect", 500, 10, objectData + 3814, 0}, + {"audio", "audio", 501, 10, objectData + 3824, 0}, + {"documentPublisher", "documentPublisher", 502, 10, objectData + 3834, 0}, + {"x500UniqueIdentifier", "x500UniqueIdentifier", 503, 3, objectData + 3844, 0}, + {"mime-mhs", "MIME MHS", 504, 5, objectData + 3847, 0}, + {"mime-mhs-headings", "mime-mhs-headings", 505, 6, objectData + 3852, 0}, + {"mime-mhs-bodies", "mime-mhs-bodies", 506, 6, objectData + 3858, 0}, + {"id-hex-partial-message", "id-hex-partial-message", 507, 7, objectData + 3864, 0}, + {"id-hex-multipart-message", "id-hex-multipart-message", 508, 7, objectData + 3871, 0}, + {"generationQualifier", "generationQualifier", 509, 3, objectData + 3878, 0}, + {"pseudonym", "pseudonym", 510, 3, objectData + 3881, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {"id-set", "Secure Electronic Transactions", 512, 2, objectData + 3884, 0}, + {"set-ctype", "content types", 513, 3, objectData + 3886, 0}, + {"set-msgExt", "message extensions", 514, 3, objectData + 3889, 0}, + {"set-attr", "set-attr", 515, 3, objectData + 3892, 0}, + {"set-policy", "set-policy", 516, 3, objectData + 3895, 0}, + {"set-certExt", "certificate extensions", 517, 3, objectData + 3898, 0}, + {"set-brand", "set-brand", 518, 3, objectData + 3901, 0}, + {"setct-PANData", "setct-PANData", 519, 4, objectData + 3904, 0}, + {"setct-PANToken", "setct-PANToken", 520, 4, objectData + 3908, 0}, + {"setct-PANOnly", "setct-PANOnly", 521, 4, objectData + 3912, 0}, + {"setct-OIData", "setct-OIData", 522, 4, objectData + 3916, 0}, + {"setct-PI", "setct-PI", 523, 4, objectData + 3920, 0}, + {"setct-PIData", "setct-PIData", 524, 4, objectData + 3924, 0}, + {"setct-PIDataUnsigned", "setct-PIDataUnsigned", 525, 4, objectData + 3928, 0}, + {"setct-HODInput", "setct-HODInput", 526, 4, objectData + 3932, 0}, + {"setct-AuthResBaggage", "setct-AuthResBaggage", 527, 4, objectData + 3936, 0}, + {"setct-AuthRevReqBaggage", "setct-AuthRevReqBaggage", 528, 4, objectData + 3940, 0}, + {"setct-AuthRevResBaggage", "setct-AuthRevResBaggage", 529, 4, objectData + 3944, 0}, + {"setct-CapTokenSeq", "setct-CapTokenSeq", 530, 4, objectData + 3948, 0}, + {"setct-PInitResData", "setct-PInitResData", 531, 4, objectData + 3952, 0}, + {"setct-PI-TBS", "setct-PI-TBS", 532, 4, objectData + 3956, 0}, + {"setct-PResData", "setct-PResData", 533, 4, objectData + 3960, 0}, + {"setct-AuthReqTBS", "setct-AuthReqTBS", 534, 4, objectData + 3964, 0}, + {"setct-AuthResTBS", "setct-AuthResTBS", 535, 4, objectData + 3968, 0}, + {"setct-AuthResTBSX", "setct-AuthResTBSX", 536, 4, objectData + 3972, 0}, + {"setct-AuthTokenTBS", "setct-AuthTokenTBS", 537, 4, objectData + 3976, 0}, + {"setct-CapTokenData", "setct-CapTokenData", 538, 4, objectData + 3980, 0}, + {"setct-CapTokenTBS", "setct-CapTokenTBS", 539, 4, objectData + 3984, 0}, + {"setct-AcqCardCodeMsg", "setct-AcqCardCodeMsg", 540, 4, objectData + 3988, 0}, + {"setct-AuthRevReqTBS", "setct-AuthRevReqTBS", 541, 4, objectData + 3992, 0}, + {"setct-AuthRevResData", "setct-AuthRevResData", 542, 4, objectData + 3996, 0}, + {"setct-AuthRevResTBS", "setct-AuthRevResTBS", 543, 4, objectData + 4000, 0}, + {"setct-CapReqTBS", "setct-CapReqTBS", 544, 4, objectData + 4004, 0}, + {"setct-CapReqTBSX", "setct-CapReqTBSX", 545, 4, objectData + 4008, 0}, + {"setct-CapResData", "setct-CapResData", 546, 4, objectData + 4012, 0}, + {"setct-CapRevReqTBS", "setct-CapRevReqTBS", 547, 4, objectData + 4016, 0}, + {"setct-CapRevReqTBSX", "setct-CapRevReqTBSX", 548, 4, objectData + 4020, 0}, + {"setct-CapRevResData", "setct-CapRevResData", 549, 4, objectData + 4024, 0}, + {"setct-CredReqTBS", "setct-CredReqTBS", 550, 4, objectData + 4028, 0}, + {"setct-CredReqTBSX", "setct-CredReqTBSX", 551, 4, objectData + 4032, 0}, + {"setct-CredResData", "setct-CredResData", 552, 4, objectData + 4036, 0}, + {"setct-CredRevReqTBS", "setct-CredRevReqTBS", 553, 4, objectData + 4040, 0}, + {"setct-CredRevReqTBSX", "setct-CredRevReqTBSX", 554, 4, objectData + 4044, 0}, + {"setct-CredRevResData", "setct-CredRevResData", 555, 4, objectData + 4048, 0}, + {"setct-PCertReqData", "setct-PCertReqData", 556, 4, objectData + 4052, 0}, + {"setct-PCertResTBS", "setct-PCertResTBS", 557, 4, objectData + 4056, 0}, + {"setct-BatchAdminReqData", "setct-BatchAdminReqData", 558, 4, objectData + 4060, 0}, + {"setct-BatchAdminResData", "setct-BatchAdminResData", 559, 4, objectData + 4064, 0}, + {"setct-CardCInitResTBS", "setct-CardCInitResTBS", 560, 4, objectData + 4068, 0}, + {"setct-MeAqCInitResTBS", "setct-MeAqCInitResTBS", 561, 4, objectData + 4072, 0}, + {"setct-RegFormResTBS", "setct-RegFormResTBS", 562, 4, objectData + 4076, 0}, + {"setct-CertReqData", "setct-CertReqData", 563, 4, objectData + 4080, 0}, + {"setct-CertReqTBS", "setct-CertReqTBS", 564, 4, objectData + 4084, 0}, + {"setct-CertResData", "setct-CertResData", 565, 4, objectData + 4088, 0}, + {"setct-CertInqReqTBS", "setct-CertInqReqTBS", 566, 4, objectData + 4092, 0}, + {"setct-ErrorTBS", "setct-ErrorTBS", 567, 4, objectData + 4096, 0}, + {"setct-PIDualSignedTBE", "setct-PIDualSignedTBE", 568, 4, objectData + 4100, 0}, + {"setct-PIUnsignedTBE", "setct-PIUnsignedTBE", 569, 4, objectData + 4104, 0}, + {"setct-AuthReqTBE", "setct-AuthReqTBE", 570, 4, objectData + 4108, 0}, + {"setct-AuthResTBE", "setct-AuthResTBE", 571, 4, objectData + 4112, 0}, + {"setct-AuthResTBEX", "setct-AuthResTBEX", 572, 4, objectData + 4116, 0}, + {"setct-AuthTokenTBE", "setct-AuthTokenTBE", 573, 4, objectData + 4120, 0}, + {"setct-CapTokenTBE", "setct-CapTokenTBE", 574, 4, objectData + 4124, 0}, + {"setct-CapTokenTBEX", "setct-CapTokenTBEX", 575, 4, objectData + 4128, 0}, + {"setct-AcqCardCodeMsgTBE", "setct-AcqCardCodeMsgTBE", 576, 4, objectData + 4132, 0}, + {"setct-AuthRevReqTBE", "setct-AuthRevReqTBE", 577, 4, objectData + 4136, 0}, + {"setct-AuthRevResTBE", "setct-AuthRevResTBE", 578, 4, objectData + 4140, 0}, + {"setct-AuthRevResTBEB", "setct-AuthRevResTBEB", 579, 4, objectData + 4144, 0}, + {"setct-CapReqTBE", "setct-CapReqTBE", 580, 4, objectData + 4148, 0}, + {"setct-CapReqTBEX", "setct-CapReqTBEX", 581, 4, objectData + 4152, 0}, + {"setct-CapResTBE", "setct-CapResTBE", 582, 4, objectData + 4156, 0}, + {"setct-CapRevReqTBE", "setct-CapRevReqTBE", 583, 4, objectData + 4160, 0}, + {"setct-CapRevReqTBEX", "setct-CapRevReqTBEX", 584, 4, objectData + 4164, 0}, + {"setct-CapRevResTBE", "setct-CapRevResTBE", 585, 4, objectData + 4168, 0}, + {"setct-CredReqTBE", "setct-CredReqTBE", 586, 4, objectData + 4172, 0}, + {"setct-CredReqTBEX", "setct-CredReqTBEX", 587, 4, objectData + 4176, 0}, + {"setct-CredResTBE", "setct-CredResTBE", 588, 4, objectData + 4180, 0}, + {"setct-CredRevReqTBE", "setct-CredRevReqTBE", 589, 4, objectData + 4184, 0}, + {"setct-CredRevReqTBEX", "setct-CredRevReqTBEX", 590, 4, objectData + 4188, 0}, + {"setct-CredRevResTBE", "setct-CredRevResTBE", 591, 4, objectData + 4192, 0}, + {"setct-BatchAdminReqTBE", "setct-BatchAdminReqTBE", 592, 4, objectData + 4196, 0}, + {"setct-BatchAdminResTBE", "setct-BatchAdminResTBE", 593, 4, objectData + 4200, 0}, + {"setct-RegFormReqTBE", "setct-RegFormReqTBE", 594, 4, objectData + 4204, 0}, + {"setct-CertReqTBE", "setct-CertReqTBE", 595, 4, objectData + 4208, 0}, + {"setct-CertReqTBEX", "setct-CertReqTBEX", 596, 4, objectData + 4212, 0}, + {"setct-CertResTBE", "setct-CertResTBE", 597, 4, objectData + 4216, 0}, + {"setct-CRLNotificationTBS", "setct-CRLNotificationTBS", 598, 4, objectData + 4220, 0}, + {"setct-CRLNotificationResTBS", "setct-CRLNotificationResTBS", 599, 4, objectData + 4224, 0}, + {"setct-BCIDistributionTBS", "setct-BCIDistributionTBS", 600, 4, objectData + 4228, 0}, + {"setext-genCrypt", "generic cryptogram", 601, 4, objectData + 4232, 0}, + {"setext-miAuth", "merchant initiated auth", 602, 4, objectData + 4236, 0}, + {"setext-pinSecure", "setext-pinSecure", 603, 4, objectData + 4240, 0}, + {"setext-pinAny", "setext-pinAny", 604, 4, objectData + 4244, 0}, + {"setext-track2", "setext-track2", 605, 4, objectData + 4248, 0}, + {"setext-cv", "additional verification", 606, 4, objectData + 4252, 0}, + {"set-policy-root", "set-policy-root", 607, 4, objectData + 4256, 0}, + {"setCext-hashedRoot", "setCext-hashedRoot", 608, 4, objectData + 4260, 0}, + {"setCext-certType", "setCext-certType", 609, 4, objectData + 4264, 0}, + {"setCext-merchData", "setCext-merchData", 610, 4, objectData + 4268, 0}, + {"setCext-cCertRequired", "setCext-cCertRequired", 611, 4, objectData + 4272, 0}, + {"setCext-tunneling", "setCext-tunneling", 612, 4, objectData + 4276, 0}, + {"setCext-setExt", "setCext-setExt", 613, 4, objectData + 4280, 0}, + {"setCext-setQualf", "setCext-setQualf", 614, 4, objectData + 4284, 0}, + {"setCext-PGWYcapabilities", "setCext-PGWYcapabilities", 615, 4, objectData + 4288, 0}, + {"setCext-TokenIdentifier", "setCext-TokenIdentifier", 616, 4, objectData + 4292, 0}, + {"setCext-Track2Data", "setCext-Track2Data", 617, 4, objectData + 4296, 0}, + {"setCext-TokenType", "setCext-TokenType", 618, 4, objectData + 4300, 0}, + {"setCext-IssuerCapabilities", "setCext-IssuerCapabilities", 619, 4, objectData + 4304, 0}, + {"setAttr-Cert", "setAttr-Cert", 620, 4, objectData + 4308, 0}, + {"setAttr-PGWYcap", "payment gateway capabilities", 621, 4, objectData + 4312, 0}, + {"setAttr-TokenType", "setAttr-TokenType", 622, 4, objectData + 4316, 0}, + {"setAttr-IssCap", "issuer capabilities", 623, 4, objectData + 4320, 0}, + {"set-rootKeyThumb", "set-rootKeyThumb", 624, 5, objectData + 4324, 0}, + {"set-addPolicy", "set-addPolicy", 625, 5, objectData + 4329, 0}, + {"setAttr-Token-EMV", "setAttr-Token-EMV", 626, 5, objectData + 4334, 0}, + {"setAttr-Token-B0Prime", "setAttr-Token-B0Prime", 627, 5, objectData + 4339, 0}, + {"setAttr-IssCap-CVM", "setAttr-IssCap-CVM", 628, 5, objectData + 4344, 0}, + {"setAttr-IssCap-T2", "setAttr-IssCap-T2", 629, 5, objectData + 4349, 0}, + {"setAttr-IssCap-Sig", "setAttr-IssCap-Sig", 630, 5, objectData + 4354, 0}, + {"setAttr-GenCryptgrm", "generate cryptogram", 631, 6, objectData + 4359, 0}, + {"setAttr-T2Enc", "encrypted track 2", 632, 6, objectData + 4365, 0}, + {"setAttr-T2cleartxt", "cleartext track 2", 633, 6, objectData + 4371, 0}, + {"setAttr-TokICCsig", "ICC or token signature", 634, 6, objectData + 4377, 0}, + {"setAttr-SecDevSig", "secure device signature", 635, 6, objectData + 4383, 0}, + {"set-brand-IATA-ATA", "set-brand-IATA-ATA", 636, 4, objectData + 4389, 0}, + {"set-brand-Diners", "set-brand-Diners", 637, 4, objectData + 4393, 0}, + {"set-brand-AmericanExpress", "set-brand-AmericanExpress", 638, 4, objectData + 4397, 0}, + {"set-brand-JCB", "set-brand-JCB", 639, 4, objectData + 4401, 0}, + {"set-brand-Visa", "set-brand-Visa", 640, 4, objectData + 4405, 0}, + {"set-brand-MasterCard", "set-brand-MasterCard", 641, 4, objectData + 4409, 0}, + {"set-brand-Novus", "set-brand-Novus", 642, 5, objectData + 4413, 0}, + {"DES-CDMF", "des-cdmf", 643, 8, objectData + 4418, 0}, + {"rsaOAEPEncryptionSET", "rsaOAEPEncryptionSET", 644, 9, objectData + 4426, 0}, + {"ITU-T", "itu-t", 645, 0, nullptr, 0}, + {"JOINT-ISO-ITU-T", "joint-iso-itu-t", 646, 0, nullptr, 0}, + {"international-organizations", "International Organizations", 647, 1, objectData + 4435, 0}, + {"msSmartcardLogin", "Microsoft Smartcardlogin", 648, 10, objectData + 4436, 0}, + {"msUPN", "Microsoft Universal Principal Name", 649, 10, objectData + 4446, 0}, + {"AES-128-CFB1", "aes-128-cfb1", 650, 0, nullptr, 0}, + {"AES-192-CFB1", "aes-192-cfb1", 651, 0, nullptr, 0}, + {"AES-256-CFB1", "aes-256-cfb1", 652, 0, nullptr, 0}, + {"AES-128-CFB8", "aes-128-cfb8", 653, 0, nullptr, 0}, + {"AES-192-CFB8", "aes-192-cfb8", 654, 0, nullptr, 0}, + {"AES-256-CFB8", "aes-256-cfb8", 655, 0, nullptr, 0}, + {"DES-CFB1", "des-cfb1", 656, 0, nullptr, 0}, + {"DES-CFB8", "des-cfb8", 657, 0, nullptr, 0}, + {"DES-EDE3-CFB1", "des-ede3-cfb1", 658, 0, nullptr, 0}, + {"DES-EDE3-CFB8", "des-ede3-cfb8", 659, 0, nullptr, 0}, + {"street", "streetAddress", 660, 3, objectData + 4456, 0}, + {"postalCode", "postalCode", 661, 3, objectData + 4459, 0}, + {"id-ppl", "id-ppl", 662, 7, objectData + 4462, 0}, + {"proxyCertInfo", "Proxy Certificate Information", 663, 8, objectData + 4469, 0}, + {"id-ppl-anyLanguage", "Any language", 664, 8, objectData + 4477, 0}, + {"id-ppl-inheritAll", "Inherit all", 665, 8, objectData + 4485, 0}, + {"nameConstraints", "X509v3 Name Constraints", 666, 3, objectData + 4493, 0}, + {"id-ppl-independent", "Independent", 667, 8, objectData + 4496, 0}, + {"RSA-SHA256", "sha256WithRSAEncryption", 668, 9, objectData + 4504, 0}, + {"RSA-SHA384", "sha384WithRSAEncryption", 669, 9, objectData + 4513, 0}, + {"RSA-SHA512", "sha512WithRSAEncryption", 670, 9, objectData + 4522, 0}, + {"RSA-SHA224", "sha224WithRSAEncryption", 671, 9, objectData + 4531, 0}, + {"SHA256", "sha256", 672, 9, objectData + 4540, 0}, + {"SHA384", "sha384", 673, 9, objectData + 4549, 0}, + {"SHA512", "sha512", 674, 9, objectData + 4558, 0}, + {"SHA224", "sha224", 675, 9, objectData + 4567, 0}, + {"identified-organization", "identified-organization", 676, 1, objectData + 4576, 0}, + {"certicom-arc", "certicom-arc", 677, 3, objectData + 4577, 0}, + {"wap", "wap", 678, 2, objectData + 4580, 0}, + {"wap-wsg", "wap-wsg", 679, 3, objectData + 4582, 0}, + {"id-characteristic-two-basis", "id-characteristic-two-basis", 680, 8, objectData + 4585, 0}, + {"onBasis", "onBasis", 681, 9, objectData + 4593, 0}, + {"tpBasis", "tpBasis", 682, 9, objectData + 4602, 0}, + {"ppBasis", "ppBasis", 683, 9, objectData + 4611, 0}, + {"c2pnb163v1", "c2pnb163v1", 684, 8, objectData + 4620, 0}, + {"c2pnb163v2", "c2pnb163v2", 685, 8, objectData + 4628, 0}, + {"c2pnb163v3", "c2pnb163v3", 686, 8, objectData + 4636, 0}, + {"c2pnb176v1", "c2pnb176v1", 687, 8, objectData + 4644, 0}, + {"c2tnb191v1", "c2tnb191v1", 688, 8, objectData + 4652, 0}, + {"c2tnb191v2", "c2tnb191v2", 689, 8, objectData + 4660, 0}, + {"c2tnb191v3", "c2tnb191v3", 690, 8, objectData + 4668, 0}, + {"c2onb191v4", "c2onb191v4", 691, 8, objectData + 4676, 0}, + {"c2onb191v5", "c2onb191v5", 692, 8, objectData + 4684, 0}, + {"c2pnb208w1", "c2pnb208w1", 693, 8, objectData + 4692, 0}, + {"c2tnb239v1", "c2tnb239v1", 694, 8, objectData + 4700, 0}, + {"c2tnb239v2", "c2tnb239v2", 695, 8, objectData + 4708, 0}, + {"c2tnb239v3", "c2tnb239v3", 696, 8, objectData + 4716, 0}, + {"c2onb239v4", "c2onb239v4", 697, 8, objectData + 4724, 0}, + {"c2onb239v5", "c2onb239v5", 698, 8, objectData + 4732, 0}, + {"c2pnb272w1", "c2pnb272w1", 699, 8, objectData + 4740, 0}, + {"c2pnb304w1", "c2pnb304w1", 700, 8, objectData + 4748, 0}, + {"c2tnb359v1", "c2tnb359v1", 701, 8, objectData + 4756, 0}, + {"c2pnb368w1", "c2pnb368w1", 702, 8, objectData + 4764, 0}, + {"c2tnb431r1", "c2tnb431r1", 703, 8, objectData + 4772, 0}, + {"secp112r1", "secp112r1", 704, 5, objectData + 4780, 0}, + {"secp112r2", "secp112r2", 705, 5, objectData + 4785, 0}, + {"secp128r1", "secp128r1", 706, 5, objectData + 4790, 0}, + {"secp128r2", "secp128r2", 707, 5, objectData + 4795, 0}, + {"secp160k1", "secp160k1", 708, 5, objectData + 4800, 0}, + {"secp160r1", "secp160r1", 709, 5, objectData + 4805, 0}, + {"secp160r2", "secp160r2", 710, 5, objectData + 4810, 0}, + {"secp192k1", "secp192k1", 711, 5, objectData + 4815, 0}, + {"secp224k1", "secp224k1", 712, 5, objectData + 4820, 0}, + {"secp224r1", "secp224r1", 713, 5, objectData + 4825, 0}, + {"secp256k1", "secp256k1", 714, 5, objectData + 4830, 0}, + {"secp384r1", "secp384r1", 715, 5, objectData + 4835, 0}, + {"secp521r1", "secp521r1", 716, 5, objectData + 4840, 0}, + {"sect113r1", "sect113r1", 717, 5, objectData + 4845, 0}, + {"sect113r2", "sect113r2", 718, 5, objectData + 4850, 0}, + {"sect131r1", "sect131r1", 719, 5, objectData + 4855, 0}, + {"sect131r2", "sect131r2", 720, 5, objectData + 4860, 0}, + {"sect163k1", "sect163k1", 721, 5, objectData + 4865, 0}, + {"sect163r1", "sect163r1", 722, 5, objectData + 4870, 0}, + {"sect163r2", "sect163r2", 723, 5, objectData + 4875, 0}, + {"sect193r1", "sect193r1", 724, 5, objectData + 4880, 0}, + {"sect193r2", "sect193r2", 725, 5, objectData + 4885, 0}, + {"sect233k1", "sect233k1", 726, 5, objectData + 4890, 0}, + {"sect233r1", "sect233r1", 727, 5, objectData + 4895, 0}, + {"sect239k1", "sect239k1", 728, 5, objectData + 4900, 0}, + {"sect283k1", "sect283k1", 729, 5, objectData + 4905, 0}, + {"sect283r1", "sect283r1", 730, 5, objectData + 4910, 0}, + {"sect409k1", "sect409k1", 731, 5, objectData + 4915, 0}, + {"sect409r1", "sect409r1", 732, 5, objectData + 4920, 0}, + {"sect571k1", "sect571k1", 733, 5, objectData + 4925, 0}, + {"sect571r1", "sect571r1", 734, 5, objectData + 4930, 0}, + {"wap-wsg-idm-ecid-wtls1", "wap-wsg-idm-ecid-wtls1", 735, 5, objectData + 4935, 0}, + {"wap-wsg-idm-ecid-wtls3", "wap-wsg-idm-ecid-wtls3", 736, 5, objectData + 4940, 0}, + {"wap-wsg-idm-ecid-wtls4", "wap-wsg-idm-ecid-wtls4", 737, 5, objectData + 4945, 0}, + {"wap-wsg-idm-ecid-wtls5", "wap-wsg-idm-ecid-wtls5", 738, 5, objectData + 4950, 0}, + {"wap-wsg-idm-ecid-wtls6", "wap-wsg-idm-ecid-wtls6", 739, 5, objectData + 4955, 0}, + {"wap-wsg-idm-ecid-wtls7", "wap-wsg-idm-ecid-wtls7", 740, 5, objectData + 4960, 0}, + {"wap-wsg-idm-ecid-wtls8", "wap-wsg-idm-ecid-wtls8", 741, 5, objectData + 4965, 0}, + {"wap-wsg-idm-ecid-wtls9", "wap-wsg-idm-ecid-wtls9", 742, 5, objectData + 4970, 0}, + {"wap-wsg-idm-ecid-wtls10", "wap-wsg-idm-ecid-wtls10", 743, 5, objectData + 4975, 0}, + {"wap-wsg-idm-ecid-wtls11", "wap-wsg-idm-ecid-wtls11", 744, 5, objectData + 4980, 0}, + {"wap-wsg-idm-ecid-wtls12", "wap-wsg-idm-ecid-wtls12", 745, 5, objectData + 4985, 0}, + {"anyPolicy", "X509v3 Any Policy", 746, 4, objectData + 4990, 0}, + {"policyMappings", "X509v3 Policy Mappings", 747, 3, objectData + 4994, 0}, + {"inhibitAnyPolicy", "X509v3 Inhibit Any Policy", 748, 3, objectData + 4997, 0}, + {"Oakley-EC2N-3", "ipsec3", 749, 0, nullptr, 0}, + {"Oakley-EC2N-4", "ipsec4", 750, 0, nullptr, 0}, + {"CAMELLIA-128-CBC", "camellia-128-cbc", 751, 11, objectData + 5000, 0}, + {"CAMELLIA-192-CBC", "camellia-192-cbc", 752, 11, objectData + 5011, 0}, + {"CAMELLIA-256-CBC", "camellia-256-cbc", 753, 11, objectData + 5022, 0}, + {"CAMELLIA-128-ECB", "camellia-128-ecb", 754, 8, objectData + 5033, 0}, + {"CAMELLIA-192-ECB", "camellia-192-ecb", 755, 8, objectData + 5041, 0}, + {"CAMELLIA-256-ECB", "camellia-256-ecb", 756, 8, objectData + 5049, 0}, + {"CAMELLIA-128-CFB", "camellia-128-cfb", 757, 8, objectData + 5057, 0}, + {"CAMELLIA-192-CFB", "camellia-192-cfb", 758, 8, objectData + 5065, 0}, + {"CAMELLIA-256-CFB", "camellia-256-cfb", 759, 8, objectData + 5073, 0}, + {"CAMELLIA-128-CFB1", "camellia-128-cfb1", 760, 0, nullptr, 0}, + {"CAMELLIA-192-CFB1", "camellia-192-cfb1", 761, 0, nullptr, 0}, + {"CAMELLIA-256-CFB1", "camellia-256-cfb1", 762, 0, nullptr, 0}, + {"CAMELLIA-128-CFB8", "camellia-128-cfb8", 763, 0, nullptr, 0}, + {"CAMELLIA-192-CFB8", "camellia-192-cfb8", 764, 0, nullptr, 0}, + {"CAMELLIA-256-CFB8", "camellia-256-cfb8", 765, 0, nullptr, 0}, + {"CAMELLIA-128-OFB", "camellia-128-ofb", 766, 8, objectData + 5081, 0}, + {"CAMELLIA-192-OFB", "camellia-192-ofb", 767, 8, objectData + 5089, 0}, + {"CAMELLIA-256-OFB", "camellia-256-ofb", 768, 8, objectData + 5097, 0}, + {"subjectDirectoryAttributes", "X509v3 Subject Directory Attributes", 769, 3, objectData + 5105, 0}, + {"issuingDistributionPoint", "X509v3 Issuing Distribution Point", 770, 3, objectData + 5108, 0}, + {"certificateIssuer", "X509v3 Certificate Issuer", 771, 3, objectData + 5111, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {"KISA", "kisa", 773, 6, objectData + 5114, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {nullptr, nullptr, 0, 0, nullptr, 0}, + {"SEED-ECB", "seed-ecb", 776, 8, objectData + 5120, 0}, + {"SEED-CBC", "seed-cbc", 777, 8, objectData + 5128, 0}, + {"SEED-OFB", "seed-ofb", 778, 8, objectData + 5136, 0}, + {"SEED-CFB", "seed-cfb", 779, 8, objectData + 5144, 0}, + {"HMAC-MD5", "hmac-md5", 780, 8, objectData + 5152, 0}, + {"HMAC-SHA1", "hmac-sha1", 781, 8, objectData + 5160, 0}, + {"id-PasswordBasedMAC", "password based MAC", 782, 9, objectData + 5168, 0}, + {"id-DHBasedMac", "Diffie-Hellman based MAC", 783, 9, objectData + 5177, 0}, + {"id-it-suppLangTags", "id-it-suppLangTags", 784, 8, objectData + 5186, 0}, + {"caRepository", "CA Repository", 785, 8, objectData + 5194, 0}, + {"id-smime-ct-compressedData", "id-smime-ct-compressedData", 786, 11, objectData + 5202, 0}, + {"id-ct-asciiTextWithCRLF", "id-ct-asciiTextWithCRLF", 787, 11, objectData + 5213, 0}, + {"id-aes128-wrap", "id-aes128-wrap", 788, 9, objectData + 5224, 0}, + {"id-aes192-wrap", "id-aes192-wrap", 789, 9, objectData + 5233, 0}, + {"id-aes256-wrap", "id-aes256-wrap", 790, 9, objectData + 5242, 0}, + {"ecdsa-with-Recommended", "ecdsa-with-Recommended", 791, 7, objectData + 5251, 0}, + {"ecdsa-with-Specified", "ecdsa-with-Specified", 792, 7, objectData + 5258, 0}, + {"ecdsa-with-SHA224", "ecdsa-with-SHA224", 793, 8, objectData + 5265, 0}, + {"ecdsa-with-SHA256", "ecdsa-with-SHA256", 794, 8, objectData + 5273, 0}, + {"ecdsa-with-SHA384", "ecdsa-with-SHA384", 795, 8, objectData + 5281, 0}, + {"ecdsa-with-SHA512", "ecdsa-with-SHA512", 796, 8, objectData + 5289, 0}, + {"hmacWithMD5", "hmacWithMD5", 797, 8, objectData + 5297, 0}, + {"hmacWithSHA224", "hmacWithSHA224", 798, 8, objectData + 5305, 0}, + {"hmacWithSHA256", "hmacWithSHA256", 799, 8, objectData + 5313, 0}, + {"hmacWithSHA384", "hmacWithSHA384", 800, 8, objectData + 5321, 0}, + {"hmacWithSHA512", "hmacWithSHA512", 801, 8, objectData + 5329, 0}, + {"dsa_with_SHA224", "dsa_with_SHA224", 802, 9, objectData + 5337, 0}, + {"dsa_with_SHA256", "dsa_with_SHA256", 803, 9, objectData + 5346, 0}, + {"whirlpool", "whirlpool", 804, 6, objectData + 5355, 0}, + {"cryptopro", "cryptopro", 805, 5, objectData + 5361, 0}, + {"cryptocom", "cryptocom", 806, 5, objectData + 5366, 0}, + {"id-GostR3411-94-with-GostR3410-2001", "GOST R 34.11-94 with GOST R 34.10-2001", 807, 6, objectData + 5371, 0}, + {"id-GostR3411-94-with-GostR3410-94", "GOST R 34.11-94 with GOST R 34.10-94", 808, 6, objectData + 5377, 0}, + {"md_gost94", "GOST R 34.11-94", 809, 6, objectData + 5383, 0}, + {"id-HMACGostR3411-94", "HMAC GOST 34.11-94", 810, 6, objectData + 5389, 0}, + {"gost2001", "GOST R 34.10-2001", 811, 6, objectData + 5395, 0}, + {"gost94", "GOST R 34.10-94", 812, 6, objectData + 5401, 0}, + {"gost89", "GOST 28147-89", 813, 6, objectData + 5407, 0}, + {"gost89-cnt", "gost89-cnt", 814, 0, nullptr, 0}, + {"gost-mac", "GOST 28147-89 MAC", 815, 6, objectData + 5413, 0}, + {"prf-gostr3411-94", "GOST R 34.11-94 PRF", 816, 6, objectData + 5419, 0}, + {"id-GostR3410-2001DH", "GOST R 34.10-2001 DH", 817, 6, objectData + 5425, 0}, + {"id-GostR3410-94DH", "GOST R 34.10-94 DH", 818, 6, objectData + 5431, 0}, + {"id-Gost28147-89-CryptoPro-KeyMeshing", "id-Gost28147-89-CryptoPro-KeyMeshing", 819, 7, objectData + 5437, 0}, + {"id-Gost28147-89-None-KeyMeshing", "id-Gost28147-89-None-KeyMeshing", 820, 7, objectData + 5444, 0}, + {"id-GostR3411-94-TestParamSet", "id-GostR3411-94-TestParamSet", 821, 7, objectData + 5451, 0}, + {"id-GostR3411-94-CryptoProParamSet", "id-GostR3411-94-CryptoProParamSet", 822, 7, objectData + 5458, 0}, + {"id-Gost28147-89-TestParamSet", "id-Gost28147-89-TestParamSet", 823, 7, objectData + 5465, 0}, + {"id-Gost28147-89-CryptoPro-A-ParamSet", "id-Gost28147-89-CryptoPro-A-ParamSet", 824, 7, objectData + 5472, 0}, + {"id-Gost28147-89-CryptoPro-B-ParamSet", "id-Gost28147-89-CryptoPro-B-ParamSet", 825, 7, objectData + 5479, 0}, + {"id-Gost28147-89-CryptoPro-C-ParamSet", "id-Gost28147-89-CryptoPro-C-ParamSet", 826, 7, objectData + 5486, 0}, + {"id-Gost28147-89-CryptoPro-D-ParamSet", "id-Gost28147-89-CryptoPro-D-ParamSet", 827, 7, objectData + 5493, 0}, + {"id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet", "id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet", 828, 7, objectData + 5500, 0}, + {"id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet", "id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet", 829, 7, objectData + 5507, 0}, + {"id-Gost28147-89-CryptoPro-RIC-1-ParamSet", "id-Gost28147-89-CryptoPro-RIC-1-ParamSet", 830, 7, objectData + 5514, 0}, + {"id-GostR3410-94-TestParamSet", "id-GostR3410-94-TestParamSet", 831, 7, objectData + 5521, 0}, + {"id-GostR3410-94-CryptoPro-A-ParamSet", "id-GostR3410-94-CryptoPro-A-ParamSet", 832, 7, objectData + 5528, 0}, + {"id-GostR3410-94-CryptoPro-B-ParamSet", "id-GostR3410-94-CryptoPro-B-ParamSet", 833, 7, objectData + 5535, 0}, + {"id-GostR3410-94-CryptoPro-C-ParamSet", "id-GostR3410-94-CryptoPro-C-ParamSet", 834, 7, objectData + 5542, 0}, + {"id-GostR3410-94-CryptoPro-D-ParamSet", "id-GostR3410-94-CryptoPro-D-ParamSet", 835, 7, objectData + 5549, 0}, + {"id-GostR3410-94-CryptoPro-XchA-ParamSet", "id-GostR3410-94-CryptoPro-XchA-ParamSet", 836, 7, objectData + 5556, 0}, + {"id-GostR3410-94-CryptoPro-XchB-ParamSet", "id-GostR3410-94-CryptoPro-XchB-ParamSet", 837, 7, objectData + 5563, 0}, + {"id-GostR3410-94-CryptoPro-XchC-ParamSet", "id-GostR3410-94-CryptoPro-XchC-ParamSet", 838, 7, objectData + 5570, 0}, + {"id-GostR3410-2001-TestParamSet", "id-GostR3410-2001-TestParamSet", 839, 7, objectData + 5577, 0}, + {"id-GostR3410-2001-CryptoPro-A-ParamSet", "id-GostR3410-2001-CryptoPro-A-ParamSet", 840, 7, objectData + 5584, 0}, + {"id-GostR3410-2001-CryptoPro-B-ParamSet", "id-GostR3410-2001-CryptoPro-B-ParamSet", 841, 7, objectData + 5591, 0}, + {"id-GostR3410-2001-CryptoPro-C-ParamSet", "id-GostR3410-2001-CryptoPro-C-ParamSet", 842, 7, objectData + 5598, 0}, + {"id-GostR3410-2001-CryptoPro-XchA-ParamSet", "id-GostR3410-2001-CryptoPro-XchA-ParamSet", 843, 7, objectData + 5605, 0}, + {"id-GostR3410-2001-CryptoPro-XchB-ParamSet", "id-GostR3410-2001-CryptoPro-XchB-ParamSet", 844, 7, objectData + 5612, 0}, + {"id-GostR3410-94-a", "id-GostR3410-94-a", 845, 7, objectData + 5619, 0}, + {"id-GostR3410-94-aBis", "id-GostR3410-94-aBis", 846, 7, objectData + 5626, 0}, + {"id-GostR3410-94-b", "id-GostR3410-94-b", 847, 7, objectData + 5633, 0}, + {"id-GostR3410-94-bBis", "id-GostR3410-94-bBis", 848, 7, objectData + 5640, 0}, + {"id-Gost28147-89-cc", "GOST 28147-89 Cryptocom ParamSet", 849, 8, objectData + 5647, 0}, + {"gost94cc", "GOST 34.10-94 Cryptocom", 850, 8, objectData + 5655, 0}, + {"gost2001cc", "GOST 34.10-2001 Cryptocom", 851, 8, objectData + 5663, 0}, + {"id-GostR3411-94-with-GostR3410-94-cc", "GOST R 34.11-94 with GOST R 34.10-94 Cryptocom", 852, 8, objectData + 5671, 0}, + {"id-GostR3411-94-with-GostR3410-2001-cc", "GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom", 853, 8, objectData + 5679, 0}, + {"id-GostR3410-2001-ParamSet-cc", "GOST R 3410-2001 Parameter Set Cryptocom", 854, 8, objectData + 5687, 0}, + {"HMAC", "hmac", 855, 0, nullptr, 0}, + {"LocalKeySet", "Microsoft Local Key set", 856, 9, objectData + 5695, 0}, + {"freshestCRL", "X509v3 Freshest CRL", 857, 3, objectData + 5704, 0}, + {"id-on-permanentIdentifier", "Permanent Identifier", 858, 8, objectData + 5707, 0}, + {"searchGuide", "searchGuide", 859, 3, objectData + 5715, 0}, + {"businessCategory", "businessCategory", 860, 3, objectData + 5718, 0}, + {"postalAddress", "postalAddress", 861, 3, objectData + 5721, 0}, + {"postOfficeBox", "postOfficeBox", 862, 3, objectData + 5724, 0}, + {"physicalDeliveryOfficeName", "physicalDeliveryOfficeName", 863, 3, objectData + 5727, 0}, + {"telephoneNumber", "telephoneNumber", 864, 3, objectData + 5730, 0}, + {"telexNumber", "telexNumber", 865, 3, objectData + 5733, 0}, + {"teletexTerminalIdentifier", "teletexTerminalIdentifier", 866, 3, objectData + 5736, 0}, + {"facsimileTelephoneNumber", "facsimileTelephoneNumber", 867, 3, objectData + 5739, 0}, + {"x121Address", "x121Address", 868, 3, objectData + 5742, 0}, + {"internationaliSDNNumber", "internationaliSDNNumber", 869, 3, objectData + 5745, 0}, + {"registeredAddress", "registeredAddress", 870, 3, objectData + 5748, 0}, + {"destinationIndicator", "destinationIndicator", 871, 3, objectData + 5751, 0}, + {"preferredDeliveryMethod", "preferredDeliveryMethod", 872, 3, objectData + 5754, 0}, + {"presentationAddress", "presentationAddress", 873, 3, objectData + 5757, 0}, + {"supportedApplicationContext", "supportedApplicationContext", 874, 3, objectData + 5760, 0}, + {"member", "member", 875, 3, objectData + 5763, 0}, + {"owner", "owner", 876, 3, objectData + 5766, 0}, + {"roleOccupant", "roleOccupant", 877, 3, objectData + 5769, 0}, + {"seeAlso", "seeAlso", 878, 3, objectData + 5772, 0}, + {"userPassword", "userPassword", 879, 3, objectData + 5775, 0}, + {"userCertificate", "userCertificate", 880, 3, objectData + 5778, 0}, + {"cACertificate", "cACertificate", 881, 3, objectData + 5781, 0}, + {"authorityRevocationList", "authorityRevocationList", 882, 3, objectData + 5784, 0}, + {"certificateRevocationList", "certificateRevocationList", 883, 3, objectData + 5787, 0}, + {"crossCertificatePair", "crossCertificatePair", 884, 3, objectData + 5790, 0}, + {"enhancedSearchGuide", "enhancedSearchGuide", 885, 3, objectData + 5793, 0}, + {"protocolInformation", "protocolInformation", 886, 3, objectData + 5796, 0}, + {"distinguishedName", "distinguishedName", 887, 3, objectData + 5799, 0}, + {"uniqueMember", "uniqueMember", 888, 3, objectData + 5802, 0}, + {"houseIdentifier", "houseIdentifier", 889, 3, objectData + 5805, 0}, + {"supportedAlgorithms", "supportedAlgorithms", 890, 3, objectData + 5808, 0}, + {"deltaRevocationList", "deltaRevocationList", 891, 3, objectData + 5811, 0}, + {"dmdName", "dmdName", 892, 3, objectData + 5814, 0}, + {"id-alg-PWRI-KEK", "id-alg-PWRI-KEK", 893, 11, objectData + 5817, 0}, + {"CMAC", "cmac", 894, 0, nullptr, 0}, + {"id-aes128-GCM", "aes-128-gcm", 895, 9, objectData + 5828, 0}, + {"id-aes128-CCM", "aes-128-ccm", 896, 9, objectData + 5837, 0}, + {"id-aes128-wrap-pad", "id-aes128-wrap-pad", 897, 9, objectData + 5846, 0}, + {"id-aes192-GCM", "aes-192-gcm", 898, 9, objectData + 5855, 0}, + {"id-aes192-CCM", "aes-192-ccm", 899, 9, objectData + 5864, 0}, + {"id-aes192-wrap-pad", "id-aes192-wrap-pad", 900, 9, objectData + 5873, 0}, + {"id-aes256-GCM", "aes-256-gcm", 901, 9, objectData + 5882, 0}, + {"id-aes256-CCM", "aes-256-ccm", 902, 9, objectData + 5891, 0}, + {"id-aes256-wrap-pad", "id-aes256-wrap-pad", 903, 9, objectData + 5900, 0}, + {"AES-128-CTR", "aes-128-ctr", 904, 0, nullptr, 0}, + {"AES-192-CTR", "aes-192-ctr", 905, 0, nullptr, 0}, + {"AES-256-CTR", "aes-256-ctr", 906, 0, nullptr, 0}, + {"id-camellia128-wrap", "id-camellia128-wrap", 907, 11, objectData + 5909, 0}, + {"id-camellia192-wrap", "id-camellia192-wrap", 908, 11, objectData + 5920, 0}, + {"id-camellia256-wrap", "id-camellia256-wrap", 909, 11, objectData + 5931, 0}, + {"anyExtendedKeyUsage", "Any Extended Key Usage", 910, 4, objectData + 5942, 0}, + {"MGF1", "mgf1", 911, 9, objectData + 5946, 0}, + {"RSASSA-PSS", "rsassaPss", 912, 9, objectData + 5955, 0}, + {"AES-128-XTS", "aes-128-xts", 913, 0, nullptr, 0}, + {"AES-256-XTS", "aes-256-xts", 914, 0, nullptr, 0}, + {"RC4-HMAC-MD5", "rc4-hmac-md5", 915, 0, nullptr, 0}, + {"AES-128-CBC-HMAC-SHA1", "aes-128-cbc-hmac-sha1", 916, 0, nullptr, 0}, + {"AES-192-CBC-HMAC-SHA1", "aes-192-cbc-hmac-sha1", 917, 0, nullptr, 0}, + {"AES-256-CBC-HMAC-SHA1", "aes-256-cbc-hmac-sha1", 918, 0, nullptr, 0}, + {"RSAES-OAEP", "rsaesOaep", 919, 9, objectData + 5964, 0}, + {"teletrust", "teletrust", 920, 2, objectData + 5973, 0}, + {"brainpool", "brainpool", 921, 7, objectData + 5975, 0}, + {"brainpoolP160r1", "brainpoolP160r1", 922, 9, objectData + 5982, 0}, + {"brainpoolP160t1", "brainpoolP160t1", 923, 9, objectData + 5991, 0}, + {"brainpoolP192r1", "brainpoolP192r1", 924, 9, objectData + 6000, 0}, + {"brainpoolP192t1", "brainpoolP192t1", 925, 9, objectData + 6009, 0}, + {"brainpoolP224r1", "brainpoolP224r1", 926, 9, objectData + 6018, 0}, + {"brainpoolP224t1", "brainpoolP224t1", 927, 9, objectData + 6027, 0}, + {"brainpoolP256r1", "brainpoolP256r1", 928, 9, objectData + 6036, 0}, + {"brainpoolP256t1", "brainpoolP256t1", 929, 9, objectData + 6045, 0}, + {"brainpoolP320r1", "brainpoolP320r1", 930, 9, objectData + 6054, 0}, + {"brainpoolP320t1", "brainpoolP320t1", 931, 9, objectData + 6063, 0}, + {"brainpoolP384r1", "brainpoolP384r1", 932, 9, objectData + 6072, 0}, + {"brainpoolP384t1", "brainpoolP384t1", 933, 9, objectData + 6081, 0}, + {"brainpoolP512r1", "brainpoolP512r1", 934, 9, objectData + 6090, 0}, + {"brainpoolP512t1", "brainpoolP512t1", 935, 9, objectData + 6099, 0}, + {"FRP256v1", "FRP256v1", 936, 10, objectData + 6108, 0}, + {"ChaCha", "chacha", 937, 0, nullptr, 0}, + {"gost89-ecb", "gost89-ecb", 938, 0, nullptr, 0}, + {"gost89-cbc", "gost89-cbc", 939, 0, nullptr, 0}, + {"tc26", "tc26", 940, 5, objectData + 6118, 0}, + {"streebog256", "GOST R 34.11-2012 (256 bit)", 941, 8, objectData + 6123, 0}, + {"streebog512", "GOST R 34-11-2012 (512 bit)", 942, 8, objectData + 6131, 0}, + {"id-tc26-gost-3410-2012-512-paramSetA", "id-tc26-gost-3410-2012-512-paramSetA", 943, 9, objectData + 6139, 0}, + {"id-tc26-gost-3410-2012-512-paramSetB", "id-tc26-gost-3410-2012-512-paramSetB", 944, 9, objectData + 6148, 0}, + {"id-tc26-gost-28147-param-Z", "id-tc26-gost-28147-param-Z", 945, 9, objectData + 6157, 0}, + {"id-tc26-gost3410-2012-256", "GOST R 34.10-2012 (256 bit)", 946, 8, objectData + 6166, 0}, + {"id-tc26-gost3410-2012-512", "GOST R 34.10-2012 (512 bit)", 947, 8, objectData + 6174, 0}, + {"id-tc26-signwithdigest-gost3410-2012-256", "GOST R 34.11-2012 with GOST R 34.10-2012 (256 bit)", 948, 8, objectData + 6182, 0}, + {"id-tc26-signwithdigest-gost3410-2012-512", "GOST R 34.11-2012 with GOST R 34.10-2012 (512 bit)", 949, 8, objectData + 6190, 0}, + {"X25519", "X25519", 950, 3, objectData + 6198, 0}, + {"X448", "X448", 951, 3, objectData + 6201, 0}, + {"Ed25519", "Ed25519", 952, 3, objectData + 6204, 0}, + {"Ed448", "Ed448", 953, 3, objectData + 6207, 0}, + {"Ed25519ph", "Ed25519ph", 954, 3, objectData + 6210, 0}, + {"Ed448ph", "Ed448ph", 955, 3, objectData + 6213, 0}, +}; + +const unsigned short LibreSslObjectOrder_nid_no_patch[LibreSslObjectOrderCount] = { + 0, 181, 393, 404, 645, 646, 434, 182, 379, 676, 11, 647, 380, 920, 12, 378, + 81, 512, 678, 435, 183, 381, 950, 951, 952, 953, 954, 955, 677, 394, 13, 100, + 105, 14, 15, 16, 660, 17, 18, 106, 107, 859, 860, 861, 661, 862, 863, 864, + 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, + 881, 882, 883, 884, 173, 99, 101, 509, 503, 174, 885, 886, 887, 888, 889, 890, + 891, 892, 510, 400, 769, 82, 83, 84, 85, 86, 87, 88, 141, 430, 142, 140, + 770, 771, 666, 103, 89, 747, 90, 401, 126, 857, 748, 402, 403, 513, 514, 515, + 516, 517, 518, 679, 382, 383, 384, 385, 386, 387, 388, 376, 395, 19, 96, 95, + 746, 910, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, + 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, + 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, + 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, + 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, + 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 620, 621, 622, 623, 607, 608, + 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 636, 640, 641, 637, 638, + 639, 805, 806, 940, 184, 405, 389, 504, 104, 29, 31, 45, 30, 377, 67, 66, + 42, 32, 41, 64, 70, 115, 117, 143, 721, 722, 728, 717, 718, 704, 705, 709, + 708, 714, 723, 729, 730, 719, 720, 724, 725, 726, 727, 706, 707, 710, 711, 712, + 713, 715, 716, 731, 732, 733, 734, 624, 625, 626, 627, 628, 629, 630, 642, 735, + 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 804, 124, 773, 807, 808, 809, + 810, 811, 812, 813, 815, 816, 817, 818, 1, 185, 127, 505, 506, 119, 631, 632, + 633, 634, 635, 436, 820, 819, 845, 846, 847, 848, 821, 822, 823, 824, 825, 826, + 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, + 843, 844, 2, 431, 432, 433, 116, 113, 406, 407, 408, 416, 791, 792, 258, 175, + 259, 128, 260, 261, 262, 263, 264, 265, 266, 267, 268, 662, 176, 507, 508, 921, + 57, 754, 766, 757, 755, 767, 758, 756, 768, 759, 437, 776, 777, 779, 778, 852, + 853, 850, 851, 849, 854, 946, 947, 941, 942, 948, 949, 186, 27, 187, 20, 47, + 3, 257, 4, 797, 163, 798, 799, 800, 801, 37, 5, 44, 120, 643, 680, 684, + 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, + 701, 702, 703, 409, 410, 411, 412, 413, 414, 415, 793, 794, 795, 796, 269, 270, + 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 177, 285, + 286, 287, 288, 289, 290, 291, 292, 397, 398, 663, 164, 165, 293, 129, 130, 131, + 132, 294, 295, 296, 133, 180, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, + 307, 308, 309, 310, 311, 312, 784, 313, 314, 323, 324, 325, 326, 327, 328, 329, + 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 858, 348, 349, 351, 352, 353, 354, 355, 356, 357, 358, 399, 359, 360, + 361, 362, 664, 665, 667, 178, 179, 363, 364, 785, 780, 781, 58, 59, 438, 439, + 440, 441, 943, 944, 945, 108, 112, 782, 783, 6, 7, 396, 8, 65, 644, 919, + 911, 912, 668, 669, 670, 671, 28, 9, 10, 168, 169, 170, 68, 69, 161, 162, + 21, 22, 23, 24, 25, 26, 48, 49, 50, 51, 52, 53, 54, 55, 56, 172, + 167, 188, 156, 157, 681, 682, 683, 417, 856, 390, 91, 315, 316, 317, 318, 319, + 320, 321, 322, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 922, 923, + 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 418, 419, 420, 421, + 788, 895, 896, 897, 422, 423, 424, 425, 789, 898, 899, 900, 426, 427, 428, 429, + 790, 901, 902, 903, 672, 673, 674, 675, 802, 803, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 139, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, + 470, 471, 472, 473, 474, 475, 476, 477, 391, 478, 479, 480, 481, 482, 483, 484, + 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, + 501, 502, 442, 443, 444, 445, 446, 447, 448, 449, 392, 450, 451, 452, 453, 454, + 455, 456, 457, 936, 189, 190, 191, 192, 193, 194, 195, 158, 159, 160, 144, 145, + 146, 147, 148, 149, 171, 134, 135, 136, 137, 138, 648, 649, 751, 752, 753, 907, + 908, 909, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 786, 787, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 125, 893, 248, 249, 250, 251, 252, 253, + 254, 255, 256, 150, 151, 152, 153, 154, 155, 34, +}; + +} diff --git a/core/libs/prx/libSceLibreSsl/src/ObjectText.cpp b/core/libs/prx/libSceLibreSsl/src/ObjectText.cpp new file mode 100644 index 0000000000..3d954047ac --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/ObjectText.cpp @@ -0,0 +1,231 @@ +#include "prx/libSceLibreSsl/include/ObjectTable.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static_assert(sizeof(LibreSslAsn1Object) == 40); +static_assert(offsetof(LibreSslAsn1Object, longName) == 8); +static_assert(offsetof(LibreSslAsn1Object, nid) == 16); +static_assert(offsetof(LibreSslAsn1Object, length) == 20); +static_assert(offsetof(LibreSslAsn1Object, data) == 24); +static_assert(offsetof(LibreSslAsn1Object, flags) == 32); + +namespace { + +constexpr int ObjectLibrary = 8; +constexpr int BigNumberLibrary = 3; +constexpr int AllocationFailure = 65; +constexpr int AnyFunction = 0xfff; +constexpr int UnknownNid = 101; + +class BigNumber { +public: + explicit BigNumber(std::uint64_t value) : limbs{static_cast(value), static_cast(value >> 32)} {} + + void ShiftLeft7() { + std::uint32_t carry = 0; + for (auto& limb : limbs) { + const std::uint32_t next = limb >> 25; + limb = (limb << 7) | carry; + carry = next; + } + if (carry) limbs.push_back(carry); + } + + void Add(std::uint32_t value) { + for (auto& limb : limbs) { + const std::uint64_t sum = static_cast(limb) + value; + limb = static_cast(sum); + value = static_cast(sum >> 32); + if (!value) return; + } + limbs.push_back(value); + } + + void Subtract(std::uint32_t value) { + for (auto& limb : limbs) { + const std::uint32_t before = limb; + limb -= value; + if (before >= value) return; + value = 1; + } + } + + std::string Decimal() const { + std::vector number = limbs; + std::string digits; + while (std::any_of(number.begin(), number.end(), [](std::uint32_t limb) { return limb != 0; })) { + std::uint64_t remainder = 0; + for (auto limb = number.rbegin(); limb != number.rend(); ++limb) { + const std::uint64_t current = (remainder << 32) | *limb; + *limb = static_cast(current / 10); + remainder = current % 10; + } + digits.push_back(static_cast('0' + remainder)); + } + if (digits.empty()) digits = "0"; + std::reverse(digits.begin(), digits.end()); + return digits; + } + +private: + std::vector limbs; +}; + +int CompareObject(const LibreSslAsn1Object* key, const LibreSslAsn1Object* entry) { + const int difference = key->length - entry->length; + if (difference) return difference; + return key->length > 0 ? std::memcmp(key->data, entry->data, static_cast(key->length)) : 0; +} + +const char* Name(int nid, bool longName) { + if (nid < 0 || nid >= LibreSslObjectCount) return nullptr; + const auto& object = LibreSslObjects_nid_no_patch[nid]; + if (nid != 0 && object.nid == 0) { + LibreSslPutError_nid_no_patch(ObjectLibrary, AnyFunction, UnknownNid); + return nullptr; + } + return longName ? object.longName : object.shortName; +} + +void Write(char*& buffer, int& space, std::string_view text) { + if (space > 0) { + const std::size_t copied = std::min(text.size(), static_cast(space - 1)); + std::memcpy(buffer, text.data(), copied); + buffer[copied] = '\0'; + } + const int written = static_cast(text.size()); + if (written >= space) { + space = 0; + } else { + buffer += written; + space -= written; + } +} + +int NumericObjectText(char* buffer, int space, const LibreSslAsn1Object* object) { + int result = 0; + int remaining = object->length; + const unsigned char* bytes = object->data; + bool first = true; + while (remaining > 0) { + std::uint64_t value = 0; + bool big = false; + std::optional number; + for (;;) { + const unsigned char byte = *bytes++; + --remaining; + if (remaining == 0 && (byte & 0x80)) return 0; + if (big) number->Add(byte & 0x7f); + else value |= byte & 0x7f; + if (!(byte & 0x80)) break; + if (!big && value > (std::numeric_limits::max() >> 7)) { + number.emplace(value); + big = true; + } + if (big) number->ShiftLeft7(); + else value <<= 7; + } + if (first) { + first = false; + int arc; + if (value >= 80) { + arc = 2; + if (big) number->Subtract(80); + else value -= 80; + } else { + arc = static_cast(value / 40); + value -= static_cast(arc) * 40; + } + if (space > 1) { + *buffer++ = static_cast('0' + arc); + *buffer = '\0'; + --space; + } + ++result; + } + if (big) { + const std::string text = "." + number->Decimal(); + Write(buffer, space, text); + result += static_cast(text.size()); + } else { + char text[32]; + text[0] = '.'; + const auto converted = std::to_chars(text + 1, text + sizeof(text), value); + const std::string_view part(text, static_cast(converted.ptr - text)); + Write(buffer, space, part); + result += static_cast(part.size()); + } + } + return result; +} + +int ObjectText(char* buffer, int space, const LibreSslAsn1Object* object, const char* function) { + if (space < 0 || (space > 0 && !buffer)) NotImplemented_nid_no_patch(function); + if (space > 0) buffer[0] = '\0'; + if (!object || !object->data) return 0; + const int nid = LibreSslObjectNid_nid_no_patch(object); + if (nid != 0) { + const char* name = LibreSslObjectLongName_nid_no_patch(nid); + if (!name) name = LibreSslObjectShortName_nid_no_patch(nid); + if (name) { + const std::size_t length = std::strlen(name); + if (space > 0) { + const std::size_t copied = std::min(length, static_cast(space - 1)); + std::memcpy(buffer, name, copied); + buffer[copied] = '\0'; + } + return static_cast(length); + } + } + try { + return NumericObjectText(buffer, space, object); + } catch (const std::bad_alloc&) { + LibreSslPutError_nid_no_patch(BigNumberLibrary, AnyFunction, AllocationFailure); + return 0; + } +} + +} // namespace + +extern "C" { + +int LibreSslObjectNid_nid_no_patch(const LibreSslAsn1Object* object) { + if (!object) return 0; + if (object->nid != 0) return object->nid; + int low = 0; + int high = LibreSslObjectOrderCount; + while (low < high) { + const int middle = (low + high) / 2; + const auto& entry = LibreSslObjects_nid_no_patch[LibreSslObjectOrder_nid_no_patch[middle]]; + const int comparison = CompareObject(object, &entry); + if (comparison < 0) high = middle; + else if (comparison > 0) low = middle + 1; + else return entry.nid; + } + return 0; +} + +const char* LibreSslObjectShortName_nid_no_patch(int nid) { + return Name(nid, false); +} + +const char* LibreSslObjectLongName_nid_no_patch(int nid) { + return Name(nid, true); +} + +int APS5_VABI i2t_ASN1_OBJECT_nid_postfix(char* buffer, int length, const LibreSslAsn1Object* object) { + return ObjectText(buffer, length, object, __func__); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp b/core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp new file mode 100644 index 0000000000..e028e86c4e --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509StoreContext.cpp @@ -0,0 +1,45 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslX509StoreContext) == 240); +static_assert(offsetof(LibreSslX509StoreContext, chain) == 160); +static_assert(offsetof(LibreSslX509StoreContext, errorDepth) == 180); +static_assert(offsetof(LibreSslX509StoreContext, error) == 184); +static_assert(offsetof(LibreSslX509StoreContext, currentCert) == 192); +static_assert(offsetof(LibreSslX509StoreContext, exData) == 232); + +namespace { + +LibreSslX509StoreContext* Context(LibreSslX509StoreContext* context, const char* function) { + if (!context) throw std::invalid_argument(std::string(function) + ": null context"); + return context; +} + +} + +extern "C" { + +int APS5_VABI X509_STORE_CTX_get_error_nid_postfix(LibreSslX509StoreContext* context) { + return Context(context, __func__)->error; +} + +void APS5_VABI X509_STORE_CTX_set_error_nid_postfix(LibreSslX509StoreContext* context, int error) { + Context(context, __func__)->error = error; +} + +int APS5_VABI X509_STORE_CTX_get_error_depth_nid_postfix(LibreSslX509StoreContext* context) { + return Context(context, __func__)->errorDepth; +} + +void* APS5_VABI X509_STORE_CTX_get_current_cert_nid_postfix(LibreSslX509StoreContext* context) { + return Context(context, __func__)->currentCert; +} + +void* APS5_VABI X509_STORE_CTX_get_chain_nid_postfix(LibreSslX509StoreContext* context) { + return Context(context, __func__)->chain; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/tests/ExData.cpp b/core/libs/prx/libSceLibreSsl/tests/ExData.cpp new file mode 100644 index 0000000000..b037aaaf1f --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/tests/ExData.cpp @@ -0,0 +1,72 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +int APS5_VABI NewCallback(void*, void*, LibreSslExData*, int, std::int64_t, void*) { + std::abort(); +} + +void APS5_VABI FreeCallback(void*, void*, LibreSslExData*, int, std::int64_t, void*) { + std::abort(); +} + +int APS5_VABI DupCallback(LibreSslExData*, LibreSslExData*, void*, int, std::int64_t, void*) { + std::abort(); +} + +int NewSslIndex() { + return SSL_get_ex_new_index_nid_postfix(7, nullptr, nullptr, nullptr, nullptr); +} + +int NewContextIndex() { + int marker = 0; + return SSL_CTX_get_ex_new_index_nid_postfix(-1, &marker, nullptr, nullptr, nullptr); +} + +} + +int main() { + Require(NewSslIndex() == 0 && NewSslIndex() == 1 && NewSslIndex() == 2, "SSL indices count from 0"); + Require(NewContextIndex() == 0 && NewContextIndex() == 1, "SSL_CTX indices are counted apart from SSL ones"); + Require(SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix() == 0, "the verify-callback index is the first X509_STORE_CTX index"); + Require(SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix() == 0, "the verify-callback index is allocated once"); + Require(NewSslIndex() == 3, "the verify-callback index does not use an SSL index"); + + CRYPTO_cleanup_all_ex_data_nid_postfix(); + Require(NewSslIndex() == 0 && NewContextIndex() == 0, "cleanup releases every class"); + Require(SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix() == 0, "cleanup keeps the cached verify-callback index"); + CRYPTO_cleanup_all_ex_data_nid_postfix(); + CRYPTO_cleanup_all_ex_data_nid_postfix(); + Require(NewContextIndex() == 0, "cleanup can be repeated"); + + CRYPTO_cleanup_all_ex_data_nid_postfix(); + int argument = 0; + Require(SSL_get_ex_new_index_nid_postfix(-5, &argument, NewCallback, DupCallback, FreeCallback) == 0, "typed SSL callbacks are accepted and stored"); + Require(SSL_CTX_get_ex_new_index_nid_postfix(9, &argument, NewCallback, DupCallback, FreeCallback) == 0, "typed SSL_CTX callbacks are accepted and stored"); + Require(NewSslIndex() == 1 && NewContextIndex() == 1, "indices with callbacks count like any other"); + CRYPTO_cleanup_all_ex_data_nid_postfix(); + std::vector threads; + std::vector indices(64, -1); + for (int thread = 0; thread < 8; ++thread) + threads.emplace_back([&indices, thread] { + for (int round = 0; round < 8; ++round) indices[thread * 8 + round] = NewSslIndex(); + }); + for (auto& thread : threads) thread.join(); + std::vector seen(indices.size(), false); + for (const int index : indices) { + Require(index >= 0 && index < static_cast(indices.size()) && !seen[index], "concurrent callers get distinct indices"); + seen[index] = true; + } + return 0; +} diff --git a/core/libs/prx/libSceLibreSsl/tests/ObjectText.cpp b/core/libs/prx/libSceLibreSsl/tests/ObjectText.cpp new file mode 100644 index 0000000000..b0f93aa427 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/tests/ObjectText.cpp @@ -0,0 +1,138 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libSceLibreSsl/include/ObjectTable.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint64_t UnknownNidError = (8ull << 24) | (0xfffull << 12) | 101; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +template +void RequireThrows(TAction action, const char* message) { + try { + action(); + } catch (const std::exception&) { + return; + } + Require(false, message); +} + +struct Encoded { + std::vector bytes; + LibreSslAsn1Object object; + + Encoded(std::initializer_list data, int nid = 0) : bytes(data), object{nullptr, nullptr, nid, static_cast(bytes.size()), bytes.data(), 0} {} +}; + +void RequireText(const LibreSslAsn1Object& object, int space, int expected, const char* text, const char* message) { + char buffer[128]; + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(i2t_ASN1_OBJECT_nid_postfix(buffer, space, &object) == expected, message); + Require(std::strcmp(buffer, text) == 0, message); + Require(static_cast(buffer[std::strlen(text) + 1]) == 0x5a || std::strlen(text) + 1 >= static_cast(space), + "nothing is written after the terminator"); +} + +void Table() { + const auto& undefined = LibreSslObjects_nid_no_patch[0]; + Require(std::strcmp(undefined.shortName, "UNDEF") == 0 && std::strcmp(undefined.longName, "undefined") == 0 && !undefined.data, "NID 0"); + const auto& commonName = LibreSslObjects_nid_no_patch[13]; + Require(commonName.nid == 13 && std::strcmp(commonName.shortName, "CN") == 0 && std::strcmp(commonName.longName, "commonName") == 0 + && commonName.length == 3 && std::memcmp(commonName.data, "\x55\x04\x03", 3) == 0 && commonName.flags == 0, "commonName"); + const auto& last = LibreSslObjects_nid_no_patch[LibreSslObjectCount - 1]; + Require(last.nid == 955 && std::strcmp(last.shortName, "Ed448ph") == 0, "the last 2.5.4 NID"); + for (int nid : {102, 118, 350, 511, 772, 774, 775}) { + const auto& gap = LibreSslObjects_nid_no_patch[nid]; + Require(!gap.shortName && !gap.longName && gap.nid == 0 && gap.length == 0 && !gap.data, "unassigned NIDs are empty"); + } + for (int index = 0; index < LibreSslObjectOrderCount; ++index) + Require(LibreSslObjectOrder_nid_no_patch[index] < LibreSslObjectCount, "the search order indexes the table"); +} + +void Lookup() { + Require(LibreSslObjectNid_nid_no_patch(nullptr) == 0, "null object"); + Encoded commonName{0x55, 0x04, 0x03}; + Require(LibreSslObjectNid_nid_no_patch(&commonName.object) == 13, "an encoding found by search"); + Encoded preset{0x55, 0x04, 0x03, 0x01}, unknown{0x2a, 0x03, 0x04}; + preset.object.nid = 6; + Require(LibreSslObjectNid_nid_no_patch(&preset.object) == 6, "a set NID wins without a search"); + Require(LibreSslObjectNid_nid_no_patch(&unknown.object) == 0, "an unknown encoding"); + Encoded organization{0x2b}; + Require(LibreSslObjectNid_nid_no_patch(&organization.object) == 379, "1.3 is listed twice; the search lands on ORG as in LibreSSL"); + unsigned char none = 0; + LibreSslAsn1Object empty{nullptr, nullptr, 0, 0, &none, 0}; + Require(LibreSslObjectNid_nid_no_patch(&empty) == 404, "an empty encoding lands on NID_ccitt"); +} + +void Names() { + Encoded commonName{0x55, 0x04, 0x03}; + RequireText(commonName.object, 64, 10, "commonName", "the long name"); + RequireText(commonName.object, 5, 10, "comm", "a short buffer truncates and returns the full length"); + RequireText(commonName.object, 1, 10, "", "a one-byte buffer holds the terminator"); + Require(i2t_ASN1_OBJECT_nid_postfix(nullptr, 0, &commonName.object) == 10, "a zero-length buffer is not written"); + Encoded named{0x2a, 0x03, 0x04}, sha256{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}; + named.object.nid = 13; + RequireText(named.object, 64, 10, "commonName", "a set NID names the object whatever its encoding"); + RequireText(sha256.object, 64, 6, "sha256", "a long name equal to the short name"); +} + +void Numbers() { + Encoded unknown{0x2a, 0x03, 0x04}; + RequireText(unknown.object, 64, 7, "1.2.3.4", "an unknown OID is printed in dotted form"); + RequireText(unknown.object, 4, 7, "1.2", "a dotted OID is truncated part by part"); + RequireText(unknown.object, 2, 7, "1", "a two-byte buffer keeps the first arc"); + RequireText(unknown.object, 1, 7, "", "a one-byte buffer keeps nothing"); + Encoded second{0x88, 0x37, 0x05}; + RequireText(second.object, 64, 7, "2.999.5", "a first subidentifier of 80 or more starts with 2"); + Encoded large{0x2a, 0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00}; + RequireText(large.object, 64, 24, "1.2.18446744073709551616", "an arc past 64 bits is printed exactly"); + Encoded largeFirst{0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00}; + RequireText(largeFirst.object, 64, 22, "2.18446744073709551536", "a first subidentifier past 64 bits loses 80"); + Encoded truncated{0x2a, 0x03, 0x83}; + RequireText(truncated.object, 64, 0, "1.2.3", "a truncated subidentifier returns 0 and keeps what was written"); + Encoded high{0x2a, 0x05}; + high.object.nid = 5000; + RequireText(high.object, 64, 5, "1.2.5", "a NID past the table prints the encoding"); + Require(ERR_get_error_nid_postfix() == 0, "a NID past the table reports no error"); +} + +void Errors() { + ERR_clear_error_nid_postfix(); + Encoded gap{0x2a, 0x07}; + gap.object.nid = 102; + RequireText(gap.object, 64, 5, "1.2.7", "an unassigned NID prints the encoding"); + Require(ERR_get_error_nid_postfix() == UnknownNidError && ERR_get_error_nid_postfix() == UnknownNidError + && ERR_get_error_nid_postfix() == 0, "the long and short name lookups each report OBJ_R_UNKNOWN_NID"); + LibreSslAsn1Object none{nullptr, nullptr, 13, 3, nullptr, 0}; + RequireText(none, 64, 0, "", "an object without data"); + Require(i2t_ASN1_OBJECT_nid_postfix(nullptr, 0, nullptr) == 0, "a null object"); + char buffer[8]; + RequireThrows([&] { i2t_ASN1_OBJECT_nid_postfix(buffer, -1, &gap.object); }, "a negative length"); + RequireThrows([&] { i2t_ASN1_OBJECT_nid_postfix(nullptr, 8, &gap.object); }, "a null buffer with a length"); +} + +} // namespace + +int main() { + std::array api{}; + ApplicationHeapRegister_nid_no_patch(api.data()); + Table(); + Lookup(); + Names(); + Numbers(); + Errors(); +} diff --git a/core/libs/prx/libSceLibreSsl/tests/ObjectTextAllocation.cpp b/core/libs/prx/libSceLibreSsl/tests/ObjectTextAllocation.cpp new file mode 100644 index 0000000000..d18cd76455 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/tests/ObjectTextAllocation.cpp @@ -0,0 +1,135 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +thread_local bool armed = false, fired = false; +thread_local std::size_t failAt = 0, allocations = 0; +thread_local std::array tracked{}; +std::size_t failures = 0, checks = 0, faultCases = 0, escaped = 0; +void Check(bool value, const char* label) { + ++checks; + if (value) return; + ++failures; + if (failures <= 8) std::fprintf(stderr, "FAIL: %s\n", label); +} +std::size_t Live() { + return std::count_if(tracked.begin(), tracked.end(), [](void* p) { return p != nullptr; }); +} +} + +void* operator new(std::size_t size) { + if (armed) { + ++allocations; + if (failAt == allocations) { fired = true; throw std::bad_alloc(); } + } + void* pointer = std::malloc(size ? size : 1); + if (!pointer) throw std::bad_alloc(); + if (armed) { + auto slot = std::find(tracked.begin(), tracked.end(), nullptr); + if (slot == tracked.end()) std::abort(); + *slot = pointer; + } + return pointer; +} +void* operator new[](std::size_t size) { return ::operator new(size); } +void operator delete(void* pointer) noexcept { + if (pointer) { + auto slot = std::find(tracked.begin(), tracked.end(), pointer); + if (slot != tracked.end()) *slot = nullptr; + } + std::free(pointer); +} +void operator delete[](void* pointer) noexcept { ::operator delete(pointer); } +void operator delete(void* pointer, std::size_t) noexcept { ::operator delete(pointer); } +void operator delete[](void* pointer, std::size_t) noexcept { ::operator delete(pointer); } + +struct NumericRun { + std::array buffer; + int returned; + bool threw, allocationFailed; + std::size_t calls, live; + std::uint64_t error, following; +}; +NumericRun Run(const unsigned char* data, int length, int space, std::size_t fail, bool seed = false) { + NumericRun result{}; + result.buffer.fill('Z'); + LibreSslAsn1Object object{nullptr, nullptr, 0, length, data, 0}; + ERR_clear_error_nid_postfix(); + if (seed) LibreSslPutError_nid_no_patch(8, 0xfff, 101); + allocations = 0; failAt = fail; fired = false; + armed = true; + try { result.returned = i2t_ASN1_OBJECT_nid_postfix(result.buffer.data(), space, &object); } + catch (const std::bad_alloc&) { result.threw = true; } + armed = false; + result.calls = allocations; + result.allocationFailed = fired; + result.live = Live(); + result.error = ERR_get_error_nid_postfix(); + result.following = ERR_get_error_nid_postfix(); + return result; +} + +int main() { + constexpr std::uint64_t allocationError = (3ull << 24) | (0xfffull << 12) | 65; + std::size_t fastCases = 0; + const unsigned char warmUp[] {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f}; + Run(warmUp, sizeof(warmUp), 0, 1); + tracked.fill(nullptr); + const unsigned char shortOid[] {0x2a, 0x03, 0x04}; + const unsigned char maxWord[] {0x81,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f}; + const unsigned char malformed[] {0x2a,0x81}; + for (const auto bytes : {std::pair{shortOid, sizeof(shortOid)}, std::pair{maxWord, sizeof(maxWord)}, std::pair{malformed, sizeof(malformed)}}) { + for (int space : {0, 1, 2, 5, 32, 512}) { + const auto expected = Run(bytes.first, static_cast(bytes.second), space, 0); + const auto result = Run(bytes.first, static_cast(bytes.second), space, 1); + ++fastCases; + Check(!result.threw && result.returned == expected.returned && result.buffer == expected.buffer, "word/malformed path works when host new would fail"); + Check(result.calls == 0 && !result.allocationFailed, "word/malformed path does not allocate"); + Check(result.live == 0 && result.error == 0, "word/malformed path preserves empty error queue"); + escaped += result.threw; + } + } + for (int size : {11, 20, 40}) { + for (bool later : {false, true}) { + std::vector bytes; + if (later) bytes = {0x2a, 0x03}; + bytes.insert(bytes.end(), static_cast(size), 0xff); + bytes.back() = 0x7f; + for (int space : {0, 1, 5, 512}) { + const auto normal = Run(bytes.data(), static_cast(bytes.size()), space, 0); + for (std::size_t point = 1; point <= normal.calls; ++point) { + const auto result = Run(bytes.data(), static_cast(bytes.size()), space, point); + ++faultCases; + escaped += result.threw; + Check(result.allocationFailed, "selected big-number allocation actually failed"); + Check(!result.threw && result.returned == 0, "big-number allocation failure returns zero"); + Check(result.error == allocationError && result.following == 0, "big-number allocation failure uses BN malloc error"); + Check(result.live == 0, "failed conversion releases scratch allocations"); + if (space > 0) { + const void* end = std::memchr(result.buffer.data(), 0, static_cast(space)); + Check(end != nullptr, "failed conversion leaves output terminated"); + } + } + } + } + } + const unsigned char big[] {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f}; + const auto seeded = Run(big, sizeof(big), 64, 1, true); + escaped += seeded.threw; + Check(!seeded.threw && seeded.returned == 0 && seeded.error == ((8ull << 24) | (0xfffull << 12) | 101) && seeded.following == allocationError, + "allocation error appends to existing queue entries"); + Check(ERR_get_error_nid_postfix() == 0 && seeded.live == 0, "seeded failure adds exactly one error and leaks nothing"); + std::printf("{\"fast_cases\":%zu,\"allocation_fault_cases\":%zu,\"checks\":%zu,\"failures\":%zu,\"escaped\":%zu,\"final_live\":%zu}\n", + fastCases, faultCases, checks, failures, escaped, Live()); + return failures ? 1 : 0; +} diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 3ff9dad773..29c7a2d2dd 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -289,6 +289,7 @@ struct Sock { int rcv_buffer = 0; int onesbcast = 0; int keepalive = 0; + std::string name; }; #ifdef _WIN32 @@ -345,6 +346,7 @@ HostMutex g_mutex; HostCondition g_cv; std::map g_socks; std::set g_epolls; +std::map g_epoll_names; using EpollRegistrations = std::map>; std::map g_epoll_socks; std::set g_epoll_aborted; @@ -373,6 +375,7 @@ int* errno_slot() { } extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity); +extern "C" bool NetCtlHostIpv4Address_nid_no_patch(std::uint32_t* address, std::uint32_t* netmask, std::uint32_t* mtu); std::mutex g_dnsLock; bool g_dnsOverridden = false; @@ -745,12 +748,6 @@ int* APS5_VABI sceNetErrnoLoc(void) { return errno_slot(); } -int APS5_VABI sceNetShowNetstat(void) { - std::lock_guard lock(g_mutex); - if (!g_net_inited) return fail(NET_ENOTINIT); - throw std::runtime_error(__func__); -} - int PollSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) { std::vector> natives(static_cast(count)); { @@ -916,7 +913,6 @@ int APS5_VABI sceNetPoolDestroy(int memid) { } int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) { - (void)name; if (family != NET_AF_INET && family != NET_AF_INET6) { return fail(NET_EAFNOSUPPORT); } @@ -925,7 +921,12 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) } if (!initialize_sockets()) return fail(5); const NativeSocket native = ::socket(family == NET_AF_INET ? AF_INET : AF_INET6, type, protocol); - if (native == INVALID_NATIVE_SOCKET) return fail(native_error()); + if (native == INVALID_NATIVE_SOCKET) { +#ifdef __APPLE__ + if (errno == EPROTOTYPE) return fail(NET_EPROTONOSUPPORT); +#endif // __APPLE__ + return fail(native_error()); + } #ifdef _WIN32 u_long nonblocking = 1; if (ioctlsocket(native, FIONBIO, &nonblocking) != 0) { @@ -958,6 +959,7 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) if (protocol == 0 && type == NET_SOCK_STREAM) s.protocol = IPPROTO_TCP; if (protocol == 0 && type == NET_SOCK_DGRAM) s.protocol = IPPROTO_UDP; s.native = std::move(handle); + if (name != nullptr) s.name = std::string(name).substr(0, 31); g_socks[fd] = s; return fd; } @@ -1922,10 +1924,70 @@ int APS5_VABI sceNetEpollCreate(const char* name, int flags) { std::lock_guard lk(g_mutex); const int id = g_next_epoll++; g_epolls.insert(id); + g_epoll_names[id] = name != nullptr ? std::string(name).substr(0, 31) : std::string(); g_epoll_socks.emplace(id, EpollRegistrations{}); return id; } +static std::string endpoint_text(const sockaddr_storage& address) { + char text[INET6_ADDRSTRLEN] = {}; + std::uint16_t port = 0; + std::string host; + if (address.ss_family == AF_INET6) { + const auto& v6 = reinterpret_cast(address); + static constexpr in6_addr any{}; + host = std::memcmp(&v6.sin6_addr, &any, sizeof(any)) == 0 || inet_ntop(AF_INET6, &v6.sin6_addr, text, sizeof(text)) == nullptr ? "[*]" : std::string("[") + text + "]"; + port = ntohs(v6.sin6_port); + } else { + const auto& v4 = reinterpret_cast(address); + host = address.ss_family != AF_INET || v4.sin_addr.s_addr == 0 || inet_ntop(AF_INET, &v4.sin_addr, text, sizeof(text)) == nullptr ? "*" : std::string(text); + port = address.ss_family == AF_INET ? ntohs(v4.sin_port) : 0; + } + return host + ":" + (port == 0 ? std::string("*") : std::to_string(port)); +} + +int APS5_VABI sceNetShowNetstat(void) { + std::vector> sockets; + std::vector> epolls; + { + std::lock_guard lock(g_mutex); + if (!g_net_inited) return fail(NET_ENOTINIT); + sockets.assign(g_socks.begin(), g_socks.end()); + epolls.assign(g_epoll_names.begin(), g_epoll_names.end()); + } + const int pid = getpid_nid_postfix(); + std::printf("--- ID, type, [self/R/S/RE/SE//NB], ... ---\n"); + for (const int type : {NET_SOCK_DGRAM, NET_SOCK_STREAM, NET_SOCK_RAW}) { + for (auto it = sockets.rbegin(); it != sockets.rend(); ++it) { + const auto& [id, socket] = *it; + if (socket.type != type) continue; + sockaddr_storage local{}; + sockaddr_storage remote{}; + NativeLength length = sizeof(local); + if (::getsockname(socket.native->value, reinterpret_cast(&local), &length) != 0) local = {}; + length = sizeof(remote); + const bool connected = ::getpeername(socket.native->value, reinterpret_cast(&remote), &length) == 0; + if (!connected) remote = {}; + local.ss_family = remote.ss_family = socket.family == NET_AF_INET6 ? AF_INET6 : AF_INET; + std::int32_t send = 0; + std::int32_t receive = 0; + if (sock_buffer_size(socket, true, send) != 0) send = 0; + if (sock_buffer_size(socket, false, receive) != 0) receive = 0; + const char* kind = type == NET_SOCK_STREAM ? "tcp" : type == NET_SOCK_DGRAM ? "udp" : "raw"; + const char* state = type != NET_SOCK_STREAM ? "OPENED" : socket.listening ? "LISTEN" : connected ? "ESTABLISHED" : "CLOSED"; + std::printf("ID: %4d (%5s, 0) *....-%c %s pid=0x%08x %s\n", id, kind, socket.native->nonblock ? '*' : '.', state, static_cast(pid), socket.name.c_str()); + std::printf(" Local : %s%28s\n", endpoint_text(local).c_str(), ""); + std::printf(" Remote: %s%28s\n", endpoint_text(remote).c_str(), ""); + std::printf(" Send-Q: 0/%d TX: 0bps/0bps, dropped 0 (0) \n", send); + std::printf(" Recv-Q: 0/%d RX: 0bps/0bps, dropped 0\n", receive); + } + } + for (auto it = epolls.rbegin(); it != epolls.rend(); ++it) + std::printf("ID: %4d (epoll) *....-. OPENED pid=0x%08x %s\n", it->first, static_cast(pid), it->second.c_str()); + std::fflush(stdout); + return 0; +} + int APS5_VABI sceNetShowNetstatWithMemory(int memid) { struct SocketStatus { int id; int family; int type; bool nonblock; bool bound; bool listening; }; std::vector sockets; @@ -1963,6 +2025,7 @@ int APS5_VABI sceNetEpollDestroy(int eid) { if (g_epolls.erase(eid) == 0) { return fail(NET_EBADF); } + g_epoll_names.erase(eid); g_epoll_socks.erase(eid); g_epoll_aborted.erase(eid); g_cv.NotifyAll(); @@ -2155,6 +2218,13 @@ int APS5_VABI sceNetInetPton(int af, const char* src, void* dst) { return fail(NET_EINVAL); } const auto convert = [](int family, const std::string& text, unsigned char* address) { +#ifdef __APPLE__ + if (family == AF_INET) { + for (std::size_t i = 0; i + 1 < text.size(); ++i) { + if (text[i] == '0' && (i == 0 || text[i - 1] == '.') && text[i + 1] >= '0' && text[i + 1] <= '9') return 0; + } + } +#endif // __APPLE__ #ifdef _WIN32 return InetPtonA(family, text.c_str(), address); #else @@ -2423,9 +2493,38 @@ int APS5_VABI sceNetResolverAbort(int rid, int flags) { } int APS5_VABI sceNetShowIfconfig() { - std::lock_guard lock(g_mutex); - if (!g_net_inited) return fail(NET_ENOTINIT); - throw std::runtime_error(__func__); + { + std::lock_guard lock(g_mutex); + if (!g_net_inited) return fail(NET_ENOTINIT); + } + const auto dotted = [](std::uint32_t address) { + char text[INET_ADDRSTRLEN] = {}; + in_addr value{}; + value.s_addr = address; + return std::string(inet_ntop(AF_INET, &value, text, sizeof(text)) != nullptr ? text : "0.0.0.0"); + }; + std::uint32_t address = 0; + std::uint32_t netmask = 0; + std::uint32_t mtu = 0; + if (NetCtlHostIpv4Address_nid_no_patch(&address, &netmask, &mtu)) { + NetEtherAddr mac{}; + sceNetGetMacAddress(&mac, 0); + std::printf("eth0 (index=1)\n"); + std::printf(" Type:Ethernet HWaddr:%02x:%02x:%02x:%02x:%02x:%02x\n", mac.data[0], mac.data[1], mac.data[2], mac.data[3], mac.data[4], mac.data[5]); + std::printf(" inet:%s/%s Bcast:%s\n", dotted(address).c_str(), dotted(netmask).c_str(), dotted(address | ~netmask).c_str()); + std::printf(" UP BROADCAST MULTICAST INTERNET CONNECTED MTU:%u(max:%u) \n", mtu, mtu); + } + std::printf("lo0 (index=2)\n"); + std::printf(" inet:127.0.0.1/255.0.0.0\n"); + std::printf(" inet6:::1/128\n"); + std::printf(" UP MULTICAST CONNECTED MTU:16384(max:16384) \n"); + std::array servers{}; + NetCtlHostDnsServers_nid_no_patch(servers.data(), servers.size()); + std::printf("dns addresses\n"); + std::printf(" %s, %s\n", dotted(servers[0]).c_str(), dotted(servers[1]).c_str()); + std::printf(" ::, ::\n"); + std::fflush(stdout); + return 0; } extern const std::uint8_t in6addr_any_nid_postfix[16] = {}; diff --git a/core/libs/prx/libSceNet/tests/Ipv6Only.cpp b/core/libs/prx/libSceNet/tests/Ipv6Only.cpp index 9470a5be35..b34ae99196 100644 --- a/core/libs/prx/libSceNet/tests/Ipv6Only.cpp +++ b/core/libs/prx/libSceNet/tests/Ipv6Only.cpp @@ -48,6 +48,9 @@ static void Exercise(int descriptor) { RequireOption(descriptor, 1); std::uint32_t length = sizeof(enabled); int output = 0; + Require(sceNetGetsockopt(descriptor, 41, 27, &output, nullptr) == static_cast(0x80410116u) && *sceNetErrnoLoc() == 22); + Require(sceNetGetsockopt(-1, 41, 27, &output, &length) == static_cast(0x80410109u) && *sceNetErrnoLoc() == 9); + Require(sceNetSetsockopt(-1, 41, 27, &enabled, sizeof(enabled)) == static_cast(0x80410109u) && *sceNetErrnoLoc() == 9); Require(sceNetGetsockopt(descriptor, 41, 28, &output, &length) == static_cast(0x8041012au) && *sceNetErrnoLoc() == 42); Require(sceNetSetsockopt(descriptor, 41, 28, &enabled, sizeof(enabled)) == static_cast(0x8041012au) && *sceNetErrnoLoc() == 42); } diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index c3cb3b3a08..43277e29bf 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -822,6 +822,15 @@ static NetCtlNatInfo DiscoverNatInfo(in_addr localAddress) { return result; } +extern "C" bool NetCtlHostIpv4Address_nid_no_patch(std::uint32_t* address, std::uint32_t* netmask, std::uint32_t* mtu) { + HostIpv4Interface host; + if (!FindHostIpv4Interface(host)) return false; + *address = host.address.s_addr; + *netmask = host.netmask.s_addr; + *mtu = host.mtu; + return true; +} + static void WriteInfoWord(NetCtlInfo* info, std::uint32_t value) { std::memcpy(info->opaque, &value, sizeof(value)); } diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index 2fb63ebcc8..b272d7da30 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -263,6 +263,9 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { filter.channel_mask = custom.channel_mask; if (custom.type == 0) { filter = {}; + filter.index = index; + filter.location = 1; + filter.type = 0; } else if (custom.type == 1) { filter.frequency = custom.param.fcq.frequency; filter.q = custom.param.fcq.q; diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index 4e8f3999b7..0741982ce4 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -257,6 +257,60 @@ static void TestRackOptions() { Require(calls.cleanupVoices == (std::vector{0, 1})); } +static void TestCustomFilterResetIndex() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 1); + + Ngs2CustomSamplerRackOption option{}; + auto& common = option.custom_rack_option.rack_option; + common.size = sizeof(option); + common.max_grain_samples = 512; + common.max_voices = 1; + common.max_matrices = 1; + common.max_ports = 1; + option.custom_rack_option.num_buffers = 1; + option.custom_rack_option.num_modules = 2; + for (auto& module : option.custom_rack_option.module) { + module.module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; + } + option.max_channel_works = 1; + + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); + const auto info = Buffer(query); + uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK); + + const auto voice = Voice(rack); + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP, + Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}}); + const std::vector pcm(Grain * 4, 16384); + const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 1, 0, static_cast(pcm.size()), 0, 0}; + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, + Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block}); + Patch(voice, master); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + + Ngs2CustomVoiceFilterParam mute{}; + mute.type = 1; + mute.param.fcq.frequency = 1000.0f; + mute.param.fcq.q = 0.70710678f; + mute.param.fcq.level = 0.0f; + Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER | 0, mute); + std::vector out(Grain, -1.0f); + const Ngs2RenderBufferInfo renderInfo{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1}; + Require(sceNgs2SystemRender(system, &renderInfo, 1) == SCE_NGS2_OK); + for (const float sample : out) Require(sample == 0.0f); + + Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER | 1, Ngs2CustomVoiceFilterParam{}); + out.assign(Grain, -1.0f); + Require(sceNgs2SystemRender(system, &renderInfo, 1) == SCE_NGS2_OK); + for (const float sample : out) Require(sample == 0.0f); + + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + static void TestCustomSampler() { calls = {}; const auto system = CreateSystem(); @@ -901,6 +955,7 @@ int main() { TestUserFxChain(); TestRackOptions(); TestCustomRackChecks(); + TestCustomFilterResetIndex(); TestCustomSampler(); TestCustomFilter(); for (const auto moduleIndex : {0u, 1u, 23u}) TestCustomLegacyUserFx(moduleIndex); diff --git a/core/libs/prx/libSceNpCommon/CMakeLists.txt b/core/libs/prx/libSceNpCommon/CMakeLists.txt index 79edd3a3b4..d4cc13ef65 100644 --- a/core/libs/prx/libSceNpCommon/CMakeLists.txt +++ b/core/libs/prx/libSceNpCommon/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceNpCommon SHARED EXCLUDE_FROM_ALL PendingExports.cpp) +add_library(libSceNpCommon SHARED EXCLUDE_FROM_ALL PendingExports.cpp PendingExportsRound7.cpp) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp") target_sources(libSceNpCommon PRIVATE Export.cpp) endif() diff --git a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp new file mode 100644 index 0000000000..4ae852c424 --- /dev/null +++ b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp @@ -0,0 +1,759 @@ +#include "prx/libSceNpCommon/include/Api.hpp" +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI _ZN3sce2np10CancelableC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpUtilXmlEscape(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4CondD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _sceNpAllocatorExRealloc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9WorkQueue11GetItemByIdEi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np14JsonDocBuilderC2ERKNS0_9JsonValueE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18MemoryStreamReaderD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7RingBuf4dtorEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZThn16_N3sce2np9HttpTransD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10MemoryFileD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4Cond4ctorEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np9EventFlag6IsInitEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10CancelLockC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERK16SceNpTitleSecret(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8WorkItem10SetRunningEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient21CreateRequestForAsyncEPiiPKvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18HttpConnectionPool13InvalidateAllEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient21DeleteRequestForAsyncEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18MemoryStreamWriter5WriteEPNS0_6HandleEPKvmPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np15CancelableScopeD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8JsonFileD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9JsonArray12AddItemArrayEPPS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6ObjectdaEPv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np14CalloutContextD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9Semaphore4WaitEj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2npeqERKNS0_4TimeES3_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13RingBufMemory6ExpandEm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZThn8_N3sce2np10MemoryFile4ReadEPNS0_6HandleEPvmPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7RingBuf15CheckoutForReadEPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlag4OpenEPKc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13NpTitleSecret5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10EventQueue7EnqueueEPKvmj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12HttpTemplateD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9WorkQueueD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZThn16_N3sce2np9HttpTrans5WriteEPNS0_6HandleEPKvmPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10EventQueue4ctorEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12StreamReader4ReadEPNS0_6HandleEPNS0_9StreamCtxEPvmPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9NpTitleId5ParseEPS1_PKcm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np20BufferedStreamReaderD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc10IpmiClient7ConnectEPKvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc10IpmiClient11IsConnectedEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTypePKcPcS5_iPPhPmPiPvESA_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpUtilParseJid(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6ObjectnaEmR16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9Semaphore6CreateEiiPKc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2npleERKNS0_4TimeERK10SceRtcTick(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpUtilXmlUnescape(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpXmlParse(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9WorkQueue15GetFinishedItemENS0_14WorkItemStatusE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlag4PollEmjPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlagD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9LocalFile6RemoveEPKc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np14JsonDocBuilder12BuildBufSizeEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10MemoryFile8TruncateEl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9HttpTrans24GetResponseContentLengthEPNS0_6HandleEPbPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient13CreateRequestEPiiPKvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8SelectorD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10CancelableD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7HttpUriD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np15CancelableScopeD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10Cancelable7DestroyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZThn8_N3sce2np9LocalFileD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np10JsonString6GetStrEPcm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8NpCommIdC2ERK20SceNpCommunicationId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10Cancelable10LockCancelEPKciS3_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8WorkItem10SetPendingEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7Callout4StopEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7RingBuf14CheckinForReadEm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12StreamWriter9WriteDataEPNS0_6HandleEPKvmPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6ThreadD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2npeqERK12SceNpTitleIdRKNS0_9NpTitleIdE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlag3SetEm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9RefObjectC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np7RingBuf6IsFullEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2npneERKNS0_13NpTitleSecretERK16SceNpTitleSecret(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9LocalFileD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np16StreamReadBufferD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient11TermServiceEi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6HandleD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient17UnregisterServiceEi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9WorkQueue4ctorEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6Thread4InitEPKcimm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np4Time18ConvertToPosixTimeEPl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7Callout10IsTimedoutEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7Callout11CalloutFuncEPv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZThn8_N3sce2np6HandleD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6Handle4InitEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZThn16_N3sce2np10MemoryFileD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _sceNpAllocatorExFree(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient7ConnectEPKvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7RingBufC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10MemoryFileD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9HttpTrans21GetResponseStatusCodeEPNS0_6HandleEPi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12StreamReader8SkipDataEPNS0_6HandleElPl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6ObjectdlEPvR16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9HttpTransD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12HttpTemplateC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6ObjectdaEPvR14SceNpAllocator(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlag6CreateEPKcj(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient15RegisterServiceEPKNS1_17ServiceClientInfoE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8WorkItemD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9SemaphoreD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18MemoryStreamReaderC1EPKvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlagD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9RefObjectD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _sceNpAllocatorMalloc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18MemoryStreamWriterD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2npneERK10SceRtcTickRKNS0_4TimeE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13RingBufMemory6IsInitEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7RingBufD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6ObjectnwEmR16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _sceNpHeapMalloc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12HttpTemplate7DestroyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np15CancelableScope5BeginEPNS0_6HandleEPKciS5_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np10EventQueue6IsInitEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18MemoryStreamWriterD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8JsonBool5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9LocalFile8TruncateEl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np10JsonNumber6GetNumEPcm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13RingBufMemory7DestroyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9RefObject7ReleaseEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9LocalFileC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np7RingBufD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4PathD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPvmPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4CondD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9EventFlag7DestroyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9HttpTransC1EP16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np6Thread4dtorEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4Time8AddHoursEl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9NpTitleIdC2ERK12SceNpTitleId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np12WorkerThreadD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np13RingBufMemoryD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np9LocalFile5CloseEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np10JsonString9GetLengthEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np10EventQueueD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np8JsonNull5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np18MemoryStreamWriterD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np4Time12GetUserClockEPS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient12AbortRequestEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2np5Mutex6IsInitEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceNpCommon/Runtime.cpp b/core/libs/prx/libSceNpCommon/Runtime.cpp index 510328687f..25ddc81535 100644 --- a/core/libs/prx/libSceNpCommon/Runtime.cpp +++ b/core/libs/prx/libSceNpCommon/Runtime.cpp @@ -1,4 +1,3 @@ -#include "prx/libSceSaveData/SaveDataCore.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libSceNpCommon/include/Api.hpp" diff --git a/core/libs/prx/libSceNpCommon/Strings.cpp b/core/libs/prx/libSceNpCommon/Strings.cpp index 08125a4c0f..bda070c212 100644 --- a/core/libs/prx/libSceNpCommon/Strings.cpp +++ b/core/libs/prx/libSceNpCommon/Strings.cpp @@ -437,7 +437,8 @@ int APS5_VABI sceNpStrncpyToBuf(char* dst, std::size_t size, const char* src, st int APS5_VABI sceNpUtilJsonEscape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength) { if (dst == nullptr || dstSize == 0 || src == nullptr || srcLength == 0) return NP_UTIL_ERROR_INVALID_ARGUMENT; if (dstSize < JSON_ESCAPE_MARGIN) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; - std::size_t written = 0; + std::size_t written = ::strnlen(dst, dstSize); + if (written >= dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; for (std::size_t i = 0; i < srcLength; ++i) { if (written > dstSize - JSON_ESCAPE_MARGIN) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; const auto c = static_cast(src[i]); @@ -522,7 +523,7 @@ int APS5_VABI sceNpUtilJsonUnescape(char* dst, std::size_t dstSize, const char* emit(encoded, length, JSON_SURROGATE_PAIR_LENGTH); } ++count; - if (count > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + if (dst != nullptr && count > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; if (dstLength != nullptr) *dstLength = count; if (out != nullptr) *out = '\0'; return status; @@ -556,7 +557,7 @@ int APS5_VABI sceNpUtilJsonGetOneChar(char* dst, std::size_t dstSize, const char } else { used = JSON_SURROGATE_PAIR_LENGTH; std::uint32_t low = 0; - if (srcLength < JSON_SURROGATE_PAIR_LENGTH || ParseUtf16Unit(src + 8, &low) < 0 + if (srcLength < JSON_SURROGATE_PAIR_LENGTH || src[6] != '\\' || src[7] != 'u' || ParseUtf16Unit(src + 8, &low) < 0 || (low & SURROGATE_MASK) != LOW_SURROGATE) { status = NP_UTIL_ERROR_BUFFER_TOO_SMALL; } else { diff --git a/core/libs/prx/libSceNpCommon/include/Api.hpp b/core/libs/prx/libSceNpCommon/include/Api.hpp index d4c373e182..1cae74d9d8 100644 --- a/core/libs/prx/libSceNpCommon/include/Api.hpp +++ b/core/libs/prx/libSceNpCommon/include/Api.hpp @@ -217,6 +217,157 @@ int APS5_VABI sceNpUtilJsonGetOneChar(char* dst, std::size_t dstSize, const char int APS5_VABI sceNpUtilGetNthChar(std::size_t index, const char* text, const char** character); int APS5_VABI sceNpUtilHttpUrlEncode(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* dstLength); +int APS5_VABI _ZN3sce2np10CancelableC2Ev(void); +int APS5_VABI sceNpUtilXmlEscape(void); +int APS5_VABI _ZN3sce2np4CondD0Ev(void); +int APS5_VABI _sceNpAllocatorExRealloc(void); +int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void); +int APS5_VABI _ZN3sce2np9WorkQueue11GetItemByIdEi(void); +int APS5_VABI _ZN3sce2np14JsonDocBuilderC2ERKNS0_9JsonValueE(void); +int APS5_VABI _ZN3sce2np18MemoryStreamReaderD0Ev(void); +int APS5_VABI _ZN3sce2np7RingBuf4dtorEv(void); +int APS5_VABI _ZThn16_N3sce2np9HttpTransD0Ev(void); +int APS5_VABI _ZN3sce2np10MemoryFileD0Ev(void); +int APS5_VABI _ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np4Cond4ctorEv(void); +int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void); +int APS5_VABI _ZNK3sce2np9EventFlag6IsInitEv(void); +int APS5_VABI _ZN3sce2np10CancelLockC2Ev(void); +int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERK16SceNpTitleSecret(void); +int APS5_VABI _ZN3sce2np8WorkItem10SetRunningEv(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient21CreateRequestForAsyncEPiiPKvm(void); +int APS5_VABI _ZN3sce2np18HttpConnectionPool13InvalidateAllEv(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient21DeleteRequestForAsyncEii(void); +int APS5_VABI _ZN3sce2np18MemoryStreamWriter5WriteEPNS0_6HandleEPKvmPm(void); +int APS5_VABI _ZN3sce2np15CancelableScopeD1Ev(void); +int APS5_VABI _ZN3sce2np8JsonFileD2Ev(void); +int APS5_VABI _ZN3sce2np9JsonArray12AddItemArrayEPPS1_(void); +int APS5_VABI _ZN3sce2np6ObjectdaEPv(void); +int APS5_VABI _ZN3sce2np14CalloutContextD0Ev(void); +int APS5_VABI _ZN3sce2np9Semaphore4WaitEj(void); +int APS5_VABI _ZN3sce2npeqERKNS0_4TimeES3_(void); +int APS5_VABI _ZN3sce2np13RingBufMemory6ExpandEm(void); +int APS5_VABI _ZThn8_N3sce2np10MemoryFile4ReadEPNS0_6HandleEPvmPm(void); +int APS5_VABI _ZN3sce2np7RingBuf15CheckoutForReadEPm(void); +int APS5_VABI _ZN3sce2np9EventFlag4OpenEPKc(void); +int APS5_VABI _ZN3sce2np13NpTitleSecret5ClearEv(void); +int APS5_VABI _ZN3sce2np10EventQueue7EnqueueEPKvmj(void); +int APS5_VABI _ZN3sce2np12HttpTemplateD1Ev(void); +int APS5_VABI _ZN3sce2np9WorkQueueD2Ev(void); +int APS5_VABI _ZThn16_N3sce2np9HttpTrans5WriteEPNS0_6HandleEPKvmPm(void); +int APS5_VABI _ZN3sce2np10EventQueue4ctorEv(void); +int APS5_VABI _ZN3sce2np12StreamReader4ReadEPNS0_6HandleEPNS0_9StreamCtxEPvmPm(void); +int APS5_VABI _ZN3sce2np9NpTitleId5ParseEPS1_PKcm(void); +int APS5_VABI _ZN3sce2np20BufferedStreamReaderD1Ev(void); +int APS5_VABI _ZN3sce2np3ipc10IpmiClient7ConnectEPKvm(void); +int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void); +int APS5_VABI _ZN3sce2np3ipc10IpmiClient11IsConnectedEv(void); +int APS5_VABI _ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTypePKcPcS5_iPPhPmPiPvESA_(void); +int APS5_VABI sceNpUtilParseJid(void); +int APS5_VABI _ZN3sce2np6ObjectnaEmR16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np9Semaphore6CreateEiiPKc(void); +int APS5_VABI _ZN3sce2npleERKNS0_4TimeERK10SceRtcTick(void); +int APS5_VABI sceNpUtilXmlUnescape(void); +int APS5_VABI sceNpXmlParse(void); +int APS5_VABI _ZN3sce2np9WorkQueue15GetFinishedItemENS0_14WorkItemStatusE(void); +int APS5_VABI _ZN3sce2np9EventFlag4PollEmjPm(void); +int APS5_VABI _ZN3sce2np9EventFlagD0Ev(void); +int APS5_VABI _ZN3sce2np9LocalFile6RemoveEPKc(void); +int APS5_VABI _ZN3sce2np14JsonDocBuilder12BuildBufSizeEv(void); +int APS5_VABI _ZN3sce2np10MemoryFile8TruncateEl(void); +int APS5_VABI _ZN3sce2np9HttpTrans24GetResponseContentLengthEPNS0_6HandleEPbPm(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient13CreateRequestEPiiPKvm(void); +int APS5_VABI _ZN3sce2np8SelectorD2Ev(void); +int APS5_VABI _ZN3sce2np10CancelableD1Ev(void); +int APS5_VABI _ZN3sce2np7HttpUriD1Ev(void); +int APS5_VABI _ZN3sce2np15CancelableScopeD0Ev(void); +int APS5_VABI _ZN3sce2np10Cancelable7DestroyEv(void); +int APS5_VABI _ZThn8_N3sce2np9LocalFileD1Ev(void); +int APS5_VABI _ZNK3sce2np10JsonString6GetStrEPcm(void); +int APS5_VABI _ZN3sce2np8NpCommIdC2ERK20SceNpCommunicationId(void); +int APS5_VABI _ZN3sce2np10Cancelable10LockCancelEPKciS3_(void); +int APS5_VABI _ZN3sce2np8WorkItem10SetPendingEv(void); +int APS5_VABI _ZN3sce2np7Callout4StopEv(void); +int APS5_VABI _ZN3sce2np7RingBuf14CheckinForReadEm(void); +int APS5_VABI _ZN3sce2np12StreamWriter9WriteDataEPNS0_6HandleEPKvmPm(void); +int APS5_VABI _ZN3sce2np6ThreadD1Ev(void); +int APS5_VABI _ZN3sce2npeqERK12SceNpTitleIdRKNS0_9NpTitleIdE(void); +int APS5_VABI _ZN3sce2np9EventFlag3SetEm(void); +int APS5_VABI _ZN3sce2np9RefObjectC1Ev(void); +int APS5_VABI _ZNK3sce2np7RingBuf6IsFullEv(void); +int APS5_VABI _ZN3sce2npneERKNS0_13NpTitleSecretERK16SceNpTitleSecret(void); +int APS5_VABI _ZN3sce2np9LocalFileD0Ev(void); +int APS5_VABI _ZN3sce2np16StreamReadBufferD2Ev(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient11TermServiceEi(void); +int APS5_VABI _ZN3sce2np6HandleD1Ev(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient17UnregisterServiceEi(void); +int APS5_VABI _ZN3sce2np9WorkQueue4ctorEv(void); +int APS5_VABI _ZN3sce2np6Thread4InitEPKcimm(void); +int APS5_VABI _ZNK3sce2np4Time18ConvertToPosixTimeEPl(void); +int APS5_VABI _ZN3sce2np7Callout10IsTimedoutEv(void); +int APS5_VABI _ZN3sce2np7Callout11CalloutFuncEPv(void); +int APS5_VABI _ZThn8_N3sce2np6HandleD1Ev(void); +int APS5_VABI _ZN3sce2np6Handle4InitEv(void); +int APS5_VABI _ZThn16_N3sce2np10MemoryFileD1Ev(void); +int APS5_VABI _sceNpAllocatorExFree(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient7ConnectEPKvm(void); +int APS5_VABI _ZN3sce2np7RingBufC1Ev(void); +int APS5_VABI _ZN3sce2np10MemoryFileD1Ev(void); +int APS5_VABI _ZN3sce2np9HttpTrans21GetResponseStatusCodeEPNS0_6HandleEPi(void); +int APS5_VABI _ZN3sce2np12StreamReader8SkipDataEPNS0_6HandleElPl(void); +int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(void); +int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void); +int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void); +int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void); +int APS5_VABI _ZN3sce2np6ObjectdlEPvR16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np9HttpTransD2Ev(void); +int APS5_VABI _ZN3sce2np12HttpTemplateC2Ev(void); +int APS5_VABI _ZN3sce2np6ObjectdaEPvR14SceNpAllocator(void); +int APS5_VABI _ZN3sce2np9EventFlag6CreateEPKcj(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient15RegisterServiceEPKNS1_17ServiceClientInfoE(void); +int APS5_VABI _ZN3sce2np8WorkItemD0Ev(void); +int APS5_VABI _ZN3sce2np9SemaphoreD2Ev(void); +int APS5_VABI _ZN3sce2np18MemoryStreamReaderC1EPKvm(void); +int APS5_VABI _ZN3sce2np9EventFlagD2Ev(void); +int APS5_VABI _ZN3sce2np9RefObjectD1Ev(void); +int APS5_VABI _sceNpAllocatorMalloc(void); +int APS5_VABI _ZN3sce2np18MemoryStreamWriterD0Ev(void); +int APS5_VABI _ZN3sce2npneERK10SceRtcTickRKNS0_4TimeE(void); +int APS5_VABI _ZN3sce2np13RingBufMemory6IsInitEv(void); +int APS5_VABI _ZN3sce2np7RingBufD0Ev(void); +int APS5_VABI _ZN3sce2np6ObjectnwEmR16SceNpAllocatorEx(void); +int APS5_VABI _sceNpHeapMalloc(void); +int APS5_VABI _ZN3sce2np12HttpTemplate7DestroyEv(void); +int APS5_VABI _ZN3sce2np15CancelableScope5BeginEPNS0_6HandleEPKciS5_(void); +int APS5_VABI _ZNK3sce2np10EventQueue6IsInitEv(void); +int APS5_VABI _ZN3sce2np18MemoryStreamWriterD1Ev(void); +int APS5_VABI _ZN3sce2np8JsonBool5ClearEv(void); +int APS5_VABI _ZN3sce2np9LocalFile8TruncateEl(void); +int APS5_VABI _ZNK3sce2np10JsonNumber6GetNumEPcm(void); +int APS5_VABI _ZN3sce2np13RingBufMemory7DestroyEv(void); +int APS5_VABI _ZN3sce2np9RefObject7ReleaseEv(void); +int APS5_VABI _ZN3sce2np9LocalFileC1Ev(void); +int APS5_VABI _ZN3sce2np7RingBufD2Ev(void); +int APS5_VABI _ZN3sce2np4PathD2Ev(void); +int APS5_VABI _ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPvmPm(void); +int APS5_VABI _ZN3sce2np4CondD1Ev(void); +int APS5_VABI _ZN3sce2np9EventFlag7DestroyEv(void); +int APS5_VABI _ZN3sce2np9HttpTransC1EP16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np6Thread4dtorEv(void); +int APS5_VABI _ZN3sce2np4Time8AddHoursEl(void); +int APS5_VABI _ZN3sce2np9NpTitleIdC2ERK12SceNpTitleId(void); +int APS5_VABI _ZN3sce2np12WorkerThreadD0Ev(void); +int APS5_VABI _ZN3sce2np13RingBufMemoryD0Ev(void); +int APS5_VABI _ZN3sce2np9LocalFile5CloseEv(void); +int APS5_VABI _ZNK3sce2np10JsonString9GetLengthEv(void); +int APS5_VABI _ZN3sce2np10EventQueueD0Ev(void); +int APS5_VABI _ZN3sce2np8JsonNull5ClearEv(void); +int APS5_VABI _ZN3sce2np18MemoryStreamWriterD2Ev(void); +int APS5_VABI _ZN3sce2np4Time12GetUserClockEPS1_(void); +int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient12AbortRequestEii(void); +int APS5_VABI _ZNK3sce2np5Mutex6IsInitEv(void); + [[noreturn]] void APS5_VABI sceNpPanic(void); } diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index c8e9a9ca52..340eb7ffed 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -208,6 +208,7 @@ static constexpr int ParameterNotInitialized = static_cast(0x80553502); static constexpr int ParameterInvalidArgument = static_cast(0x80553503); static constexpr int ParameterAlreadyInitialized = static_cast(0x8055350f); static constexpr int ParameterTerminatedUninitialized = static_cast(0x80553510); +static constexpr int ParameterBodyMissing = static_cast(0x80553512); struct ParameterObject { void* base; @@ -242,6 +243,13 @@ static int TerminateParameter(void* self) { return 0; } +static int InitializeParameterWithBody(void* self, const std::uint64_t* context, const bool textsPresent, const IntrusivePtr* body) { + const int result = InitializeParameter(self, context, textsPresent); + if (result != 0 || body->object != nullptr) return result; + static_cast(self)->implementation = nullptr; + return ParameterBodyMissing; +} + int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context, const char* text, int value) { (void)value; return InitializeParameter(self, context, text != nullptr); @@ -967,7 +975,8 @@ void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sess return nullptr; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev(void* self) { + (void)self; return 0; } @@ -977,8 +986,8 @@ const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V110FromMember11getO return nullptr; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc(void* self, const char* filter) { + return SetParameterText(self, filter); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEC1Ev(IntrusivePtr* self) { @@ -997,7 +1006,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev(void* self) { + (void)self; return 0; } @@ -1126,12 +1136,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseC1Ev(void* self) { return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv(void* self) { + return TerminateParameter(self); } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE(void* self, const std::uint64_t* context, const IntrusivePtr* body) { + return InitializeParameterWithBody(self, context, true, body); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEEptEv(const IntrusivePtr* self) { @@ -1151,7 +1161,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sessi ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev(void* self) { + ConstructParameter(self); return 0; } @@ -1197,7 +1208,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev(void* self) { + (void)self; return 0; } @@ -1233,7 +1245,8 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2 return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev(void* self) { + ConstructParameter(self); return 0; } @@ -1260,7 +1273,8 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_1 return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev(void* self) { + (void)self; return 0; } @@ -1292,12 +1306,13 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev(void* self) { + ConstructParameter(self); return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body) { + return InitializeParameterWithBody(self, context, sessionId != nullptr, body); } bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody16invitationsIsSetEv(const void* self) { @@ -1310,8 +1325,8 @@ void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sess return HostVectorElement(self, index, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context, const char* sessionId, const char* accountId) { + return InitializeParameter(self, context, sessionId != nullptr && accountId != nullptr); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEED1Ev(IntrusivePtr* self) { @@ -1357,7 +1372,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi20create return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev(void* self) { + ConstructParameter(self); return 0; } @@ -1412,8 +1428,8 @@ bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14Se return false; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* accountId) { + return InitializeParameter(self, context, accountId != nullptr); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self) { @@ -2037,8 +2053,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28Parame return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(void* self) { + return TerminateParameter(self); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev() { @@ -2067,8 +2083,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBloc return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv(void* self) { + return TerminateParameter(self); } int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData1IsSetEv() { diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index b827506807..9730b22af1 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -179,14 +179,14 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2 int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession16setMaxSpectatorsERKi(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestPlayerSessionMemberPlayerFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_32RequestCreatePlayerSessionPlayerEEEEEPNS9_INS3_32RequestPlayerSessionMemberPlayerEEE(); void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE3endEv(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev(void* self); const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V110FromMember11getOnlineIdEv(const void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc(void* self, const char* filter); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEC1Ev(IntrusivePtr* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEptEv(const IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V122LocalizedStringFactory6createEPNS1_6Common10LibContextEPKcNS_4Json6ObjectEPNS5_12IntrusivePtrINS3_15LocalizedStringEEE(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self); @@ -212,13 +212,13 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25joinPl int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(CommonString* self, const char* text); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseC1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE(void* self, const std::uint64_t* context, const IntrusivePtr* body); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEEptEv(const IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEED1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev(void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEC1Ev(IntrusivePtr* self); const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead12getSessionIdEv(const void* self); @@ -228,7 +228,7 @@ void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsRespo int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi18leavePlayerSessionEiRKNS4_29ParameterToLeavePlayerSessionERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi27getPlayerSessionInvitationsEiRKNS4_38ParameterToGetPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_54GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEED1Ev(IntrusivePtr* self); @@ -237,24 +237,24 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEC1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEED1Ev(IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEptEv(const IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEppEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession19setJoinableUserTypeERKNS3_16JoinableUserTypeE(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev(void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead14sessionIdIsSetEv(const void* self); bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead17invitationIdIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody16invitationsIsSetEv(const void* self); void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEEEixEm(void* self, std::uint64_t index); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_(void* self, const std::uint64_t* context, const char* sessionId, const char* accountId); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEdeEv(const void* self); @@ -264,7 +264,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6String void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133RequestPlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi20createPlayerSessionsEiRKNS4_31ParameterToCreatePlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30PostPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev(void* self); bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead9fromIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V126RequestPlayerSessionPlayer15setPushContextsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V127RequestPlayerSessionFactory6createEPNS1_6Common10LibContextEiNS5_12IntrusivePtrINS3_32RequestPlayerSessionMemberPlayerEEERKNS5_6VectorINS5_6StringEEENS8_INS3_15LocalizedStringEEEPNS8_INS3_20RequestPlayerSessionEEE(); @@ -276,7 +276,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEC1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(IntrusivePtr* self, const void* element); bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEneERKS9_(const void* self, const void* other); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* accountId); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr* self); @@ -408,14 +408,14 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPla int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev(); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersC1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv(void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData1IsSetEv(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38addPlayerSessionJoinableSpecifiedUsersEiRKNS4_49ParameterToAddPlayerSessionJoinableSpecifiedUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_61PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv(); diff --git a/core/libs/prx/libSceNpMatching2/Export.cpp b/core/libs/prx/libSceNpMatching2/Export.cpp index 0884985aa7..c6a28b47b5 100644 --- a/core/libs/prx/libSceNpMatching2/Export.cpp +++ b/core/libs/prx/libSceNpMatching2/Export.cpp @@ -13,6 +13,7 @@ static constexpr int SCE_NP_MATCHING2_ERROR_CONTEXT_MAX = static_cast(0x805 static constexpr int SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED = static_cast(0x80550C08); static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID = static_cast(0x80550C0B); static constexpr int SCE_NET_ERROR_RESOLVER_ETIMEDOUT = static_cast(0x804101E2); +static constexpr std::int32_t SCE_NP_SIGNALING_CONN_STATUS_INACTIVE = 0; static constexpr int MAX_CONTEXTS = 10; static constexpr std::uint64_t CREATE_CONTEXT_PARAMETER_SIZE = 0x28; @@ -215,9 +216,10 @@ int APS5_VABI sceNpMatching2Terminate(void) { return 0; } -int APS5_VABI sceNpMatching2AbortContextStart(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2AbortContextStart(std::uint16_t ctxId) { + (void)ctxId; + std::lock_guard lock(matching2Mutex); + return matching2Initialized ? 0 : SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; } int APS5_VABI sceNpMatching2CreateContextA(void) { @@ -380,8 +382,16 @@ int APS5_VABI sceNpMatching2SignalingGetConnectionInfoA(void) { return 0; } -int APS5_VABI sceNpMatching2SignalingGetConnectionStatus(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpMatching2SignalingGetConnectionStatus(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t* connStatus, void* peerAddr, std::uint16_t* peerPort) { + (void)roomId; + (void)memberId; + (void)peerAddr; + (void)peerPort; + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!connStatus) NotImplemented_nid_no_patch(__func__); + if (const int error = CheckContext(ctxId)) return error; + *connStatus = SCE_NP_SIGNALING_CONN_STATUS_INACTIVE; return 0; } diff --git a/core/libs/prx/libSceNpMatching2/include/Api.hpp b/core/libs/prx/libSceNpMatching2/include/Api.hpp index 74c4a2b2d7..e83dba6539 100644 --- a/core/libs/prx/libSceNpMatching2/include/Api.hpp +++ b/core/libs/prx/libSceNpMatching2/include/Api.hpp @@ -52,7 +52,7 @@ int APS5_VABI sceNpMatching2SetUserInfo(); int APS5_VABI sceNpMatching2SignalingGetConnectionInfo(); int APS5_VABI sceNpMatching2Terminate(void); -int APS5_VABI sceNpMatching2AbortContextStart(); +int APS5_VABI sceNpMatching2AbortContextStart(std::uint16_t ctxId); int APS5_VABI sceNpMatching2CreateContextA(); int APS5_VABI sceNpMatching2CreateContextInternal(); int APS5_VABI sceNpMatching2CreateJoinRoomA(); @@ -85,7 +85,7 @@ int APS5_VABI sceNpMatching2SignalingCancelPeerNetInfo(); int APS5_VABI sceNpMatching2SignalingEnableManualUdpMode(); int APS5_VABI sceNpMatching2SignalingEstablishConnection(); int APS5_VABI sceNpMatching2SignalingGetConnectionInfoA(); -int APS5_VABI sceNpMatching2SignalingGetConnectionStatus(); +int APS5_VABI sceNpMatching2SignalingGetConnectionStatus(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t* connStatus, void* peerAddr, std::uint16_t* peerPort); int APS5_VABI sceNpMatching2SignalingGetLocalNetInfo(); int APS5_VABI sceNpMatching2SignalingGetPeerNetInfo(); int APS5_VABI sceNpMatching2SignalingGetPeerNetInfoResult(); diff --git a/core/libs/prx/libSceNpTrophy/Export.cpp b/core/libs/prx/libSceNpTrophy/Export.cpp index 2571c199f6..c5cdd826a1 100644 --- a/core/libs/prx/libSceNpTrophy/Export.cpp +++ b/core/libs/prx/libSceNpTrophy/Export.cpp @@ -1,3 +1,4 @@ +#include #include "prx/libSceNpTrophy/include/Api.hpp" #include #include @@ -107,12 +108,19 @@ void LoadState(Context& context) { } } -void SaveState(const Context& context) { +void SaveState(const Context& context, const std::set& unlocked) { std::filesystem::create_directories(context.state.parent_path()); const auto temporary = context.state.string() + ".tmp"; { std::ofstream file(temporary, std::ios::trunc); - for (const auto id : context.unlocked) file << id << '\n'; + for (const auto id : unlocked) file << id << '\n'; + file.flush(); + if (!file) { + file.close(); + std::error_code removeError; + std::filesystem::remove(temporary, removeError); + throw std::runtime_error("sceNpTrophy: cannot write the unlock state " + temporary); + } } std::filesystem::rename(temporary, context.state); } @@ -251,8 +259,10 @@ int APS5_VABI sceNpTrophyUnlockTrophy(int contextId, int handle, int trophyId, i if (!context->registered) return NP_TROPHY_ERROR_NOT_REGISTERED; if (trophyId < 0 || trophyId >= context->trophies) return NP_TROPHY_ERROR_INVALID_TROPHY_ID; if (context->unlocked.contains(trophyId)) return NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED; - context->unlocked.insert(trophyId); - SaveState(*context); + auto updated = context->unlocked; + updated.insert(trophyId); + SaveState(*context, updated); + context->unlocked = std::move(updated); *platinumId = NP_TROPHY_INVALID_ID; return 0; } diff --git a/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp b/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp index 58ec7f5e33..547751437a 100644 --- a/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp +++ b/core/libs/prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCENPTROPHY2_NPTROPHY2CONSTANTS_HPP #define CORE_LIBS_PRX_LIBSCENPTROPHY2_NPTROPHY2CONSTANTS_HPP +#include #include static constexpr int SCE_NP_TROPHY2_OK = 0; diff --git a/core/libs/prx/libSceNpTus/Export.cpp b/core/libs/prx/libSceNpTus/Export.cpp index 2f632e6d18..fcd25e7083 100644 --- a/core/libs/prx/libSceNpTus/Export.cpp +++ b/core/libs/prx/libSceNpTus/Export.cpp @@ -1001,7 +1001,7 @@ int APS5_VABI sceNpTssGetData() { return 0; } -int APS5_VABI sceNpTusCreateNpTitleCtx() { +int APS5_VABI sceNpTusCreateNpTitleCtx(std::uint32_t serviceLabel, void* npId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNpTus/include/Api.hpp b/core/libs/prx/libSceNpTus/include/Api.hpp index eacd0fe74a..79f4b14588 100644 --- a/core/libs/prx/libSceNpTus/include/Api.hpp +++ b/core/libs/prx/libSceNpTus/include/Api.hpp @@ -137,7 +137,7 @@ int APS5_VABI sceNpTusTryAndSetVariableForCrossSave(int reqId, std::uint64_t tar int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int32_t opeType, std::int64_t variable, const std::int64_t* compareValue, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* resultVariable, std::uint64_t resultVariableSize, void* option); int APS5_VABI sceNpTusTryAndSetVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int32_t opeType, std::int64_t variable, const std::int64_t* compareValue, const void* isLastChangedAuthor, const void* isLastChangedDate, void* resultVariable, std::uint64_t resultVariableSize, void* option); int APS5_VABI sceNpTssGetData(); -int APS5_VABI sceNpTusCreateNpTitleCtx(); +int APS5_VABI sceNpTusCreateNpTitleCtx(std::uint32_t serviceLabel, void* npId); int APS5_VABI sceNpTusGetData(int reqId, const void* targetNpId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option); int APS5_VABI sceNpTusGetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option); int APS5_VABI sceNpTusGetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); diff --git a/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt b/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt index 9d02b23500..b74a51652b 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt +++ b/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt @@ -65,4 +65,13 @@ if(BUILD_TESTING) set_tests_properties(guest_np_trophy_stats PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_np_uds_queries_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpUniversalDataSystemQueries.cpp) + target_include_directories(guest_np_uds_queries_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_uds_queries_tests PRIVATE libSceNpUniversalDataSystem libc) + configure_windows_unwind(guest_np_uds_queries_tests) + add_test(NAME guest_np_uds_queries COMMAND guest_np_uds_queries_tests) + if(WIN32) + set_tests_properties(guest_np_uds_queries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp b/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp index 28b17e212b..d419958712 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpUniversalDataSystem/include/InventoryExports.hpp" #include "prx/libSceNpUniversalDataSystem/include/Api.hpp" #include #include @@ -6,6 +7,43 @@ extern "C" { +int APS5_VABI sceNpUniversalDataSystemIntCloseStream() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntDeleteUserData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntGetNpCommIdByNpTitleId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntGetNpTitleIdListByNpCommId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntGetObjectState() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntOpenObjectsDefinitionConfStream() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntOpenObjectsDefinitionConfStreamByObjectId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntOpenStreamByUri() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpUniversalDataSystemIntReadStream() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceNpUniversalDataSystemIntAbortHandle(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceNpUniversalDataSystem/include/InventoryExports.hpp b/core/libs/prx/libSceNpUniversalDataSystem/include/InventoryExports.hpp new file mode 100644 index 0000000000..4c0a85e348 --- /dev/null +++ b/core/libs/prx/libSceNpUniversalDataSystem/include/InventoryExports.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpUniversalDataSystemIntCloseStream(); +int APS5_VABI sceNpUniversalDataSystemIntDeleteUserData(); +int APS5_VABI sceNpUniversalDataSystemIntGetNpCommIdByNpTitleId(); +int APS5_VABI sceNpUniversalDataSystemIntGetNpTitleIdListByNpCommId(); +int APS5_VABI sceNpUniversalDataSystemIntGetObjectState(); +int APS5_VABI sceNpUniversalDataSystemIntOpenObjectsDefinitionConfStream(); +int APS5_VABI sceNpUniversalDataSystemIntOpenObjectsDefinitionConfStreamByObjectId(); +int APS5_VABI sceNpUniversalDataSystemIntOpenStreamByUri(); +int APS5_VABI sceNpUniversalDataSystemIntReadStream(); + +} diff --git a/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp b/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp index e72fd1c696..1a1f547f55 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp +++ b/core/libs/prx/libSceNpUniversalDataSystem/libSceNpUniversalDataSystem.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceNpUniversalDataSystem/include/Api.hpp" +#include "prx/libSceNpUniversalDataSystem/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceNpUtility/Export.cpp b/core/libs/prx/libSceNpUtility/Export.cpp index 364f0db20c..92061623e1 100644 --- a/core/libs/prx/libSceNpUtility/Export.cpp +++ b/core/libs/prx/libSceNpUtility/Export.cpp @@ -297,7 +297,7 @@ int APS5_VABI sceNpAppLaunchLinkIntInitialize() { int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo() { return SCE_NP_ERROR_INVALID_ARGUMENT; } -int APS5_VABI sceNpLookupCreateTitleCtxA() { +int APS5_VABI sceNpLookupCreateTitleCtxA(int userId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -457,7 +457,7 @@ int APS5_VABI sceNpUtilityTerm() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpWordFilterCreateTitleCtxA() { +int APS5_VABI sceNpWordFilterCreateTitleCtxA(int userId) { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNpUtility/include/Api.hpp b/core/libs/prx/libSceNpUtility/include/Api.hpp index 0fe054556f..469ecd9005 100644 --- a/core/libs/prx/libSceNpUtility/include/Api.hpp +++ b/core/libs/prx/libSceNpUtility/include/Api.hpp @@ -60,7 +60,7 @@ int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdList(); int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdNum(); int APS5_VABI sceNpAppLaunchLinkIntInitialize(); int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo(); -int APS5_VABI sceNpLookupCreateTitleCtxA(); +int APS5_VABI sceNpLookupCreateTitleCtxA(int userId); int APS5_VABI sceNpLookupNetAbortRequest(); int APS5_VABI sceNpLookupNetCensorComment(); int APS5_VABI sceNpLookupNetConvertJidToNpId(); @@ -100,6 +100,6 @@ int APS5_VABI sceNpTitleMetadataIntGetInfo(); int APS5_VABI sceNpTitleMetadataIntGetNpTitleId(); int APS5_VABI sceNpUtilityInit(); int APS5_VABI sceNpUtilityTerm(); -int APS5_VABI sceNpWordFilterCreateTitleCtxA(); +int APS5_VABI sceNpWordFilterCreateTitleCtxA(int userId); } diff --git a/core/libs/prx/libScePad/include/InputCommandFile.hpp b/core/libs/prx/libScePad/include/InputCommandFile.hpp index e372a1f3f7..172735fd62 100644 --- a/core/libs/prx/libScePad/include/InputCommandFile.hpp +++ b/core/libs/prx/libScePad/include/InputCommandFile.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,11 @@ public: std::array sticks{}; bool touchLeft = false; bool touchRight = false; + bool hasTouch = false; + std::uint16_t touchX = 0; + std::uint16_t touchY = 0; + std::uint8_t touchId = 0; + bool touchActive = false; bool operator==(const Input&) const = default; }; @@ -107,21 +113,37 @@ private: } fields.clear(); fields.str(text); - std::string tokens[8]; + std::string tokens[9]; int count = 0; - while (count < 8 && fields >> tokens[count]) ++count; + while (count < 9 && fields >> tokens[count]) ++count; + Input input; + if (count == 4 || count == 8) { + std::string_view touch(tokens[--count]); + if (!touch.starts_with("touch(") || !touch.ends_with(')')) return {}; + touch.remove_prefix(6); + touch.remove_suffix(1); + std::uint64_t coordinates[4]{}; + for (int i = 0; i < 4; ++i) { + const auto comma = touch.find(','); + if ((i < 3) != (comma != std::string_view::npos)) return {}; + if (!parseUnsigned(touch.substr(0, comma), coordinates[i])) return {}; + if (i < 3) touch.remove_prefix(comma + 1); + } + if (coordinates[0] >= 1920 || coordinates[1] >= 943 || coordinates[2] > 127 || coordinates[3] > 1) return {}; + input.hasTouch = true; + input.touchX = static_cast(coordinates[0]); + input.touchY = static_cast(coordinates[1]); + input.touchId = static_cast(coordinates[2]); + input.touchActive = coordinates[3] != 0; + } if (count != 3 && count != 7) return {}; std::uint64_t values[7]{}; for (int i = 0; i < count; ++i) { - const char* first = tokens[i].data() + (tokens[i].front() == '+' ? 1 : 0); - const char* last = tokens[i].data() + tokens[i].size(); - const auto parsed = std::from_chars(first, last, values[i]); - if (first == last || parsed.ec != std::errc{} || parsed.ptr != last) return {}; + if (!parseUnsigned(tokens[i], values[i])) return {}; } if (values[0] == 0 || (values[1] & ~(0x10fffeull | TouchLeftBit | TouchRightBit)) != 0 || values[2] == 0 || values[2] > 5000) return {}; Command command; command.sequence = values[0]; - Input input; input.buttons = static_cast(values[1] & 0x10fffeull); input.touchLeft = (values[1] & TouchLeftBit) != 0; input.touchRight = (values[1] & TouchRightBit) != 0; @@ -134,6 +156,14 @@ private: return command; } + static bool parseUnsigned(std::string_view token, std::uint64_t& value) { + if (token.empty()) return false; + const char* first = token.data() + (token.front() == '+' ? 1 : 0); + const char* last = token.data() + token.size(); + const auto parsed = std::from_chars(first, last, value); + return first != last && parsed.ec == std::errc{} && parsed.ptr == last; + } + static constexpr std::uint64_t TouchLeftBit = 0x200000; static constexpr std::uint64_t TouchRightBit = 0x400000; diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index 0c685c42bc..2b57276eed 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -211,11 +211,19 @@ PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t chang data.timestamp = changeTime; std::uint8_t touchNum = 0; + const bool injectedContact = !intercepted && injected.hasTouch; + if (injectedContact && injected.touchActive) { + touchNum = 1; + data.touch_data_touch0_x = injected.touchX; + data.touch_data_touch0_y = injected.touchY; + data.touch_data_touch0_id = injected.touchId; + } for (int i = 0; i < 2; ++i) { const PadTouchPoint& tp = state.touch[i]; if (!tp.active) { slot.prevTouchActive[i] = false; continue; } if (!slot.prevTouchActive[i]) slot.touchIds[i] = static_cast(slot.nextTouchId++ & 0x7F); slot.prevTouchActive[i] = true; + if (injectedContact) continue; if (touchNum == 0) { data.touch_data_touch0_x = tp.x; data.touch_data_touch0_y = tp.y; data.touch_data_touch0_id = slot.touchIds[i]; } else { @@ -224,7 +232,7 @@ PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t chang ++touchNum; } const bool injectedTouch = !intercepted && (injected.touchLeft || injected.touchRight); - if (touchNum == 0 && (state.touchLeft || state.touchRight || injectedTouch)) { + if (!injectedContact && touchNum == 0 && (state.touchLeft || state.touchRight || injectedTouch)) { data.buttons |= 0x100000; touchNum = 1; data.touch_data_touch0_x = state.touchRight || (!intercepted && injected.touchRight) ? kTouchPadWidth * 3 / 4 : kTouchPadWidth / 4; diff --git a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp index 52b3363adb..d70f74b93b 100644 --- a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp +++ b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp @@ -79,9 +79,13 @@ int APS5_VABI scePlayerSelectionDialogGetResult(PlayerSelectionDialogResult* res return COMMON_DIALOG_RESULT_USER_CANCELED; } -int APS5_VABI scePlayerSelectionDialogParamInitialize(void* param) { +int APS5_VABI scePlayerSelectionDialogParamInitialize(PlayerSelectionDialogParam* param) { if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - NotImplemented_nid_no_patch(__func__); + std::memset(param, 0, sizeof(*param)); + param->base.size = sizeof(param->base); + param->base.magic = static_cast(reinterpret_cast(¶m->base)) - 1060003575u; + param->size = sizeof(*param); + return COMMON_DIALOG_OK; } int APS5_VABI scePlayerSelectionDialogTerminate(void) { diff --git a/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp b/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp index baf1617f53..8152710d3f 100644 --- a/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp +++ b/core/libs/prx/libScePlayerSelectionDialog/include/Api.hpp @@ -11,7 +11,7 @@ int APS5_VABI scePlayerSelectionDialogClose(void); int APS5_VABI scePlayerSelectionDialogUpdateStatus(void); int APS5_VABI scePlayerSelectionDialogGetStatus(void); int APS5_VABI scePlayerSelectionDialogGetResult(PlayerSelectionDialogResult* result); -int APS5_VABI scePlayerSelectionDialogParamInitialize(void* param); +int APS5_VABI scePlayerSelectionDialogParamInitialize(PlayerSelectionDialogParam* param); int APS5_VABI scePlayerSelectionDialogTerminate(void); } diff --git a/core/libs/prx/libScePosixForWebKit/Export.cpp b/core/libs/prx/libScePosixForWebKit/Export.cpp index 14675b52d3..d1a3a2320e 100644 --- a/core/libs/prx/libScePosixForWebKit/Export.cpp +++ b/core/libs/prx/libScePosixForWebKit/Export.cpp @@ -193,6 +193,16 @@ std::uint32_t APS5_VABI arc4random_nid_postfix() { return value; } +std::uint32_t APS5_VABI arc4random_uniform_nid_postfix(std::uint32_t upper_bound) { + if (upper_bound < 2) return 0; + const std::uint32_t min = -upper_bound % upper_bound; + std::uint32_t value; + do { + value = arc4random_nid_postfix(); + } while (value < min); + return value % upper_bound; +} + int APS5_VABI __wrap_access(const char* path, int mode) { return access_nid_postfix(path, mode); } diff --git a/core/libs/prx/libScePosixForWebKit/include/Api.hpp b/core/libs/prx/libScePosixForWebKit/include/Api.hpp index 96834f0ad7..21562c5fcb 100644 --- a/core/libs/prx/libScePosixForWebKit/include/Api.hpp +++ b/core/libs/prx/libScePosixForWebKit/include/Api.hpp @@ -10,6 +10,7 @@ int APS5_VABI fnmatch_nid_postfix(const char* pattern, const char* string, int f char* APS5_VABI strcasestr_nid_postfix(const char* text, const char* needle); void APS5_VABI arc4random_buf_nid_postfix(void* buffer, std::size_t size); std::uint32_t APS5_VABI arc4random_nid_postfix(); +std::uint32_t APS5_VABI arc4random_uniform_nid_postfix(std::uint32_t upper_bound); int APS5_VABI __wrap_access(const char* path, int mode); int APS5_VABI __wrap_clock_gettime(int clockId, KernelTimespec* tp); int APS5_VABI __wrap_fchown(int descriptor, std::uint32_t owner, std::uint32_t group); diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index c93e6d9ca8..315bd0e5b4 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -20,6 +20,7 @@ constexpr std::int32_t SCE_PSML_ERROR_INVALID_ARGUMENT = static_cast(0x8A810009); constexpr std::int32_t SCE_PSML_ERROR_UNSUPPORTED_TYPE = static_cast(0x8A81000D); constexpr std::int32_t SCE_PSML_ERROR_UNSUPPORTED_CONSOLE = static_cast(0x8A810012); +constexpr std::int32_t SCE_PSML_ERROR_UNSUPPORTED_RESOLUTION = static_cast(0x8A810013); constexpr std::int32_t SCE_PSML_ERROR_SHARED_RESOURCES_BUSY = static_cast(0x8A810015); constexpr std::int32_t Mfsr2ErrorNotInitialized = static_cast(0x8A8A0001); constexpr std::int32_t Mfsr2ErrorUnsupportedConsole = static_cast(0x8A8A0012); @@ -71,6 +72,8 @@ struct MfsrObject { }; std::atomic g_mfsrInitialized{false}; +std::atomic g_mfsrSpecularAA{0}; +std::atomic g_mfsrFeatureFlags{0}; std::mutex g_mfsrMutex; std::unordered_map g_mfsrObjects; @@ -162,7 +165,11 @@ std::int32_t APS5_VABI scePsmlMfsrInit() { std::int32_t APS5_VABI scePsmlMfsrGetSharedResourcesInitRequirement(MfsrRequirement* requirement, const MfsrSharedResourcesParam* param) { if (!g_mfsrInitialized) return SCE_PSML_ERROR_NOT_INITIALIZED; RequireMfsr(requirement != nullptr && param != nullptr, __func__, "null requirement or parameter"); - if (param->type != 0) return SCE_PSML_ERROR_UNSUPPORTED_TYPE; + if (param->type != 0) { + const bool gated = (param->type == 1 && (g_mfsrFeatureFlags.load() & 1u) != 0) || (param->type == 2 && (g_mfsrFeatureFlags.load() & 2u) != 0); + if (!gated) return SCE_PSML_ERROR_UNSUPPORTED_TYPE; + NotImplemented_nid_no_patch(__func__); + } *requirement = {MfsrBlockBytes, MfsrBlockBytes, MfsrSharedBlockCount}; return 0; } @@ -211,8 +218,16 @@ std::int32_t APS5_VABI scePsmlMfsrReleaseContext(void* context) { } std::int32_t APS5_VABI scePsmlMfsrCreateContext1300(void** context, const void* param) { + if (param != nullptr) { + const auto* bytes = static_cast(param); + std::uint32_t type; + std::uint16_t width, height; + std::memcpy(&type, bytes, sizeof(type)); + std::memcpy(&width, bytes + 0x18, sizeof(width)); + std::memcpy(&height, bytes + 0x1a, sizeof(height)); + if (type == 0 && (width > 3840 || height > 2160)) return SCE_PSML_ERROR_UNSUPPORTED_RESOLUTION; + } (void)context; - (void)param; if (g_mfsrInitialized) NotImplemented_nid_no_patch(__func__); return SCE_PSML_ERROR_NOT_INITIALIZED; } @@ -334,14 +349,19 @@ std::int32_t APS5_VABI scePsmlMfsr2Init() { return 0; } -int APS5_VABI scePsmlMfsrEnableSpecularAA() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePsmlMfsrEnableSpecularAA(std::uint32_t) { + g_mfsrSpecularAA = 1; + return 0; } -int APS5_VABI scePsmlMfsrEnableQuality8K() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI scePsmlMfsrEnableQuality8K(std::uint32_t) { + g_mfsrFeatureFlags |= 1u; + return 0; +} + +int APS5_VABI scePsmlMfsrEnableLightWeight(std::uint32_t) { + g_mfsrFeatureFlags |= 2u; + return 0; } } diff --git a/core/libs/prx/libScePsml_debug/include/Api.hpp b/core/libs/prx/libScePsml_debug/include/Api.hpp index 4a79a346d5..b856b4e055 100644 --- a/core/libs/prx/libScePsml_debug/include/Api.hpp +++ b/core/libs/prx/libScePsml_debug/include/Api.hpp @@ -127,7 +127,8 @@ std::int32_t APS5_VABI scePsmlMfsr2CreateSharedResources(void** sharedResources, std::int32_t APS5_VABI scePsmlMfsr2GetDispatchPackets(void* context, const MfsrCommandBuffer* commandBuffer, const Mfsr2DispatchParam* param); std::int32_t APS5_VABI scePsmlMfsr2CreateContext(void** context, const Mfsr2ContextParam* param); std::int32_t APS5_VABI scePsmlMfsr2Init(); -int APS5_VABI scePsmlMfsrEnableSpecularAA(); -int APS5_VABI scePsmlMfsrEnableQuality8K(); +int APS5_VABI scePsmlMfsrEnableSpecularAA(std::uint32_t); +int APS5_VABI scePsmlMfsrEnableQuality8K(std::uint32_t); +int APS5_VABI scePsmlMfsrEnableLightWeight(std::uint32_t); } diff --git a/core/libs/prx/libSceRemoteplay/Export.cpp b/core/libs/prx/libSceRemoteplay/Export.cpp index c398ec2033..fbc5a0351f 100644 --- a/core/libs/prx/libSceRemoteplay/Export.cpp +++ b/core/libs/prx/libSceRemoteplay/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceRemoteplay/include/InventoryExports.hpp" #include "prx/libSceRemoteplay/include/Api.hpp" #include #include @@ -10,6 +11,23 @@ constexpr int REMOTEPLAY_CONNECTION_STATUS_DISCONNECT = 0; extern "C" { +int APS5_VABI sceRemoteplayIsRemoteViewering() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemoteplayNotifyCanWakeup() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemoteplayRegistCallback() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemoteplayUnregistCallback() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceRemoteplayGetConnectionStatus(int user_id, int* status) { (void)user_id; if (!status) APS5_INVALID_ARG_EX; diff --git a/core/libs/prx/libSceRemoteplay/include/InventoryExports.hpp b/core/libs/prx/libSceRemoteplay/include/InventoryExports.hpp new file mode 100644 index 0000000000..8b7845baef --- /dev/null +++ b/core/libs/prx/libSceRemoteplay/include/InventoryExports.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRemoteplayIsRemoteViewering(); +int APS5_VABI sceRemoteplayNotifyCanWakeup(); +int APS5_VABI sceRemoteplayRegistCallback(); +int APS5_VABI sceRemoteplayUnregistCallback(); + +} diff --git a/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp b/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp index a04f3f394f..9bf2668fcb 100644 --- a/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp +++ b/core/libs/prx/libSceRemoteplay/libSceRemoteplay.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceRemoteplay/include/Api.hpp" +#include "prx/libSceRemoteplay/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceRudp/Export.cpp b/core/libs/prx/libSceRudp/Export.cpp index e50ffaab62..41955952c5 100644 --- a/core/libs/prx/libSceRudp/Export.cpp +++ b/core/libs/prx/libSceRudp/Export.cpp @@ -25,6 +25,7 @@ constexpr int RUDP_ERROR_MEMORY = static_cast(0x80770007u); constexpr int RUDP_ERROR_CONN_RESET = static_cast(0x80770009u); constexpr int RUDP_ERROR_CONN_REFUSED = static_cast(0x8077000Au); constexpr int RUDP_ERROR_NOT_ACCEPTABLE = static_cast(0x80770011u); +constexpr int RUDP_ERROR_CANCELLED = static_cast(0x80770014u); constexpr int RUDP_ERROR_INVALID_POLL_ID = static_cast(0x8077001Fu); constexpr int RUDP_ERROR_TOO_MANY_CONTEXTS = static_cast(0x80770020u); @@ -90,12 +91,38 @@ void* g_handler_arg = nullptr; struct RudpPoll { std::size_t capacity; std::map entries; + int waiters = 0; + int draining = 0; + uint64_t cancels = 0; + bool cancelPending = false; }; int g_next_poll = 1; std::map g_polls; TimedWait::Condition g_polls_changed; +struct RudpPollWaitRegistration { + int pollId; + uint64_t cancels; + std::unique_lock& lock; + + ~RudpPollWaitRegistration() { + if (!lock.owns_lock()) { + lock.lock(); + } + const auto poll = g_polls.find(pollId); + if (poll == g_polls.end()) { + return; + } + if (poll->second.cancels == cancels) { + --poll->second.waiters; + } else { + --poll->second.draining; + } + g_polls_changed.NotifyAll(); + } +}; + bool valid_ctx(int ctx) { return g_ctx.count(ctx) != 0; } @@ -444,16 +471,38 @@ int APS5_VABI sceRudpPollWait(int pollId, RudpPollEvent* events, std::size_t eve if (pollId < 0 || events == nullptr || eventLen == 0) { return RUDP_ERROR_INVALID_ARGUMENT; } - if (g_polls.count(pollId) == 0) { + const auto poll = g_polls.find(pollId); + if (poll == g_polls.end()) { return RUDP_ERROR_INVALID_POLL_ID; } - const auto closed = [pollId] { return !g_inited || g_polls.count(pollId) == 0; }; - if (timeout > RUDP_POLL_WAIT_MAX_USEC) { - g_polls_changed.Wait(lk, closed); - } else if (!g_polls_changed.WaitUntil(lk, TimedWait::DeadlineNanos(timeout), closed)) { - return 0; + if (poll->second.cancelPending) { + poll->second.cancelPending = false; + return RUDP_ERROR_CANCELLED; } - return g_inited ? RUDP_ERROR_INVALID_POLL_ID : RUDP_ERROR_NOT_INITIALIZED; + const uint64_t cancels = poll->second.cancels; + ++poll->second.waiters; + const RudpPollWaitRegistration registration{pollId, cancels, lk}; + const auto woken = [pollId, cancels] { + if (!g_inited) { + return true; + } + const auto current = g_polls.find(pollId); + return current == g_polls.end() || current->second.cancels != cancels; + }; + bool signalled = true; + if (timeout > RUDP_POLL_WAIT_MAX_USEC) { + g_polls_changed.Wait(lk, woken); + } else { + signalled = g_polls_changed.WaitUntil(lk, TimedWait::DeadlineNanos(timeout), woken); + } + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + const auto current = g_polls.find(pollId); + if (current == g_polls.end()) { + return RUDP_ERROR_INVALID_POLL_ID; + } + return signalled ? RUDP_ERROR_CANCELLED : 0; } int APS5_VABI sceRudpPollDestroy(int pollId) { @@ -491,8 +540,33 @@ int APS5_VABI sceRudpListen() { return 0; } -int APS5_VABI sceRudpPollCancel() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceRudpPollCancel(int pollId) { + std::unique_lock lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + if (pollId < 0) { + return RUDP_ERROR_INVALID_ARGUMENT; + } + const auto poll = g_polls.find(pollId); + if (poll == g_polls.end()) { + return RUDP_ERROR_INVALID_POLL_ID; + } + if (poll->second.waiters == 0) { + poll->second.cancelPending = true; + return 0; + } + ++poll->second.cancels; + poll->second.draining += poll->second.waiters; + poll->second.waiters = 0; + g_polls_changed.NotifyAll(); + g_polls_changed.Wait(lk, [pollId] { + if (!g_inited) { + return true; + } + const auto current = g_polls.find(pollId); + return current == g_polls.end() || current->second.draining == 0; + }); return 0; } diff --git a/core/libs/prx/libSceRudp/include/Api.hpp b/core/libs/prx/libSceRudp/include/Api.hpp index 9ea882c3bc..ef5db9da08 100644 --- a/core/libs/prx/libSceRudp/include/Api.hpp +++ b/core/libs/prx/libSceRudp/include/Api.hpp @@ -38,7 +38,7 @@ int APS5_VABI sceRudpAccept(); int APS5_VABI sceRudpFlush(); int APS5_VABI sceRudpGetLocalInfo(); int APS5_VABI sceRudpListen(); -int APS5_VABI sceRudpPollCancel(); +int APS5_VABI sceRudpPollCancel(int pollId); int APS5_VABI sceRudpSetMaxSegmentSize(uint16_t mss); } diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 5236fba466..7d1e1e0c55 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -1,6 +1,5 @@ -add_library(libSceSaveData.native SHARED EXCLUDE_FROM_ALL Export.cpp) -target_include_directories(libSceSaveData.native PRIVATE ${LIBS_INCLUDE_DIR}) -configure_windows_unwind(libSceSaveData.native) +include(${CMAKE_CURRENT_SOURCE_DIR}/SaveDataLibrary.cmake) +add_sce_savedata_library(libSceSaveData.native) if(BUILD_TESTING) add_test_executable(guest_savedata_search_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataSearch.cpp) diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index e32d58bb14..2caada7a85 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -940,7 +940,7 @@ int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond* cond, static int convert(const SaveDataConvert* param) { if (param == nullptr || param->dir_name == nullptr) { - throw std::runtime_error("sceSaveDataConvert: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } g_convertProgress = 0.0f; const auto* nameEnd = static_cast(std::memchr(param->dir_name->data, '\0', sizeof(param->dir_name->data))); @@ -1040,6 +1040,336 @@ int APS5_VABI sceSaveDataDebugTarget() { return 0; } +int APS5_VABI sceSaveDataAbort() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataBindPsnAccount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataBindPsnAccountForSystemBackup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataChangeDatabase(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataChangeInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckBackupData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckBackupDataInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckBackupDataLight(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckCloudData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckSaveDataBroken(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckSaveDataVersion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCheckSaveDataVersionLatest(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataClearProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataCreateUploadData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDebugGetThreadId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDeleteAllUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDeleteBackupData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDeleteCloudData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDeleteProspero(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDeleteTitle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDeleteUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDirNameSearchInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataDownload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetAllSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetAppLaunchedUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetAutoUploadConditions(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetAutoUploadRequestInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetAutoUploadSetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetBoundPsnAccountCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetClientThreadPriority(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetCloudQuotaInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetEventInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetMountedSaveDataCount() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetSaveDataCount() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetSaveDataRootDir(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetSaveDataRootPath(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetSaveDataRootUsbPath(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetSavePoint(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataGetUpdatedDataCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataInitialize2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataInitializeForCdlg(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataIsDeletingUsbDb(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataIsMounted() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataManualDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataManualDownload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataManualUpload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataMount5() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataMountInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataMountSys(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataRebuildDatabase(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataRegisterEventCallback() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataRestoreBackupData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataRestoreLoadSaveDataMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataSetAutoUploadSetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataSetEventInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataSetSaveDataLibraryUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataShutdownStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataSupportedFakeBrokenStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataSyncCloudList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataUmountSys(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataUmountWithBackup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataUnregisterEventCallback() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSaveDataUpload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } namespace { diff --git a/core/libs/prx/libSceSaveData.native/SaveDataLibrary.cmake b/core/libs/prx/libSceSaveData.native/SaveDataLibrary.cmake new file mode 100644 index 0000000000..9a7252c375 --- /dev/null +++ b/core/libs/prx/libSceSaveData.native/SaveDataLibrary.cmake @@ -0,0 +1,5 @@ +function(add_sce_savedata_library target) + add_library(${target} SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Export.cpp) + target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR}) + configure_windows_unwind(${target}) +endfunction() diff --git a/core/libs/prx/libSceSaveData/CMakeLists.txt b/core/libs/prx/libSceSaveData/CMakeLists.txt new file mode 100644 index 0000000000..ed878f0e32 --- /dev/null +++ b/core/libs/prx/libSceSaveData/CMakeLists.txt @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceSaveData.native/SaveDataLibrary.cmake) +add_sce_savedata_library(libSceSaveData) diff --git a/core/libs/prx/libSceSaveData/Export.cpp b/core/libs/prx/libSceSaveData/Export.cpp deleted file mode 100644 index 6fbc7bb184..0000000000 --- a/core/libs/prx/libSceSaveData/Export.cpp +++ /dev/null @@ -1,426 +0,0 @@ -#include "prx/libSceSaveData/include/Api.hpp" -#include -#include -#include -#include -#include "SceTypes.hpp" -#include "prx/libSceSaveData/SaveDataCore.hpp" -#include "prx/libc/include/General.hpp" - -// Savedata imports bind by NID either to libSceSaveData.native or (via the relinker alias table, -// by name) to this library. Every entry point used to return SoftFailNp (0x800242e2), which is the -// NP-family "online service not available" code - wrong dialect for a purely local save call and -// fatal-looking for callers. All entry points now delegate to SaveDataCore.hpp: a local, -// file-backed implementation that needs no PSN account and tolerates any userId -// (0 / 65535 / 268435456), mirroring the native library. - -namespace { -// Logs the first three calls of each entry point, then stays silent, keeping guest logs readable. -#define SAVEDATA_TRACE(...) \ - do { \ - static std::atomic traceCount{0}; \ - if (traceCount.fetch_add(1, std::memory_order_relaxed) < 3) { \ - std::fprintf(stderr, "[SAVEDATA:core] " __VA_ARGS__); \ - std::fputc('\n', stderr); \ - std::fflush(stderr); \ - } \ - } while (0) -} // namespace - -extern "C" { - -int APS5_VABI sceSaveDataInitialize3(const void* init) { - const int rc = savedata::initialize_internal(init); - SAVEDATA_TRACE("sceSaveDataInitialize3 -> 0x%08x", static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataTerminate(void) { - const int rc = savedata::terminate_internal(); - SAVEDATA_TRACE("sceSaveDataTerminate -> 0x%08x", static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) { - const int rc = savedata::backup_internal(backup, nullptr); - SAVEDATA_TRACE("sceSaveDataBackup -> 0x%08x", static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataGetEventResult(const void* event_param, SaveDataEvent* event) { - const int rc = savedata::get_event_result_internal(event_param, event); - SAVEDATA_TRACE("sceSaveDataGetEventResult -> 0x%08x", static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) { - if (get_param == nullptr) { - return savedata::SD_ERROR_PARAMETER; - } - const int rc = savedata::get_memory_internal(get_param); - SAVEDATA_TRACE("get2 user=%d slot=%u -> 0x%08x", static_cast(get_param->user_id), - static_cast(get_param->slot_id), static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) { - if (set_param == nullptr) { - return savedata::SD_ERROR_PARAMETER; - } - const int rc = savedata::set_memory_internal(set_param); - SAVEDATA_TRACE("set2 user=%d slot=%u num=%u -> 0x%08x", - static_cast(set_param->user_id), static_cast(set_param->slot_id), - static_cast(set_param->data_num), static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param, - SaveDataMemorySetupResult* result) { - const int rc = savedata::setup_memory_internal(setup_param, result, nullptr); - SAVEDATA_TRACE("setup2 -> 0x%08x existed=0x%zx", static_cast(rc), - result != nullptr ? result->existed_memory_size : static_cast(0)); - return rc; -} -int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* sync_param) { - if (sync_param == nullptr) { - return savedata::SD_ERROR_PARAMETER; - } - const auto* sync = static_cast(sync_param); - const int rc = savedata::sync_memory_internal(sync->user_id, sync->slot_id, sync->option, nullptr, true); - SAVEDATA_TRACE("sync user=%d slot=%u option=%u -> 0x%08x", static_cast(sync->user_id), - static_cast(sync->slot_id), static_cast(sync->option), - static_cast(rc)); - return rc; -} -int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount, - SaveDataMountResult* mount_result) { - const int rc = savedata::transferring_mount_internal(mount, mount_result); - SAVEDATA_TRACE("sceSaveDataTransferringMount -> 0x%08x", static_cast(rc)); - return rc; -} - -int APS5_VABI sceSaveDataAbort() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataBindPsnAccount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataBindPsnAccountForSystemBackup(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataChangeDatabase(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataChangeInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckBackupData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckBackupDataInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckBackupDataLight(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckCloudData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckSaveDataBroken(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckSaveDataVersion(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCheckSaveDataVersionLatest(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataClearProgress() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataCreateUploadData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDebugGetThreadId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDeleteAllUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDeleteBackupData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDeleteCloudData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDeleteProspero(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDeleteTitle(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDeleteUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDirNameSearchInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataDownload(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetAllSize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetAppLaunchedUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetAutoUploadConditions(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetAutoUploadRequestInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetAutoUploadSetting(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetBoundPsnAccountCount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetClientThreadPriority(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetCloudQuotaInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetEventInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetMountedSaveDataCount() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetProgress() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetSaveDataCount() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetSaveDataRootDir(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetSaveDataRootPath(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetSaveDataRootUsbPath(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetSavePoint(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataGetUpdatedDataCount(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataInitialize2() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataInitializeForCdlg(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataIsDeletingUsbDb(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataIsMounted() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataManualDelete(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataManualDownload(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataManualUpload(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataMount5() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataMountInternal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataMountSys(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataRebuildDatabase(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataRegisterEventCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataRestoreBackupData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataRestoreLoadSaveDataMemory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataSetAutoUploadSetting(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataSetEventInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataSetSaveDataLibraryUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataShutdownStart(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataSupportedFakeBrokenStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataSyncCloudList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataUmountSys(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataUmountWithBackup(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataUnregisterEventCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSaveDataUpload(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -} diff --git a/core/libs/prx/libSceSaveData/SaveDataCore.hpp b/core/libs/prx/libSceSaveData/SaveDataCore.hpp deleted file mode 100644 index 1da806ac51..0000000000 --- a/core/libs/prx/libSceSaveData/SaveDataCore.hpp +++ /dev/null @@ -1,1320 +0,0 @@ -#ifndef CORE_LIBS_PRX_LIBSCESAVEDATA_SAVEDATACORE_HPP -#define CORE_LIBS_PRX_LIBSCESAVEDATA_SAVEDATACORE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "SceTypes.hpp" -#include "prx/libkernel/include/Api.hpp" -#include "prx/libc/include/General.hpp" -#include "prx/libc/include/RemoveTree.hpp" -#include "prx/libSceSaveData/SaveDataFile.hpp" - -namespace savedata { - -namespace { - -constexpr std::uint32_t SD_FACILITY = 0x809F0000u; -constexpr int sd_error(std::uint32_t code) { return static_cast(SD_FACILITY + code); } - -constexpr int SD_OK = 0; -constexpr int SD_ERROR_PARAMETER = sd_error(0x00); -constexpr int SD_ERROR_NOT_INITIALIZED = sd_error(0x01); -constexpr int SD_ERROR_OUT_OF_MEMORY = sd_error(0x02); -constexpr int SD_ERROR_BUSY = sd_error(0x03); -constexpr int SD_ERROR_NOT_MOUNTED = sd_error(0x04); -constexpr int SD_ERROR_NO_PERMISSION = sd_error(0x05); -constexpr int SD_ERROR_FINGERPRINT_MISMATCH = sd_error(0x06); -constexpr int SD_ERROR_EXISTS = sd_error(0x07); -constexpr int SD_ERROR_NOT_FOUND = sd_error(0x08); -constexpr int SD_ERROR_NO_SPACE_FS = sd_error(0x0A); -constexpr int SD_ERROR_INTERNAL = sd_error(0x0B); -constexpr int SD_ERROR_MOUNT_FULL = sd_error(0x0C); -constexpr int SD_ERROR_BAD_MOUNTED = sd_error(0x0D); -constexpr int SD_ERROR_FILE_NOT_FOUND = sd_error(0x0E); -constexpr int SD_ERROR_BROKEN = sd_error(0x0F); -constexpr int SD_ERROR_INVALID_LOGIN_USER = sd_error(0x11); -constexpr int SD_ERROR_MEMORY_NOT_READY = sd_error(0x12); -constexpr int SD_ERROR_BACKUP_BUSY = sd_error(0x13); -constexpr int SD_ERROR_NOT_REGIST_CALLBACK = sd_error(0x15); -constexpr int SD_ERROR_BUSY_FOR_SAVING = sd_error(0x16); -constexpr int SD_ERROR_LIMITATION_OVER = sd_error(0x17); -constexpr int SD_ERROR_EVENT_BUSY = sd_error(0x18); - -constexpr std::uint32_t SD_MOUNT_RDONLY = 1u; -constexpr std::uint32_t SD_MOUNT_RDWR = 2u; -constexpr std::uint32_t SD_MOUNT_CREATE = 4u; -constexpr std::uint32_t SD_MOUNT_COPY_ICON = 16u; -constexpr std::uint32_t SD_MOUNT_CREATE2 = 32u; - -constexpr std::uint32_t SD_UMOUNT_BACKUP_ASYNC = 1u << 16u; -constexpr std::uint32_t SD_COMMIT_BACKUP_ASYNC = 1u; - -constexpr std::uint32_t SD_EVENT_UMOUNT_BACKUP_END = 1u; -constexpr std::uint32_t SD_EVENT_BACKUP_END = 2u; -constexpr std::uint32_t SD_EVENT_MEMORY_SYNC_END = 3u; -constexpr std::uint32_t SD_EVENT_COMMIT_BACKUP_END = 4u; - -constexpr std::uint64_t SD_BLOCKS_MIN = 1; -constexpr std::uint64_t SD_BLOCKS_MAX = 32768; - -constexpr std::uint32_t SD_MEMORY_SLOTS = 4; -constexpr std::uint32_t SD_MEMORY_MAX_MOUNTED = 4; -constexpr std::size_t SD_MEMORY_MAX_SIZE = 32u * 1024u * 1024u; -constexpr std::uint32_t SD_MEMORY_SET_PARAM = 1u; -constexpr std::uint32_t SD_MEMORY_DOUBLE_BUFFER = 2u; -constexpr std::uint32_t SD_MEMORY_MAX_DATA_NUM = 5; - -constexpr std::uint32_t SD_PARAM_WHOLE = 0; -constexpr std::uint32_t SD_PARAM_TITLE = 1; -constexpr std::uint32_t SD_PARAM_SUB_TITLE = 2; -constexpr std::uint32_t SD_PARAM_DETAIL = 3; -constexpr std::uint32_t SD_PARAM_USER_PARAM = 4; -constexpr std::uint32_t SD_PARAM_MTIME = 5; - -constexpr std::uint32_t SD_APP_STATUS_NONE = 0; -constexpr std::uint32_t SD_APP_STATUS_DELETED = 1; -constexpr std::uint32_t SD_APP_STATUS_LOCAL = 2; -constexpr std::uint32_t SD_APP_STATUS_ONLINE = 4; -constexpr std::uint32_t SD_APP_STATUS_ANY = 0x7FFFFFFFu; - -constexpr char SD_ROOT_GUEST[] = "/_sd"; -constexpr char SD_SUBDIR[] = "sce_sys"; -constexpr char SD_MEMORY_PREFIX[] = "sce_sdmemory"; -constexpr std::size_t SD_MOUNT_PREFIX_LEN = sizeof(SD_ROOT_GUEST); -constexpr std::size_t SD_MOUNT_POINT_CAPACITY = sizeof(SaveDataMountPoint) - SD_MOUNT_PREFIX_LEN - 1; - -struct MemorySync { - std::int32_t user_id; - std::uint32_t slot_id; - std::uint32_t option; - std::uint8_t reserved[28]; -}; -static_assert(sizeof(MemorySync) == 40, "SceSaveDataMemorySync is 40 bytes"); - -struct EventParam { - std::size_t size; - std::uint8_t reserved[24]; -}; - -struct MountSlot { - bool used = false; - std::string mount_point; - std::string dir_name; - std::filesystem::path directory; -}; - -struct MemorySlot { - std::vector data; - SaveDataParam param {}; - std::uint32_t option = 0; - bool dirty = false; -}; - -struct PendingEvent { - SaveDataEvent record {}; - KernelEventFlag flag = nullptr; -}; - -struct State { - std::mutex mutex; - std::uint32_t instances = 0; - std::array slots {}; - std::deque events; - std::unordered_map memory; - std::map aliases; - std::int32_t next_transaction_resource = 1; - std::set live_transaction_resources; - void* event_callback = nullptr; - void* event_callback_userdata = nullptr; - bool aliases_loaded = false; -}; - -State& state() { - static State instance; - return instance; -} - -bool is_ready() { - return state().instances != 0; -} - -bool valid_dir_name(const char* name) { - if (name == nullptr || name[0] == '\0') { - return false; - } - std::size_t length = 0; - while (name[length] != '\0') { - length++; - if (length >= sizeof(SceSaveDataDirName::data)) { - return false; - } - } - if (std::strcmp(name, ".") == 0 || std::strcmp(name, "..") == 0) { - return false; - } - for (const char* p = name; *p != '\0'; p++) { - const bool ok = std::isalnum(static_cast(*p)) != 0 || *p == '_' || *p == '-' || - *p == '.' || *p == '@'; - if (!ok) { - return false; - } - } - return true; -} - -bool bounded_string_ok(const char* data, std::size_t capacity) { - return std::memchr(data, '\0', capacity) != nullptr; -} - -const std::filesystem::path& save_root() { - static const std::filesystem::path root = ResolvePath_nid_no_patch(SD_ROOT_GUEST); - return root; -} - -bool ensure_save_root(std::error_code& error) { - if (std::filesystem::create_directories(save_root(), error)) RecordWrittenPath_nid_no_patch(save_root()); - if (error) { - error.clear(); - } - return std::filesystem::is_directory(save_root(), error); -} - -void load_aliases_locked(State& st) { - if (st.aliases_loaded) { - return; - } - st.aliases_loaded = true; - std::ifstream file(save_root() / "_aliases", std::ios::binary); - std::string line; - while (std::getline(file, line)) { - const std::size_t sep = line.find('\t'); - if (sep == std::string::npos || sep == 0) { - continue; - } - st.aliases[line.substr(0, sep)] = line.substr(sep + 1); - } -} - -void store_aliases_locked(State& st) { - std::error_code error; - ensure_save_root(error); - std::ofstream file(save_root() / "_aliases", std::ios::binary | std::ios::trunc); - for (const auto& [alias, dir_name] : st.aliases) { - file << alias << '\t' << dir_name << '\n'; - } - RecordWrittenPath_nid_no_patch(save_root() / "_aliases"); -} - -std::string save_alias_locked(State& st, const std::string& dir_name) { - if (dir_name.size() <= SD_MOUNT_POINT_CAPACITY) { - return dir_name; - } - load_aliases_locked(st); - const std::string base = dir_name.substr(0, 6); - std::uint32_t hash = 2166136261u; - for (const char c : dir_name) { - hash = (hash ^ static_cast(c)) * 16777619u; - } - for (std::uint32_t attempt = 0; attempt < 4096u; attempt++) { - char tail[8]; - std::snprintf(tail, sizeof(tail), "~%03X", static_cast((hash + attempt) & 0xFFFu)); - const std::string candidate = base + tail; - const auto found = st.aliases.find(candidate); - if (found == st.aliases.end()) { - st.aliases[candidate] = dir_name; - store_aliases_locked(st); - return candidate; - } - if (found->second == dir_name) { - return candidate; - } - } - return base; -} - -std::string real_dir_name_locked(State& st, const std::string& alias) { - load_aliases_locked(st); - const auto found = st.aliases.find(alias); - return found != st.aliases.end() ? found->second : alias; -} - -std::string mount_point_for(const std::string& alias) { - return std::string(SD_ROOT_GUEST) + "/" + alias; -} - -std::filesystem::path save_directory(const std::string& alias) { - return save_root() / alias; -} - -std::filesystem::path save_metadata_directory(const std::filesystem::path& directory) { - return directory / SD_SUBDIR; -} - -std::filesystem::path save_param_path(const std::filesystem::path& directory) { - return save_metadata_directory(directory) / "param.bin"; -} - -std::filesystem::path save_blocks_path(const std::filesystem::path& directory) { - return save_metadata_directory(directory) / "blocks.bin"; -} - -bool write_blob(const std::filesystem::path& path, const void* data, std::size_t size) { - if (!replace_file(path, data, size)) return false; - RecordWrittenPath_nid_no_patch(path); - return true; -} - -bool read_blob(const std::filesystem::path& path, void* data, std::size_t size) { - std::ifstream file(path, std::ios::binary); - if (!file) { - return false; - } - file.read(static_cast(data), static_cast(size)); - return static_cast(file.gcount()) == size; -} - -int write_u64(const std::filesystem::path& path, std::uint64_t value) { - return write_blob(path, &value, sizeof(value)) ? SD_OK : SD_ERROR_INTERNAL; -} - -int read_u64(const std::filesystem::path& path, std::uint64_t* value) { - std::error_code error; - if (!std::filesystem::is_regular_file(path, error) || error) { - return SD_ERROR_BROKEN; - } - if (std::filesystem::file_size(path, error) != sizeof(std::uint64_t) || error) { - return SD_ERROR_BROKEN; - } - return read_blob(path, value, sizeof(*value)) ? SD_OK : SD_ERROR_BROKEN; -} - -int load_save_param(const std::filesystem::path& directory, SaveDataParam* param) { - std::memset(param, 0, sizeof(*param)); - const int status = read_blob(save_param_path(directory), param, sizeof(*param)) ? SD_OK : SD_ERROR_NOT_FOUND; - if (status == SD_OK) { - std::int64_t newest = 0; - std::error_code error; - for (std::filesystem::recursive_directory_iterator it(directory, - std::filesystem::directory_options::skip_permission_denied, error), end; - it != end; it.increment(error)) { - if (error) { - error.clear(); - continue; - } - std::error_code file_error; - if (it->is_regular_file(file_error) && !file_error) { - const auto written = std::filesystem::last_write_time(it->path(), file_error); - if (!file_error) { - const auto seconds = std::chrono::duration_cast( - std::filesystem::file_time_type::clock::now() - written).count(); - const auto now = static_cast(std::time(nullptr)); - newest = std::max(newest, now - static_cast(seconds)); - } - } - } - param->mtime = newest; - } - return status; -} - -bool save_is_mounted_locked(const State& st, const std::filesystem::path& directory) { - for (const auto& slot : st.slots) { - if (slot.used && slot.directory == directory) { - return true; - } - } - return false; -} - -int slot_of_mount_point_locked(const State& st, const char* mount_point) { - for (std::size_t i = 0; i < st.slots.size(); i++) { - if (st.slots[i].used && st.slots[i].mount_point == mount_point) { - return static_cast(i); - } - } - return -1; -} - -int free_slot_locked(const State& st) { - for (std::size_t i = 0; i < st.slots.size(); i++) { - if (!st.slots[i].used) { - return static_cast(i); - } - } - return -1; -} - -void queue_event_locked(State& st, std::uint32_t type, std::int32_t user_id, - const SceSaveDataTitleId* title_id, const SceSaveDataDirName* dir_name, - int error_code, KernelEventFlag flag) { - PendingEvent pending; - pending.record = {}; - pending.record.type = type; - pending.record.error_code = error_code; - pending.record.user_id = user_id; - if (title_id != nullptr) { - std::memcpy(&pending.record.title_id, title_id, sizeof(pending.record.title_id)); - } - if (dir_name != nullptr) { - std::memcpy(&pending.record.dir_name, dir_name, sizeof(pending.record.dir_name)); - } - pending.flag = flag; - st.events.push_back(pending); - while (st.events.size() > 64) { - if (st.events.front().flag != nullptr) { - sceKernelDeleteEventFlag(st.events.front().flag); - } - st.events.pop_front(); - } -} - -KernelEventFlag create_signalled_flag(const char* name) { - KernelEventFlag flag = nullptr; - if (sceKernelCreateEventFlag(&flag, name, 0x20u, 0, nullptr) != 0) { - return nullptr; - } - sceKernelSetEventFlag(flag, 1u); - return flag; -} - -int initialize_internal(const void* init) { - State& st = state(); - std::lock_guard lock(st.mutex); - st.instances++; - return SD_OK; -} - -int terminate_internal() { - State& st = state(); - std::lock_guard lock(st.mutex); - if (st.instances == 0) { - return SD_ERROR_NOT_INITIALIZED; - } - if (std::any_of(st.slots.begin(), st.slots.end(), - [](const MountSlot& slot) { return slot.used; }) || !st.memory.empty()) { - return SD_ERROR_BUSY; - } - st.instances--; - if (st.instances == 0) { - for (auto& pending : st.events) { - if (pending.flag != nullptr) { - sceKernelDeleteEventFlag(pending.flag); - } - } - st.events.clear(); - st.memory.clear(); - st.live_transaction_resources.clear(); - } - return SD_OK; -} - -int mount_internal(const SceSaveDataDirName* dir_name, std::uint32_t mount_mode, - std::uint64_t blocks, std::int32_t user_id, SaveDataMountResult* mount_result, - const char* api) { - if (mount_result == nullptr) { - return SD_ERROR_PARAMETER; - } - std::memset(mount_result, 0, sizeof(*mount_result)); - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || - !valid_dir_name(dir_name->data)) { - return SD_ERROR_PARAMETER; - } - if (user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - const bool create = (mount_mode & SD_MOUNT_CREATE) != 0; - const bool create_or_open = (mount_mode & SD_MOUNT_CREATE2) != 0; - const bool open = !create && !create_or_open && (mount_mode & (SD_MOUNT_RDONLY | SD_MOUNT_RDWR)) != 0; - if (!create && !create_or_open && !open) { - return SD_ERROR_PARAMETER; - } - - State& st = state(); - std::lock_guard lock(st.mutex); - const std::string requested = dir_name->data; - const std::string alias = save_alias_locked(st, requested); - const std::string mount_point = mount_point_for(alias); - if (mount_point.size() + 1 > sizeof(mount_result->mount_point)) { - return SD_ERROR_PARAMETER; - } - const std::filesystem::path directory = save_directory(alias); - for (const auto& slot : st.slots) { - if (slot.used && slot.directory == directory) { - return SD_ERROR_BUSY; - } - } - const int free_index = free_slot_locked(st); - if (free_index == -1) { - return SD_ERROR_MOUNT_FULL; - } - - std::error_code error; - const bool exists = std::filesystem::is_directory(directory, error) && !error; - if (open && !exists) { - return SD_ERROR_NOT_FOUND; - } - if (create && exists) { - return SD_ERROR_EXISTS; - } - bool created = false; - if ((create || create_or_open) && !exists) { - if (blocks < SD_BLOCKS_MIN || blocks > SD_BLOCKS_MAX) { - return SD_ERROR_PARAMETER; - } - std::error_code root_error; - if (!ensure_save_root(root_error)) { - return SD_ERROR_INTERNAL; - } - if (!std::filesystem::create_directories(directory, error) || error) { - return SD_ERROR_INTERNAL; - } - RecordWrittenPath_nid_no_patch(directory); - created = true; - if (write_u64(save_blocks_path(directory), blocks) != SD_OK) { - std::error_code cleanup; - RemoveTree(directory, cleanup); - return SD_ERROR_INTERNAL; - } - SaveDataParam initial {}; - std::strncpy(initial.title, "Saved Data", sizeof(initial.title) - 1); - if (!write_blob(save_param_path(directory), &initial, sizeof(initial))) { - std::error_code cleanup; - RemoveTree(directory, cleanup); - return SD_ERROR_INTERNAL; - } - } - if (exists && !created) { - std::uint64_t recorded = 0; - if (read_u64(save_blocks_path(directory), &recorded) != SD_OK && blocks != 0) { - if (blocks < SD_BLOCKS_MIN || blocks > SD_BLOCKS_MAX) { - return SD_ERROR_PARAMETER; - } - write_u64(save_blocks_path(directory), blocks); - } - } - - MountSlot& slot = st.slots[static_cast(free_index)]; - slot.used = true; - slot.mount_point = mount_point; - slot.dir_name = requested; - slot.directory = directory; - std::memcpy(mount_result->mount_point.data, mount_point.c_str(), mount_point.size() + 1); - mount_result->required_blocks = 0; - mount_result->mount_status = created ? 1u : 0u; - return SD_OK; -} - -int umount_internal(std::uint32_t mode, const SaveDataMountPoint* mount_point, const char* api) { - State& st = state(); - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (mount_point == nullptr || !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - const int index = slot_of_mount_point_locked(st, mount_point->data); - if (index == -1) { - return SD_ERROR_NOT_MOUNTED; - } - st.slots[static_cast(index)] = MountSlot {}; - if ((mode & SD_UMOUNT_BACKUP_ASYNC) != 0) { - queue_event_locked(st, SD_EVENT_UMOUNT_BACKUP_END, 0, nullptr, nullptr, SD_OK, nullptr); - } - return SD_OK; -} - -int delete_internal(std::int32_t user_id, const SceSaveDataDirName* dir_name, - const SceSaveDataTitleId* title_id) { - State& st = state(); - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || - !valid_dir_name(dir_name->data) || - (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data)))) { - return SD_ERROR_PARAMETER; - } - if (user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - const std::string alias = save_alias_locked(st, dir_name->data); - const std::filesystem::path directory = save_directory(alias); - if (save_is_mounted_locked(st, directory)) { - return SD_ERROR_BUSY; - } - std::error_code error; - if (!std::filesystem::exists(directory, error) || error) { - return SD_ERROR_NOT_FOUND; - } - RemoveTree(directory, error); - RecordWrittenPath_nid_no_patch(directory); - if (error) { - return SD_ERROR_INTERNAL; - } - st.aliases.erase(alias); - if (alias != dir_name->data) { - store_aliases_locked(st); - } - return SD_OK; -} - -bool dir_name_matches(const char* str, const char* pattern) { - if (pattern == nullptr || pattern[0] == '\0') { - return true; - } - while (*str != '\0' && *pattern != '\0') { - if (*pattern == '%') { - for (const char* s = str;; s++) { - if (dir_name_matches(s, pattern + 1)) { - return true; - } - if (*s == '\0') { - break; - } - } - return false; - } - if (*pattern == '_') { - str++; - pattern++; - continue; - } - if (std::tolower(static_cast(*str)) != - std::tolower(static_cast(*pattern))) { - return false; - } - str++; - pattern++; - } - return *str == '\0' && *pattern == '\0'; -} - -int dir_name_search_internal(const SaveDataDirNameSearchCond* cond, - SaveDataDirNameSearchResult* result) { - State& st = state(); - if (result != nullptr) { - std::memset(result, 0, sizeof(*result)); - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (cond == nullptr || result == nullptr || - (result->dir_names_num != 0 && result->dir_names == nullptr) || - (cond->dir_name != nullptr && !bounded_string_ok(cond->dir_name->data, sizeof(cond->dir_name->data))) || - (cond->title_id != nullptr && !bounded_string_ok(cond->title_id->data, sizeof(cond->title_id->data)))) { - return SD_ERROR_PARAMETER; - } - if (cond->user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - load_aliases_locked(st); - std::vector found; - std::error_code error; - if (std::filesystem::is_directory(save_root(), error) && !error) { - for (const auto& entry : std::filesystem::directory_iterator(save_root(), error)) { - if (error) { - break; - } - std::error_code kind_error; - if (!entry.is_directory(kind_error) || kind_error) { - continue; - } - const std::string alias = entry.path().filename().string(); - if (alias.empty() || alias[0] == '.' || alias[0] == '_' || - !valid_dir_name(alias.c_str())) { - continue; - } - std::error_code meta_error; - if (!std::filesystem::is_regular_file(save_param_path(entry.path()), meta_error) || meta_error) { - continue; - } - const std::string dir_name = real_dir_name_locked(st, alias); - if (cond->dir_name != nullptr && !dir_name_matches(dir_name.c_str(), cond->dir_name->data)) { - continue; - } - found.push_back(dir_name); - } - } - std::sort(found.begin(), found.end()); - if (cond->order == 1) { - std::reverse(found.begin(), found.end()); - } - result->hit_num = static_cast(found.size()); - const std::uint32_t set_count = std::min(result->dir_names_num, found.size()); - for (std::uint32_t i = 0; i < set_count; i++) { - std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s", - found[i].c_str()); - if (result->params != nullptr) { - load_save_param(save_directory(save_alias_locked(st, found[i])), &result->params[i]); - } - if (result->infos != nullptr) { - std::memset(&result->infos[i], 0, sizeof(result->infos[i])); - std::uint64_t blocks = 0; - if (read_u64(save_blocks_path(save_directory(save_alias_locked(st, found[i]))), &blocks) == SD_OK) { - std::memcpy(&result->infos[i], &blocks, sizeof(blocks)); - } - } - } - result->set_num = set_count; - return SD_OK; -} - -int get_param_internal(const SaveDataMountPoint* mount_point, std::uint32_t param_type, - void* param_buf, std::size_t param_buf_size, std::size_t* got_size) { - State& st = state(); - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (mount_point == nullptr || param_buf == nullptr || param_type > SD_PARAM_MTIME || - !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - const int index = slot_of_mount_point_locked(st, mount_point->data); - if (index == -1) { - return SD_ERROR_NOT_MOUNTED; - } - SaveDataParam param {}; - const int status = load_save_param(st.slots[static_cast(index)].directory, ¶m); - if (status != SD_OK) { - return status; - } - std::size_t written = 0; - if (param_type == SD_PARAM_WHOLE) { - if (param_buf_size < sizeof(param)) { - return SD_ERROR_PARAMETER; - } - std::memcpy(param_buf, ¶m, sizeof(param)); - written = sizeof(param); - } else if (param_type == SD_PARAM_USER_PARAM || param_type == SD_PARAM_MTIME) { - const std::size_t size = param_type == SD_PARAM_USER_PARAM ? sizeof(param.user_param) - : sizeof(param.mtime); - if (param_buf_size < size) { - return SD_ERROR_PARAMETER; - } - std::memcpy(param_buf, - param_type == SD_PARAM_USER_PARAM - ? static_cast(¶m.user_param) - : static_cast(¶m.mtime), - size); - written = size; - } else { - char* field = param_type == SD_PARAM_TITLE ? param.title - : param_type == SD_PARAM_SUB_TITLE ? param.sub_title : param.detail; - const std::size_t capacity = param_type == SD_PARAM_DETAIL ? sizeof(param.detail) - : sizeof(param.title); - if (param_buf_size == 0) { - return SD_ERROR_PARAMETER; - } - const std::size_t length = std::min(std::strlen(field), param_buf_size - 1); - std::memcpy(param_buf, field, length); - static_cast(param_buf)[length] = '\0'; - written = length + 1; - } - if (got_size != nullptr) { - *got_size = written; - } - return SD_OK; -} - -int set_param_internal(const SaveDataMountPoint* mount_point, std::uint32_t param_type, - const void* param_buf, std::size_t param_buf_size) { - State& st = state(); - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (mount_point == nullptr || param_buf == nullptr || param_type > SD_PARAM_MTIME || - !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - const int index = slot_of_mount_point_locked(st, mount_point->data); - if (index == -1) { - return SD_ERROR_NOT_MOUNTED; - } - const std::filesystem::path directory = st.slots[static_cast(index)].directory; - if (param_type == SD_PARAM_WHOLE) { - if (param_buf_size < sizeof(SaveDataParam)) { - return SD_ERROR_PARAMETER; - } - SaveDataParam param = *static_cast(param_buf); - param.mtime = static_cast(std::time(nullptr)); - return write_blob(save_param_path(directory), ¶m, sizeof(param)) ? SD_OK : SD_ERROR_INTERNAL; - } - SaveDataParam param {}; - const int status = load_save_param(directory, ¶m); - if (status != SD_OK && status != SD_ERROR_NOT_FOUND) { - return status; - } - if (param_type == SD_PARAM_USER_PARAM || param_type == SD_PARAM_MTIME) { - const std::size_t size = param_type == SD_PARAM_USER_PARAM ? sizeof(param.user_param) - : sizeof(param.mtime); - if (param_buf_size < size) { - return SD_ERROR_PARAMETER; - } - std::memcpy(param_type == SD_PARAM_USER_PARAM ? static_cast(¶m.user_param) - : static_cast(¶m.mtime), - param_buf, size); - } else { - char* field = param_type == SD_PARAM_TITLE ? param.title - : param_type == SD_PARAM_SUB_TITLE ? param.sub_title : param.detail; - const std::size_t capacity = param_type == SD_PARAM_DETAIL ? sizeof(param.detail) - : sizeof(param.title); - if (param_buf_size == 0) { - return SD_ERROR_PARAMETER; - } - const std::size_t length = std::min(param_buf_size, capacity - 1); - const char* text = static_cast(param_buf); - const char* end = static_cast(std::memchr(text, '\0', length)); - std::memset(field, 0, capacity); - std::memcpy(field, text, end != nullptr ? static_cast(end - text) : length); - } - return write_blob(save_param_path(directory), ¶m, sizeof(param)) ? SD_OK : SD_ERROR_INTERNAL; -} - -int get_mount_info_internal(const SaveDataMountPoint* mount_point, SaveDataMountInfo* info) { - State& st = state(); - if (info != nullptr) { - std::memset(info, 0, sizeof(*info)); - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (mount_point == nullptr || info == nullptr || - !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - const int index = slot_of_mount_point_locked(st, mount_point->data); - if (index == -1) { - return SD_ERROR_NOT_MOUNTED; - } - std::uint64_t blocks = 0; - const int status = read_u64(save_blocks_path(st.slots[static_cast(index)].directory), - &blocks); - if (status != SD_OK) { - return status; - } - info->blocks = blocks; - info->free_blocks = blocks; - return SD_OK; -} - -int is_mounted_internal(const SaveDataMountPoint* mount_point, std::uint32_t* is_mounted) { - if (mount_point == nullptr || is_mounted == nullptr || - !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { - return SD_ERROR_PARAMETER; - } - State& st = state(); - std::lock_guard lock(st.mutex); - *is_mounted = slot_of_mount_point_locked(st, mount_point->data) != -1 ? 1u : 0u; - return SD_OK; -} - -std::int64_t count_mounted_internal() { - State& st = state(); - std::lock_guard lock(st.mutex); - std::int64_t count = 0; - for (const auto& slot : st.slots) { - if (slot.used) { - count++; - } - } - return count; -} - -std::string memory_key(std::int32_t user_id, std::uint32_t slot_id) { - char key[32]; - std::snprintf(key, sizeof(key), "%d:%u", static_cast(user_id), static_cast(slot_id)); - return key; -} - -std::filesystem::path memory_directory(std::int32_t user_id, std::uint32_t slot_id) { - char name[48]; - std::snprintf(name, sizeof(name), "%s%s", SD_MEMORY_PREFIX, - slot_id == 0 ? "" : std::to_string(slot_id).c_str()); - return save_root() / name / std::to_string(static_cast(user_id)); -} - -bool valid_memory_range(const SaveDataMemoryData& data, std::size_t size) { - return data.buf != nullptr && data.offset <= size && data.buf_size <= size - data.offset; -} - -int setup_memory_internal(const SaveDataMemorySetup2* setup, SaveDataMemorySetupResult* result, - KernelEventFlag flag) { - State& st = state(); - if (setup == nullptr || setup->slot_id >= SD_MEMORY_SLOTS || setup->memory_size == 0 || - setup->memory_size > SD_MEMORY_MAX_SIZE || (setup->option & ~(SD_MEMORY_SET_PARAM | SD_MEMORY_DOUBLE_BUFFER)) != 0 || - (setup->init_param != nullptr && setup->option != 0 && (setup->option & SD_MEMORY_SET_PARAM) == 0)) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (setup->user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - const std::string key = memory_key(setup->user_id, setup->slot_id); - if (st.memory.count(key) != 0) { - return SD_ERROR_BUSY; - } - if (st.memory.size() >= SD_MEMORY_MAX_MOUNTED) { - return SD_ERROR_LIMITATION_OVER; - } - if (save_is_mounted_locked(st, memory_directory(setup->user_id, setup->slot_id))) { - return SD_ERROR_BUSY; - } - MemorySlot slot; - slot.option = setup->option; - slot.data.resize(setup->memory_size, 0); - std::strncpy(slot.param.title, "Saved Data", sizeof(slot.param.title) - 1); - std::size_t existed = 0; - std::error_code error; - const std::filesystem::path blob = memory_directory(setup->user_id, setup->slot_id) / "memory.dat"; - if (std::filesystem::is_regular_file(blob, error) && !error) { - const std::size_t stored = static_cast(std::filesystem::file_size(blob, error)); - if (error || stored > SD_MEMORY_MAX_SIZE) { - return SD_ERROR_BROKEN; - } - existed = stored; - std::vector loaded(stored, 0); - std::ifstream file(blob, std::ios::binary); - file.read(loaded.data(), static_cast(stored)); - if (!file) { - return SD_ERROR_BROKEN; - } - std::copy_n(loaded.begin(), std::min(stored, slot.data.size()), slot.data.begin()); - load_save_param(memory_directory(setup->user_id, setup->slot_id), &slot.param); - } - if (existed == 0 && setup->init_param != nullptr && (setup->option & SD_MEMORY_SET_PARAM) != 0) { - slot.param = *setup->init_param; - slot.param.mtime = 0; - } - st.memory.emplace(key, std::move(slot)); - if (result != nullptr) { - std::memset(result, 0, sizeof(*result)); - result->existed_memory_size = existed; - } - if (flag != nullptr) { - queue_event_locked(st, SD_EVENT_MEMORY_SYNC_END, setup->user_id, nullptr, nullptr, SD_OK, flag); - } - return SD_OK; -} - -int get_memory_internal(SaveDataMemoryGet2* get_param) { - State& st = state(); - if (get_param == nullptr || get_param->slot_id >= SD_MEMORY_SLOTS) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (get_param->user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - const auto it = st.memory.find(memory_key(get_param->user_id, get_param->slot_id)); - if (it == st.memory.end()) { - return SD_ERROR_MEMORY_NOT_READY; - } - const MemorySlot& slot = it->second; - if (get_param->data != nullptr && - !valid_memory_range(*get_param->data, slot.data.size())) { - return SD_ERROR_PARAMETER; - } - if (get_param->param != nullptr && (slot.option & SD_MEMORY_SET_PARAM) == 0) { - return SD_ERROR_PARAMETER; - } - if (get_param->data != nullptr) { - const SaveDataMemoryData& data = *get_param->data; - std::memcpy(data.buf, slot.data.data() + data.offset, data.buf_size); - } - if (get_param->param != nullptr) { - *get_param->param = slot.param; - } - if (get_param->icon != nullptr) { - get_param->icon->data_size = 0; - } - return SD_OK; -} - -int set_memory_internal(const SaveDataMemorySet2* set_param) { - State& st = state(); - if (set_param == nullptr || set_param->slot_id >= SD_MEMORY_SLOTS || - set_param->data_num > SD_MEMORY_MAX_DATA_NUM || - (set_param->data == nullptr && set_param->data_num != 0)) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (set_param->user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - const auto it = st.memory.find(memory_key(set_param->user_id, set_param->slot_id)); - if (it == st.memory.end()) { - return SD_ERROR_MEMORY_NOT_READY; - } - if (set_param->param != nullptr && (it->second.option & SD_MEMORY_SET_PARAM) == 0) { - return SD_ERROR_PARAMETER; - } - const std::uint32_t count = - set_param->data != nullptr && set_param->data_num == 0 ? 1u : set_param->data_num; - const SaveDataMemoryData* descriptors = set_param->data; - for (std::uint32_t i = 0; i < count; i++) { - if (!valid_memory_range(descriptors[i], it->second.data.size())) { - return SD_ERROR_PARAMETER; - } - } - MemorySlot updated = it->second; - for (std::uint32_t i = 0; i < count; i++) { - const SaveDataMemoryData& data = descriptors[i]; - std::memcpy(updated.data.data() + data.offset, data.buf, data.buf_size); - } - if (set_param->param != nullptr) { - updated.param = *set_param->param; - } - updated.param.mtime = static_cast(std::time(nullptr)); - updated.dirty = true; - it->second = std::move(updated); - return SD_OK; -} - -int sync_memory_internal(const std::int32_t user_id, const std::uint32_t slot_id, - const std::uint32_t option, KernelEventFlag flag, bool report_event) { - State& st = state(); - if (slot_id >= SD_MEMORY_SLOTS || option > 1) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - const auto it = st.memory.find(memory_key(user_id, slot_id)); - if (it == st.memory.end()) { - return SD_ERROR_MEMORY_NOT_READY; - } - const std::filesystem::path directory = memory_directory(user_id, slot_id); - int status = SD_OK; - if (!write_blob(directory / "memory.dat", it->second.data.data(), it->second.data.size())) { - status = SD_ERROR_INTERNAL; - } else if (!write_blob(save_param_path(directory), &it->second.param, sizeof(it->second.param))) { - status = SD_ERROR_INTERNAL; - } else { - it->second.dirty = false; - } - if (option == 1 || !report_event) { - return status; - } - char title_text[sizeof(SceSaveDataTitleId)] = {}; - char dir_text[sizeof(SceSaveDataDirName)] = {}; - std::snprintf(dir_text, sizeof(dir_text), "%s%s", SD_MEMORY_PREFIX, - slot_id == 0 ? "" : std::to_string(slot_id).c_str()); - SceSaveDataTitleId title {}; - SceSaveDataDirName directory_name {}; - std::memcpy(title.data, title_text, sizeof(title.data)); - std::memcpy(directory_name.data, dir_text, sizeof(directory_name.data)); - queue_event_locked(st, SD_EVENT_MEMORY_SYNC_END, user_id, &title, &directory_name, status, flag); - return SD_OK; -} - -int commit_internal(const SaveDataCommitParam* param) { - State& st = state(); - if (param == nullptr) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - std::lock_guard lock(st.mutex); - for (auto& [key, slot] : st.memory) { - if (!slot.dirty) { - continue; - } - const std::size_t sep = key.find(':'); - const std::int32_t user_id = static_cast(std::strtol(key.c_str(), nullptr, 10)); - const std::uint32_t slot_id = sep == std::string::npos - ? 0u - : static_cast(std::strtoul(key.c_str() + sep + 1, nullptr, 10)); - const std::filesystem::path directory = memory_directory(user_id, slot_id); - if (!write_blob(directory / "memory.dat", slot.data.data(), slot.data.size()) || - !write_blob(save_param_path(directory), &slot.param, sizeof(slot.param))) { - return SD_ERROR_INTERNAL; - } - slot.dirty = false; - } - if ((param->commit_mode & SD_COMMIT_BACKUP_ASYNC) != 0) { - queue_event_locked(st, SD_EVENT_COMMIT_BACKUP_END, 0, nullptr, nullptr, SD_OK, nullptr); - } - return SD_OK; -} - -int prepare_internal(const SaveDataMountPoint* mount_point, const SaveDataPrepareParam* param) { - if (mount_point == nullptr || param == nullptr || - !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - return SD_OK; -} - -int backup_internal(const SaveDataBackup* backup, KernelEventFlag flag) { - State& st = state(); - if (backup == nullptr || backup->dir_name == nullptr || - !bounded_string_ok(backup->dir_name->data, sizeof(backup->dir_name->data))) { - return SD_ERROR_PARAMETER; - } - if (backup->title_id != nullptr && !bounded_string_ok(backup->title_id->data, sizeof(backup->title_id->data))) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (backup->user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - std::lock_guard lock(st.mutex); - queue_event_locked(st, SD_EVENT_BACKUP_END, backup->user_id, backup->title_id, backup->dir_name, - SD_OK, flag); - return SD_OK; -} - -int transferring_mount_internal(const SaveDataTransferringMount* mount, - SaveDataMountResult* mount_result) { - if (mount_result != nullptr) { - std::memset(mount_result, 0, sizeof(*mount_result)); - } - State& st = state(); - if (mount == nullptr || mount_result == nullptr || mount->title_id == nullptr || - mount->dir_name == nullptr || - !bounded_string_ok(mount->title_id->data, sizeof(mount->title_id->data)) || - !bounded_string_ok(mount->dir_name->data, sizeof(mount->dir_name->data))) { - return SD_ERROR_PARAMETER; - } - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - if (mount->user_id < 0) { - return SD_ERROR_INVALID_LOGIN_USER; - } - if (!valid_dir_name(mount->title_id->data) || !valid_dir_name(mount->dir_name->data)) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - const std::filesystem::path directory = save_root() / "transfer" / mount->title_id->data / - mount->dir_name->data; - std::error_code error; - if (!std::filesystem::is_directory(directory, error) || error) { - return SD_ERROR_NOT_FOUND; - } - const int index = free_slot_locked(st); - if (index == -1) { - return SD_ERROR_MOUNT_FULL; - } - for (const auto& slot : st.slots) { - if (slot.used && slot.directory == directory) { - return SD_ERROR_BUSY; - } - } - const std::string mount_point = mount_point_for(save_alias_locked( - st, std::string(mount->title_id->data) + "-" + mount->dir_name->data)); - if (mount_point.size() + 1 > sizeof(mount_result->mount_point)) { - return SD_ERROR_PARAMETER; - } - MountSlot& slot = st.slots[static_cast(index)]; - slot.used = true; - slot.mount_point = mount_point; - slot.dir_name = mount->dir_name->data; - slot.directory = directory; - std::memcpy(mount_result->mount_point.data, mount_point.c_str(), mount_point.size() + 1); - mount_result->required_blocks = 0; - mount_result->mount_status = 0; - return SD_OK; -} - -int get_event_result_internal(const void* event_param, SaveDataEvent* event) { - (void)event_param; - State& st = state(); - if (event == nullptr) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - if (st.events.empty()) { - return SD_ERROR_NOT_FOUND; - } - const PendingEvent pending = st.events.front(); - st.events.pop_front(); - if (pending.flag != nullptr) { - sceKernelDeleteEventFlag(pending.flag); - } - *event = pending.record; - return event->error_code; -} - -int cancel_internal(KernelEventFlag event) { - State& st = state(); - if (event == nullptr) { - return SD_ERROR_PARAMETER; - } - std::lock_guard lock(st.mutex); - for (auto it = st.events.begin(); it != st.events.end(); ++it) { - if (it->flag == event) { - sceKernelCancelEventFlag(event, 0, nullptr); - const KernelEventFlag flag = it->flag; - st.events.erase(it); - sceKernelDeleteEventFlag(flag); - return SD_OK; - } - } - return SD_ERROR_NOT_FOUND; -} - -int abort_internal() { - State& st = state(); - std::lock_guard lock(st.mutex); - for (auto& pending : st.events) { - if (pending.flag != nullptr) { - sceKernelCancelEventFlag(pending.flag, 0, nullptr); - sceKernelDeleteEventFlag(pending.flag); - pending.flag = nullptr; - } - } - st.events.clear(); - return SD_OK; -} - -int create_transaction_resource_internal(std::uint32_t size) { - State& st = state(); - std::lock_guard lock(st.mutex); - if (st.next_transaction_resource <= 0) { - return SD_ERROR_OUT_OF_MEMORY; - } - const std::int32_t id = st.next_transaction_resource++; - st.live_transaction_resources.insert(id); - return id; -} - -int delete_transaction_resource_internal(std::int32_t resource) { - State& st = state(); - std::lock_guard lock(st.mutex); - return st.live_transaction_resources.erase(resource) != 0 ? SD_OK : SD_ERROR_PARAMETER; -} - -std::filesystem::path app_status_path(const std::string& dir_name) { - return save_root() / "_app_status" / dir_name; -} - -int get_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, - const SceSaveDataTitleId* title_id, - std::uint32_t* status) { - if (status == nullptr || dir_name == nullptr || - !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || - (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data)))) { - return SD_ERROR_PARAMETER; - } - State& st = state(); - std::lock_guard lock(st.mutex); - *status = SD_APP_STATUS_NONE; - std::error_code error; - const std::filesystem::path path = app_status_path(dir_name->data); - if (std::filesystem::is_regular_file(path, error) && !error) { - std::uint32_t stored = 0; - std::ifstream file(path, std::ios::binary); - file.read(reinterpret_cast(&stored), sizeof(stored)); - if (!file) { - return SD_ERROR_BROKEN; - } - *status = stored; - return SD_OK; - } - const std::string alias = save_alias_locked(st, dir_name->data); - if (std::filesystem::is_directory(save_directory(alias), error) && !error) { - *status = SD_APP_STATUS_LOCAL; - return SD_OK; - } - *status = SD_APP_STATUS_NONE; - return SD_OK; -} - -int set_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, - const SceSaveDataTitleId* title_id, - std::uint32_t status) { - if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || - (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data))) || - (status & ~(SD_APP_STATUS_DELETED | SD_APP_STATUS_LOCAL | SD_APP_STATUS_ONLINE)) != 0) { - return SD_ERROR_PARAMETER; - } - std::error_code error; - if (std::filesystem::create_directories(app_status_path(dir_name->data).parent_path(), error)) - RecordWrittenPath_nid_no_patch(app_status_path(dir_name->data).parent_path()); - if (error) { - return SD_ERROR_INTERNAL; - } - return write_blob(app_status_path(dir_name->data), &status, sizeof(status)) ? SD_OK - : SD_ERROR_INTERNAL; -} - -int delete_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, - const SceSaveDataTitleId* title_id) { - if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || - (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data)))) { - return SD_ERROR_PARAMETER; - } - std::error_code error; - if (std::filesystem::remove(app_status_path(dir_name->data), error)) - RecordWrittenPath_nid_no_patch(app_status_path(dir_name->data)); - return error ? SD_ERROR_INTERNAL : SD_OK; -} - -int check_internal() { - if (!is_ready()) { - return SD_ERROR_NOT_INITIALIZED; - } - std::error_code error; - return ensure_save_root(error) ? SD_OK : SD_ERROR_INTERNAL; -} - -std::uint64_t get_all_size_internal(std::int32_t user_id) { - State& st = state(); - std::lock_guard lock(st.mutex); - std::uint64_t total = 0; - for (const auto& [key, slot] : st.memory) { - total += slot.data.size() * ((slot.option & SD_MEMORY_DOUBLE_BUFFER) != 0 ? 2u : 1u); - } - return total; -} - -} // namespace -} // namespace savedata - -#endif diff --git a/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt b/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt index ddbc456db5..6c5c408051 100644 --- a/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveDataDialog.native/CMakeLists.txt @@ -1,6 +1,5 @@ -add_library(libSceSaveDataDialog.native SHARED EXCLUDE_FROM_ALL Export.cpp) -target_include_directories(libSceSaveDataDialog.native PRIVATE ${LIBS_INCLUDE_DIR}) -configure_windows_unwind(libSceSaveDataDialog.native) +include(${CMAKE_CURRENT_SOURCE_DIR}/SaveDataDialogLibrary.cmake) +add_sce_savedata_dialog_library(libSceSaveDataDialog.native) if(BUILD_TESTING) add_test_executable(guest_savedata_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataDialog.cpp) diff --git a/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialogLibrary.cmake b/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialogLibrary.cmake new file mode 100644 index 0000000000..f4f82ce925 --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog.native/SaveDataDialogLibrary.cmake @@ -0,0 +1,5 @@ +function(add_sce_savedata_dialog_library target) + add_library(${target} SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Export.cpp) + target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR}) + configure_windows_unwind(${target}) +endfunction() diff --git a/core/libs/prx/libSceSaveDataDialog/CMakeLists.txt b/core/libs/prx/libSceSaveDataDialog/CMakeLists.txt new file mode 100644 index 0000000000..1b23415ac8 --- /dev/null +++ b/core/libs/prx/libSceSaveDataDialog/CMakeLists.txt @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceSaveDataDialog.native/SaveDataDialogLibrary.cmake) +add_sce_savedata_dialog_library(libSceSaveDataDialog) diff --git a/core/libs/prx/libSceSaveDataDialog/Export.cpp b/core/libs/prx/libSceSaveDataDialog/Export.cpp deleted file mode 100644 index 1ad8a50883..0000000000 --- a/core/libs/prx/libSceSaveDataDialog/Export.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "prx/libSceSaveDataDialog/include/Api.hpp" -#include -#include -#include -#include -#include "SceTypes.hpp" -#include "prx/libSceSaveDataDialog.native/SaveDataDialog.hpp" -#include "prx/libc/include/General.hpp" - -namespace { -// Throwing from these reaches std::terminate and aborts the game process. -int SoftFailNp(const char* func) { - std::fprintf(stderr, "[SOFT-NP] %s -> 0 (unimplemented SCE API - no-op)\n", func); - std::fflush(stderr); - return 0; -} -} // namespace - -extern "C" { - - int APS5_VABI sceSaveDataDialogClose(const void* close_param) { - (void)close_param; - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogGetResult(void* result) { - // Never return 0 with an untouched out-struct: the game would assert on the garbage later. - if (result == nullptr) { - return SAVE_DATA_DIALOG_ERROR_ARG_NULL; - } - auto* r = static_cast(result); - std::memset(r, 0, sizeof(*r)); - r->result = SAVE_DATA_DIALOG_RESULT_OK; - r->button_id = SAVE_DATA_DIALOG_BUTTON_ID_OK; - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogInitialize(void) { - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogIsReadyToDisplay(void) { - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogOpen(const void* param) { - (void)param; - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogProgressBarInc(int target, uint32_t delta) { - (void)target; - (void)delta; - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogProgressBarSetValue(int target, uint32_t rate) { - (void)target; - (void)rate; - return SoftFailNp(__func__); - } - - int APS5_VABI sceSaveDataDialogTerminate(void) { - return SoftFailNp(__func__); - } - -} diff --git a/core/libs/prx/libSceSsl/CMakeLists.txt b/core/libs/prx/libSceSsl/CMakeLists.txt index cf5b24b014..bd7f27b9f7 100644 --- a/core/libs/prx/libSceSsl/CMakeLists.txt +++ b/core/libs/prx/libSceSsl/CMakeLists.txt @@ -87,6 +87,16 @@ if(BUILD_TESTING) add_test(NAME guest_ssl_transport_${table} COMMAND guest_ssl_transport_tests ${table}) set_tests_properties(guest_ssl_transport_${table} PROPERTIES TIMEOUT 30) endforeach() + add_test_executable(guest_ssl_system_roots_tests ${LIBS_INCLUDE_DIR}/tests/GuestSslSystemRoots.cpp) + target_include_directories(guest_ssl_system_roots_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ssl_system_roots_tests PRIVATE libSceSsl libkernel libc mbedtls mbedx509 mbedcrypto) + configure_windows_unwind(guest_ssl_system_roots_tests) + foreach(list IN ITEMS system context empty blank) + set(sslSystemRootsRun ${CMAKE_CURRENT_BINARY_DIR}/guest_ssl_system_roots_${list}) + file(MAKE_DIRECTORY ${sslSystemRootsRun}) + add_test(NAME guest_ssl_system_roots_${list} COMMAND guest_ssl_system_roots_tests ${list} WORKING_DIRECTORY ${sslSystemRootsRun}) + set_tests_properties(guest_ssl_system_roots_${list} PROPERTIES TIMEOUT 30) + endforeach() add_test_executable(guest_ssl_ps5_readers_tests ${LIBS_INCLUDE_DIR}/tests/GuestSslPs5Readers.cpp) target_include_directories(guest_ssl_ps5_readers_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_ssl_ps5_readers_tests PRIVATE libSceSsl libc) @@ -94,14 +104,16 @@ if(BUILD_TESTING) add_test(NAME guest_ssl_ps5_readers COMMAND guest_ssl_ps5_readers_tests) add_test_executable(guest_ssl_context_lifetime_tests ${LIBS_INCLUDE_DIR}/tests/SslContextLifetime.cpp) target_include_directories(guest_ssl_context_lifetime_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_ssl_context_lifetime_tests PRIVATE libc mbedtls mbedx509 mbedcrypto) + target_link_libraries(guest_ssl_context_lifetime_tests PRIVATE libkernel libc mbedtls mbedx509 mbedcrypto) configure_windows_unwind(guest_ssl_context_lifetime_tests) add_test(NAME guest_ssl_context_lifetime COMMAND guest_ssl_context_lifetime_tests) set_tests_properties(guest_ssl_context_lifetime PROPERTIES TIMEOUT 30) if(WIN32) target_link_libraries(guest_ssl_transport_tests PRIVATE bcrypt ws2_32) target_link_libraries(guest_ssl_context_lifetime_tests PRIVATE bcrypt ws2_32) - set_tests_properties(guest_ssl guest_ssl_load guest_ssl_ps5_readers guest_ssl_context_lifetime guest_ssl_transport_kernel guest_ssl_transport_net PROPERTIES ENVIRONMENT_MODIFICATION + target_link_libraries(guest_ssl_system_roots_tests PRIVATE bcrypt ws2_32) + set_tests_properties(guest_ssl guest_ssl_load guest_ssl_ps5_readers guest_ssl_context_lifetime guest_ssl_transport_kernel guest_ssl_transport_net + guest_ssl_system_roots_system guest_ssl_system_roots_context guest_ssl_system_roots_empty guest_ssl_system_roots_blank PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceSsl/Export.cpp b/core/libs/prx/libSceSsl/Export.cpp index c8a56dc09d..bae126e949 100644 --- a/core/libs/prx/libSceSsl/Export.cpp +++ b/core/libs/prx/libSceSsl/Export.cpp @@ -9,7 +9,9 @@ #include "SslTypes.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" +#include "prx/libkernel/include/Api.hpp" #include #include @@ -27,6 +29,7 @@ #include #include +#include #include #include #include @@ -185,6 +188,47 @@ struct CertificateChain { ~CertificateChain() { mbedtls_x509_crt_free(&root); } }; +Pool systemRootPool{std::numeric_limits::max() / 2}; + +std::string SystemCaListPath() { + const char* const word = sceKernelGetFsSandboxRandomWord(); + if (word == nullptr) throw std::runtime_error("SSL: the sandbox word for the system CA list is not available"); + return std::string("/") + word + "/common/cert/CA_LIST.cer"; +} + +std::string ReadSystemCaList(const std::string& path) { + const int descriptor = sceKernelOpen(path.c_str(), 0, 0); + if (descriptor < 0) throw std::runtime_error("SSL: cannot open the system CA list " + path); + std::string text; + std::array buffer{}; + for (;;) { + const auto count = sceKernelRead(descriptor, buffer.data(), buffer.size()); + if (count < 0) { + sceKernelClose(descriptor); + throw std::runtime_error("SSL: reading the system CA list failed"); + } + if (count == 0) break; + text.append(buffer.data(), static_cast(count)); + } + sceKernelClose(descriptor); + return text; +} + +const mbedtls_x509_crt* SystemRoots() { + static const std::unique_ptr roots = [] { + const auto path = SystemCaListPath(); + const auto text = ReadSystemCaList(path); + if (text.empty()) throw std::runtime_error("SSL: the system CA list " + path + " is empty"); + auto chain = std::make_unique(); + PoolScope scope(systemRootPool); + const int result = mbedtls_x509_crt_parse(&chain->root, reinterpret_cast(text.c_str()), text.size() + 1); + if (result < 0 && currentPoolExhausted) throw std::bad_alloc(); + if (result < 0 || !chain->root.raw.p) throw std::runtime_error("SSL: the system CA list " + path + " has no certificate"); + return chain; + }(); + return &roots->root; +} + struct PrivateKey { mbedtls_pk_context key; PrivateKey() { mbedtls_pk_init(&key); } @@ -317,6 +361,7 @@ struct Session { Random random; PrivateKey privateKey; mbedtls_x509_crt authorities; + mbedtls_x509_crt* linkedSystemRoots = nullptr; mbedtls_ssl_config config; mbedtls_ssl_context ssl; @@ -331,20 +376,29 @@ struct Session { PoolScope scope(context->pool); mbedtls_ssl_free(&ssl); mbedtls_ssl_config_free(&config); + if (linkedSystemRoots) linkedSystemRoots->next = nullptr; mbedtls_x509_crt_free(&authorities); } - void Configure(const std::string& host, const std::string& alpn, std::uint32_t options) { + void Configure(const std::string& host, const std::string& alpn, std::uint32_t options, const mbedtls_x509_crt* systemRoots = nullptr) { verifyOptions = options; random.Seed(); for (const auto& chain : context->authorities) for (const auto* entry = &chain->root; entry; entry = entry->next) CheckBackend(mbedtls_x509_crt_parse_der(&authorities, entry->raw.p, entry->raw.len), "SSL CA certificate copy failed"); + auto* trusted = &authorities; + if (systemRoots && !authorities.raw.p) { + trusted = const_cast(systemRoots); + } else if (systemRoots) { + linkedSystemRoots = &authorities; + while (linkedSystemRoots->next) linkedSystemRoots = linkedSystemRoots->next; + linkedSystemRoots->next = const_cast(systemRoots); + } CheckBackend(mbedtls_ssl_config_defaults(&config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT), "SSL client configuration failed"); mbedtls_ssl_conf_authmode(&config, MBEDTLS_SSL_VERIFY_OPTIONAL); mbedtls_ssl_conf_min_tls_version(&config, MBEDTLS_SSL_VERSION_TLS1_2); - mbedtls_ssl_conf_ca_chain(&config, &authorities, nullptr); + mbedtls_ssl_conf_ca_chain(&config, trusted, nullptr); mbedtls_ssl_conf_rng(&config, mbedtls_ctr_drbg_random, &random.drbg); if (identity) { if (!identity->chain || !identity->key) throw std::logic_error("Incomplete SSL client identity is not supported"); @@ -480,6 +534,7 @@ struct SslConnection { int minimumVersion = SslVersionTls12; std::string alpn; std::shared_ptr session; + bool systemRoots = false; }; struct Runtime { @@ -834,6 +889,16 @@ int APS5_VABI sceSslCreateConnection(int sslCtxId, int sockId, const char* host) return id; } +int SslTrustSystemRoots_nid_no_patch(int sslConnectionId) { + SystemRoots(); + std::lock_guard lock(State().mutex); + auto* connection = FindConnection(sslConnectionId); + if (!connection) return ErrorInvalidId; + if (connection->session) throw std::logic_error("SslTrustSystemRoots_nid_no_patch: the connection has already started"); + connection->systemRoots = true; + return 0; +} + int APS5_VABI sceSslDeleteConnection(int sslConnectionId) { std::shared_ptr session; std::lock_guard lock(State().mutex); @@ -882,7 +947,7 @@ int Establish(const char* function, int sslConnectionId, std::shared_ptrsocket, &owner, &native)) return ErrorNetBadDescriptor; PoolScope scope(context->pool); session = std::make_shared(connection->context, std::move(context), std::move(owner), native); - session->Configure(connection->host, connection->alpn, connection->verifyOptions); + session->Configure(connection->host, connection->alpn, connection->verifyOptions, connection->systemRoots ? SystemRoots() : nullptr); connection->session = session; } else { session = connection->session; diff --git a/core/libs/prx/libSceSystemLogger2Game/Export.cpp b/core/libs/prx/libSceSystemLogger2Game/Export.cpp index d81f4a5e32..c7f642733d 100644 --- a/core/libs/prx/libSceSystemLogger2Game/Export.cpp +++ b/core/libs/prx/libSceSystemLogger2Game/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceSystemLogger2Game/include/InventoryExports.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,15 @@ extern "C" { +int APS5_VABI sceSystemLogger2GameGetTimestamp() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceSystemLogger2GameSetEvent() { + NotImplemented_nid_no_patch(__func__); +} + + APS5_DUMMY_FUN } diff --git a/core/libs/prx/libSceSystemLogger2Game/include/InventoryExports.hpp b/core/libs/prx/libSceSystemLogger2Game/include/InventoryExports.hpp new file mode 100644 index 0000000000..d91ba52862 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Game/include/InventoryExports.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSystemLogger2GameGetTimestamp(); +int APS5_VABI sceSystemLogger2GameSetEvent(); + +} diff --git a/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp b/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp index 6f70f09bee..7948b1dc9d 100644 --- a/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp +++ b/core/libs/prx/libSceSystemLogger2Game/libSceSystemLogger2Game.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceSystemLogger2Game/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 7d8a4c65d6..9e0190d6ad 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(libSceSystemService SHARED EXCLUDE_FROM_ALL Export.cpp PendingExports.cpp LoadExec.cpp) target_include_directories(libSceSystemService PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceSystemService PRIVATE libSceAgcDriver) +target_link_libraries(libSceSystemService PRIVATE libSceAgcDriver libSceUsbStorage) configure_windows_unwind(libSceSystemService) if(BUILD_TESTING) @@ -22,7 +22,7 @@ if(BUILD_TESTING) configure_windows_unwind(guest_loadexec_host) find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_FOUND) - set(loadexecLibraries $ $ $ $) + set(loadexecLibraries $ $ $ $ $) if(APS5_ENABLE_TRACY) list(APPEND loadexecLibraries $) endif() @@ -99,6 +99,15 @@ if(BUILD_TESTING) set_tests_properties(guest_system_service_time_zone PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_system_service_usb_storage_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceUsbStorage.cpp) + target_include_directories(guest_system_service_usb_storage_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_system_service_usb_storage_tests PRIVATE libSceSystemService libSceUsbStorage libc) + configure_windows_unwind(guest_system_service_usb_storage_tests) + add_test(NAME guest_system_service_usb_storage COMMAND guest_system_service_usb_storage_tests) + if(WIN32) + set_tests_properties(guest_system_service_usb_storage PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_system_service_app_title_id_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceAppTitleId.cpp) target_include_directories(guest_system_service_app_title_id_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_service_app_title_id_tests PRIVATE libSceSystemService libkernel libc) diff --git a/core/libs/prx/libSceSystemService/PendingExports.cpp b/core/libs/prx/libSceSystemService/PendingExports.cpp index bfa41fa26c..21179c6118 100644 --- a/core/libs/prx/libSceSystemService/PendingExports.cpp +++ b/core/libs/prx/libSceSystemService/PendingExports.cpp @@ -2157,58 +2157,47 @@ int APS5_VABI sceSystemServiceTurnOffScreenSaver() { } int APS5_VABI sceSystemServiceUsbStorageGetDeviceInfo(std::uint32_t device, UsbStorageDeviceInfo* info) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageGetDeviceInfo(device, info); } int APS5_VABI sceSystemServiceUsbStorageGetDeviceList(std::uint32_t* devices, std::int32_t* count) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageGetDeviceList(devices, count); } int APS5_VABI sceSystemServiceUsbStorageGetdentsClose(std::int32_t fd) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageGetdentsClose(fd); } int APS5_VABI sceSystemServiceUsbStorageGetdentsOpen(std::uint32_t device, std::int32_t* fd, std::uint64_t* bufferSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageGetdentsOpen(device, fd, bufferSize); } int APS5_VABI sceSystemServiceUsbStorageGetdentsRead(std::int32_t fd, void* buffer, std::uint64_t bufferSize, std::uint64_t* bytesRead) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageGetdentsRead(fd, buffer, bufferSize, bytesRead); } int APS5_VABI sceSystemServiceUsbStorageInit(void* attr) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageInit(attr); } bool APS5_VABI sceSystemServiceUsbStorageIsExist(std::uint32_t device, const char* directory, std::uint8_t* exists) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageIsExist(device, directory, exists); } int APS5_VABI sceSystemServiceUsbStorageRegisterCallback(std::int32_t eventType, UsbStorageCallback callback, void* arg) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageRegisterCallback(eventType, callback, arg); } int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageRegisterCallbackForMapAvailable(device, directory, callback, arg); } int APS5_VABI sceSystemServiceUsbStorageRegisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback, void* arg) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageRegisterCallbackForMapUnavailable(device, directory, callback, arg); } int APS5_VABI sceSystemServiceUsbStorageRequestMap(std::uint32_t device, const char* directory, std::int32_t flags, std::uint64_t volumeSize, char* mountPoint, std::uint64_t* mountId) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageRequestMap(device, directory, flags, volumeSize, mountPoint, mountId); } int APS5_VABI sceSystemServiceUsbStorageRequestMapWSB(std::uint32_t device, const char* directory, std::int32_t flags, std::uint64_t volumeSize, char* mountPoint, std::uint64_t* mountId) { @@ -2217,28 +2206,23 @@ int APS5_VABI sceSystemServiceUsbStorageRequestMapWSB(std::uint32_t device, cons } int APS5_VABI sceSystemServiceUsbStorageRequestUnmap(std::uint32_t device, const char* directory) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageRequestUnmap(device, directory); } int APS5_VABI sceSystemServiceUsbStorageTerm() { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageTerm(); } int APS5_VABI sceSystemServiceUsbStorageUnregisterCallback(std::int32_t eventType, UsbStorageCallback callback) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageUnregisterCallback(eventType, callback); } int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapAvailable(std::uint32_t device, const char* directory, UsbStorageCallback callback) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageUnregisterCallbackForMapAvailable(device, directory, callback); } int APS5_VABI sceSystemServiceUsbStorageUnregisterCallbackForMapUnavailable(std::uint32_t device, const char* directory, UsbStorageCallback callback) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceUsbStorageUnregisterCallbackForMapUnavailable(device, directory, callback); } int APS5_VABI sceSystemStateMgrCancelShutdownTimer() { diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index fa8edb8b62..07a31f1f56 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -296,12 +296,15 @@ int APS5_VABI sceUserServiceTerminate(void) { return USER_SERVICE_OK; } -// No PSN account exists, so the platform privacy setting reports the feature as not permitted. int APS5_VABI sceUserServiceGetPlatformPrivacyWs1(int32_t user_id, int32_t* value) { - (void)user_id; - if (!value) return USER_SERVICE_ERROR_INVALID_ARGUMENT; + if (!IsUserLoggedIn(user_id)) { + return USER_SERVICE_ERROR_NOT_LOGGED_IN; + } + if (value == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } *value = 0; - return 0; + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetForegroundUser(int* user_id) { diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index ae318bdedc..1bbb8968bb 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -233,6 +233,19 @@ if(BUILD_TESTING) endif() endif() +if(BUILD_TESTING) + add_test_executable(guest_video_out_event_id_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutEventId.cpp) + target_include_directories(guest_video_out_event_id_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_video_out_event_id_tests PRIVATE libSceVideoOut libkernel libc) + configure_windows_unwind(guest_video_out_event_id_tests) + add_test(NAME guest_video_out_event_id COMMAND guest_video_out_event_id_tests) + set_tests_properties(guest_video_out_event_id PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_video_out_event_id PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_video_out_hdr_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutHdr.cpp) target_include_directories(guest_video_out_hdr_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp index ad6f310991..8383a60f1d 100644 --- a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp +++ b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp @@ -25,6 +25,16 @@ void require(bool condition, const char* reason) { if (!condition) throw std::runtime_error(std::string("DisplayWindow: ") + reason); } +int initialDisplayIndex() { + const auto value = std::getenv("ANYPS5_DISPLAY_INDEX"); + if (value == nullptr) return 0; + char* end = nullptr; + const auto index = std::strtol(value, &end, 10); + const auto count = SDL_GetNumVideoDisplays(); + require(end != value && *end == '\0' && index >= 0 && index < count, "ANYPS5_DISPLAY_INDEX is not a connected display index"); + return static_cast(index); +} + void applyAppIcon(SDL_Window* window) { if (!HasAppIcon_nid_postfix()) return; const auto data = GetAppIconData_nid_postfix(); @@ -54,7 +64,8 @@ void DisplayWindow::Ensure(std::uint32_t sourceWidth, std::uint32_t sourceHeight void DisplayWindow::create(std::uint32_t sourceWidth, std::uint32_t sourceHeight) { SDL_Rect usable{}; - require(SDL_GetDisplayUsableBounds(0, &usable) == 0, SDL_GetError()); + const auto displayIndex = initialDisplayIndex(); + require(SDL_GetDisplayUsableBounds(displayIndex, &usable) == 0, SDL_GetError()); require(DisplayWindowInitialSizePercent > 0 && DisplayWindowInitialSizePercent <= 100, "initial window size percent must be between 1 and 100"); require(usable.w > 0 && usable.h > 0, "usable display extent must be positive"); const auto boundsWidth = static_cast(static_cast(usable.w) * DisplayWindowInitialSizePercent / 100); @@ -65,7 +76,7 @@ void DisplayWindow::create(std::uint32_t sourceWidth, std::uint32_t sourceHeight AgcDriverLockVulkanLoader_nid_postfix(); const char* fullscreen = std::getenv("ANYPS5_FULLSCREEN"); const Uint32 fullscreenFlag = fullscreen != nullptr && *fullscreen != '\0' && std::strcmp(fullscreen, "0") != 0 ? static_cast(SDL_WINDOW_FULLSCREEN_DESKTOP) : 0u; - window = SDL_CreateWindow(title.value, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast(initialSize.width), static_cast(initialSize.height), SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | fullscreenFlag); + window = SDL_CreateWindow(title.value, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), static_cast(initialSize.width), static_cast(initialSize.height), SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | fullscreenFlag); AgcDriverUnlockVulkanLoader_nid_postfix(); require(window != nullptr, SDL_GetError()); SDL_SetWindowMinimumSize(window, static_cast(DisplayWindowMinimumWidth), static_cast(DisplayWindowMinimumHeight)); diff --git a/core/libs/prx/libSceVideoOut/src/Event.cpp b/core/libs/prx/libSceVideoOut/src/Event.cpp index dde7b34821..2c23c1f375 100644 --- a/core/libs/prx/libSceVideoOut/src/Event.cpp +++ b/core/libs/prx/libSceVideoOut/src/Event.cpp @@ -174,23 +174,17 @@ int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev) { if (ev == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } - if (ev->filter != EVFILT_VIDEO_OUT) { - throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT"); - } - const int ident = static_cast(ev->ident); - if (ident != VIDEO_OUT_EVENT_FLIP && ident != VIDEO_OUT_EVENT_VBLANK && ident != VIDEO_OUT_EVENT_PRE_VBLANK_START && ident != VIDEO_OUT_EVENT_SET_MODE && ident != VIDEO_OUT_EVENT_VRR_STATUS) { - throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT"); - } - return ident; + if (ev->filter != EVFILT_VIDEO_OUT) return VIDEO_OUT_ERROR_INVALID_EVENT; + const uintptr_t ident = ev->ident; + if (ident != VIDEO_OUT_EVENT_FLIP && ident != VIDEO_OUT_EVENT_VBLANK && ident != VIDEO_OUT_EVENT_PRE_VBLANK_START && ident != VIDEO_OUT_EVENT_SET_MODE && ident != VIDEO_OUT_EVENT_VRR_STATUS) return VIDEO_OUT_ERROR_INVALID_EVENT; + return static_cast(ident); } int APS5_VABI sceVideoOutGetEventData(const KernelEvent* ev, int64_t* data) { if (ev == nullptr || data == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } - if (ev->filter != EVFILT_VIDEO_OUT) { - throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT"); - } + if (ev->filter != EVFILT_VIDEO_OUT) return VIDEO_OUT_ERROR_INVALID_EVENT; uint64_t eventData = static_cast(ev->data) >> 16u; if (ev->ident == static_cast(VIDEO_OUT_EVENT_FLIP) && (static_cast(ev->data) & 0x8000000000000000ULL) != 0) { eventData |= 0xffff000000000000ULL; @@ -203,9 +197,7 @@ int APS5_VABI sceVideoOutGetEventCount(const KernelEvent* ev) { if (ev == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } - if (ev->filter != EVFILT_VIDEO_OUT) { - throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT"); - } + if (ev->filter != EVFILT_VIDEO_OUT) return VIDEO_OUT_ERROR_INVALID_EVENT; return static_cast((static_cast(ev->data) >> 12u) & 0xfu); } diff --git a/core/libs/prx/libSceVideodec2/CMakeLists.txt b/core/libs/prx/libSceVideodec2/CMakeLists.txt index 244b8050b3..3f47dbe0de 100644 --- a/core/libs/prx/libSceVideodec2/CMakeLists.txt +++ b/core/libs/prx/libSceVideodec2/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(libSceVideodec2 SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/InventoryExports.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Unimplemented.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HevcMetadata.cpp diff --git a/core/libs/prx/libSceVideodec2/InventoryExports.cpp b/core/libs/prx/libSceVideodec2/InventoryExports.cpp new file mode 100644 index 0000000000..714435a324 --- /dev/null +++ b/core/libs/prx/libSceVideodec2/InventoryExports.cpp @@ -0,0 +1,18 @@ +#include "prx/libSceVideodec2/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceVideodec2DecodeFirstSlice() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideodec2DecodeNextSlice() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideodec2SyncAllSlice() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceVideodec2/include/InventoryExports.hpp b/core/libs/prx/libSceVideodec2/include/InventoryExports.hpp new file mode 100644 index 0000000000..4e29454162 --- /dev/null +++ b/core/libs/prx/libSceVideodec2/include/InventoryExports.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideodec2DecodeFirstSlice(); +int APS5_VABI sceVideodec2DecodeNextSlice(); +int APS5_VABI sceVideodec2SyncAllSlice(); + +} diff --git a/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp b/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp index 85a17c3882..1ba7994a78 100644 --- a/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp +++ b/core/libs/prx/libSceVideodec2/libSceVideodec2.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceVideodec2/include/Api.hpp" +#include "prx/libSceVideodec2/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceVrTracker2/Export.cpp b/core/libs/prx/libSceVrTracker2/Export.cpp index f9246da310..7a58f2152c 100644 --- a/core/libs/prx/libSceVrTracker2/Export.cpp +++ b/core/libs/prx/libSceVrTracker2/Export.cpp @@ -652,9 +652,8 @@ std::int32_t APS5_VABI VrTracker2Unknown01(VrTracker2CalibrationResult* result, return 0; } -APS5_EXPORT("NBhWy+Gl0tY", VrTracker2Unknown02); -std::int32_t APS5_VABI VrTracker2Unknown02(VrTracker2MeshRequirement* requirement, std::uint32_t format) { - NotImplemented_nid_no_patch("NBhWy+Gl0tY"); +std::int32_t APS5_VABI sceVrTracker2GetRawCameraMeshSize(VrTracker2MeshRequirement* requirement, std::uint32_t format) { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceVrTracker2/include/Api.hpp b/core/libs/prx/libSceVrTracker2/include/Api.hpp index 871f89b821..3851bc7a44 100644 --- a/core/libs/prx/libSceVrTracker2/include/Api.hpp +++ b/core/libs/prx/libSceVrTracker2/include/Api.hpp @@ -300,7 +300,7 @@ std::int32_t APS5_VABI VrTracker2Accept(); std::int32_t APS5_VABI VrTracker2FillDefaults(void* output); std::int32_t APS5_VABI VrTracker2ClearWord(void* output); std::int32_t APS5_VABI VrTracker2Unknown01(VrTracker2CalibrationResult* result, std::uint32_t capacity); -std::int32_t APS5_VABI VrTracker2Unknown02(VrTracker2MeshRequirement* requirement, std::uint32_t format); +std::int32_t APS5_VABI sceVrTracker2GetRawCameraMeshSize(VrTracker2MeshRequirement* requirement, std::uint32_t format); std::int32_t APS5_VABI VrTracker2Unknown03(std::uint32_t mode, void* output, std::size_t capacity, std::size_t* writtenSize); std::int32_t APS5_VABI VrTracker2Unknown04(const VrTracker2ResultParameter* parameter, VrTracker2Result* result); std::int32_t APS5_VABI VrTracker2Unknown05(VrTracker2Pose* pose, std::uint64_t coordinateId); diff --git a/core/libs/prx/libSceXml/Export.cpp b/core/libs/prx/libSceXml/Export.cpp index fb34206c21..83c8c68aea 100644 --- a/core/libs/prx/libSceXml/Export.cpp +++ b/core/libs/prx/libSceXml/Export.cpp @@ -5,6 +5,754 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceXmlUnknown_PG6C3_Y2PG4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PG6C3+Y2PG4", sceXmlUnknown_PG6C3_Y2PG4_nid_no_patch); + +int APS5_VABI sceXmlUnknown_fEvjAmW_Ius_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fEvjAmW+Ius", sceXmlUnknown_fEvjAmW_Ius_nid_no_patch); + +int APS5_VABI sceXmlUnknown_bjsL0GMcHMI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bjsL0GMcHMI", sceXmlUnknown_bjsL0GMcHMI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_QuhXPuuafi0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QuhXPuuafi0", sceXmlUnknown_QuhXPuuafi0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_0a42xZWTRqA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0a42xZWTRqA", sceXmlUnknown_0a42xZWTRqA_nid_no_patch); + +int APS5_VABI sceXmlUnknown_R2dhCyaDT48_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("R2dhCyaDT48", sceXmlUnknown_R2dhCyaDT48_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ZJgKN_own3w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZJgKN+own3w", sceXmlUnknown_ZJgKN_own3w_nid_no_patch); + +int APS5_VABI sceXmlUnknown_HzmsiSfYg3Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HzmsiSfYg3Q", sceXmlUnknown_HzmsiSfYg3Q_nid_no_patch); + +int APS5_VABI sceXmlUnknown_OUDdcf1tv80_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OUDdcf1tv80", sceXmlUnknown_OUDdcf1tv80_nid_no_patch); + +int APS5_VABI sceXmlUnknown_6lEc1ExhDbE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6lEc1ExhDbE", sceXmlUnknown_6lEc1ExhDbE_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cXQ1mGsUUso_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cXQ1mGsUUso", sceXmlUnknown_cXQ1mGsUUso_nid_no_patch); + +int APS5_VABI sceXmlUnknown_5nlaOBZYquI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5nlaOBZYquI", sceXmlUnknown_5nlaOBZYquI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_WyGkGjCMbhc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WyGkGjCMbhc", sceXmlUnknown_WyGkGjCMbhc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_kaTn7C8H6iU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("kaTn7C8H6iU", sceXmlUnknown_kaTn7C8H6iU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_UrDhHj0xBLs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UrDhHj0xBLs", sceXmlUnknown_UrDhHj0xBLs_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ZnX_pfQnudw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZnX-pfQnudw", sceXmlUnknown_ZnX_pfQnudw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_XarSmMr1Rf0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XarSmMr1Rf0", sceXmlUnknown_XarSmMr1Rf0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ezzkCWLzmI0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ezzkCWLzmI0", sceXmlUnknown_ezzkCWLzmI0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_NU1VKlqKDrA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NU1VKlqKDrA", sceXmlUnknown_NU1VKlqKDrA_nid_no_patch); + +int APS5_VABI sceXmlUnknown_BmJSILyuz90_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BmJSILyuz90", sceXmlUnknown_BmJSILyuz90_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Z56irUtiXt0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z56irUtiXt0", sceXmlUnknown_Z56irUtiXt0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_MYonML9r0sQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("MYonML9r0sQ", sceXmlUnknown_MYonML9r0sQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_X4NiektK72E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("X4NiektK72E", sceXmlUnknown_X4NiektK72E_nid_no_patch); + +int APS5_VABI sceXmlUnknown_VwkgbfMINsc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VwkgbfMINsc", sceXmlUnknown_VwkgbfMINsc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_zl8amn17Exk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zl8amn17Exk", sceXmlUnknown_zl8amn17Exk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ak09Q_mYM0k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ak09Q+mYM0k", sceXmlUnknown_ak09Q_mYM0k_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cGZgcrq_L9A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cGZgcrq-L9A", sceXmlUnknown_cGZgcrq_L9A_nid_no_patch); + +int APS5_VABI sceXmlUnknown_hX8j9yVCL4Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hX8j9yVCL4Y", sceXmlUnknown_hX8j9yVCL4Y_nid_no_patch); + +int APS5_VABI sceXmlUnknown_YDUKJ_yIjaE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("YDUKJ-yIjaE", sceXmlUnknown_YDUKJ_yIjaE_nid_no_patch); + +int APS5_VABI sceXmlUnknown_LkOFUiblGSk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LkOFUiblGSk", sceXmlUnknown_LkOFUiblGSk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_4s_kfKFg_5o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4s-kfKFg+5o", sceXmlUnknown_4s_kfKFg_5o_nid_no_patch); + +int APS5_VABI sceXmlUnknown_N1iB3I8VXis_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("N1iB3I8VXis", sceXmlUnknown_N1iB3I8VXis_nid_no_patch); + +int APS5_VABI sceXmlUnknown_R2XV0ewj3dI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("R2XV0ewj3dI", sceXmlUnknown_R2XV0ewj3dI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_SEgecjvVvtM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SEgecjvVvtM", sceXmlUnknown_SEgecjvVvtM_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ahrF_7fmUY8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ahrF-7fmUY8", sceXmlUnknown_ahrF_7fmUY8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_rG2ESToL3K0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rG2ESToL3K0", sceXmlUnknown_rG2ESToL3K0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_dqsvARv0Bvc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dqsvARv0Bvc", sceXmlUnknown_dqsvARv0Bvc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_xxLlsTkSZN8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xxLlsTkSZN8", sceXmlUnknown_xxLlsTkSZN8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_61hqQqOvL00_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("61hqQqOvL00", sceXmlUnknown_61hqQqOvL00_nid_no_patch); + +int APS5_VABI sceXmlUnknown_nP8b1EXmb4c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("nP8b1EXmb4c", sceXmlUnknown_nP8b1EXmb4c_nid_no_patch); + +int APS5_VABI sceXmlUnknown_AXsvyvweIck_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AXsvyvweIck", sceXmlUnknown_AXsvyvweIck_nid_no_patch); + +int APS5_VABI sceXmlUnknown_lGzRzz4Zp9o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lGzRzz4Zp9o", sceXmlUnknown_lGzRzz4Zp9o_nid_no_patch); + +int APS5_VABI sceXmlUnknown_IYU31xtHBBw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IYU31xtHBBw", sceXmlUnknown_IYU31xtHBBw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cdo_5lVs1Zk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cdo-5lVs1Zk", sceXmlUnknown_cdo_5lVs1Zk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_q1eydak0HVM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("q1eydak0HVM", sceXmlUnknown_q1eydak0HVM_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ksmy_WdnC_8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ksmy+WdnC+8", sceXmlUnknown_ksmy_WdnC_8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Zm_3BN1raUM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Zm+3BN1raUM", sceXmlUnknown_Zm_3BN1raUM_nid_no_patch); + +int APS5_VABI sceXmlUnknown_6rdgvpVBBGI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6rdgvpVBBGI", sceXmlUnknown_6rdgvpVBBGI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_3SsdQzYQzo4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3SsdQzYQzo4", sceXmlUnknown_3SsdQzYQzo4_nid_no_patch); + +int APS5_VABI sceXmlUnknown_r2rDWToC1MQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("r2rDWToC1MQ", sceXmlUnknown_r2rDWToC1MQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_3HSPRdTF1fw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3HSPRdTF1fw", sceXmlUnknown_3HSPRdTF1fw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_wtDBw_PQhB8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wtDBw-PQhB8", sceXmlUnknown_wtDBw_PQhB8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_hFqu5w4zIiA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hFqu5w4zIiA", sceXmlUnknown_hFqu5w4zIiA_nid_no_patch); + +int APS5_VABI sceXmlUnknown_aVTInRBzsG0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("aVTInRBzsG0", sceXmlUnknown_aVTInRBzsG0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_BpqEaUzi1YI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BpqEaUzi1YI", sceXmlUnknown_BpqEaUzi1YI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_NHsN2nJAVmM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NHsN2nJAVmM", sceXmlUnknown_NHsN2nJAVmM_nid_no_patch); + +int APS5_VABI sceXmlUnknown__AmYlACOriA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-AmYlACOriA", sceXmlUnknown__AmYlACOriA_nid_no_patch); + +int APS5_VABI sceXmlUnknown_DpQmygHE0Mo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DpQmygHE0Mo", sceXmlUnknown_DpQmygHE0Mo_nid_no_patch); + +int APS5_VABI sceXmlUnknown_U79P_srDil8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("U79P-srDil8", sceXmlUnknown_U79P_srDil8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_IXzpGKFGdHE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IXzpGKFGdHE", sceXmlUnknown_IXzpGKFGdHE_nid_no_patch); + +int APS5_VABI sceXmlUnknown_MOs8PnGhPHU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("MOs8PnGhPHU", sceXmlUnknown_MOs8PnGhPHU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_EM5NLPqpUlc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EM5NLPqpUlc", sceXmlUnknown_EM5NLPqpUlc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_lELUb_ymzQE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lELUb+ymzQE", sceXmlUnknown_lELUb_ymzQE_nid_no_patch); + +int APS5_VABI sceXmlUnknown_KYan1Hzj36c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KYan1Hzj36c", sceXmlUnknown_KYan1Hzj36c_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cTq2UrFRl_s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cTq2UrFRl-s", sceXmlUnknown_cTq2UrFRl_s_nid_no_patch); + +int APS5_VABI sceXmlUnknown_AVskck6Ei5A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AVskck6Ei5A", sceXmlUnknown_AVskck6Ei5A_nid_no_patch); + +int APS5_VABI sceXmlUnknown_AwO_jxy52uE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AwO-jxy52uE", sceXmlUnknown_AwO_jxy52uE_nid_no_patch); + +int APS5_VABI sceXmlUnknown__b6MeTX_oKs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-b6MeTX-oKs", sceXmlUnknown__b6MeTX_oKs_nid_no_patch); + +int APS5_VABI sceXmlUnknown_gbbdoYygOOY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gbbdoYygOOY", sceXmlUnknown_gbbdoYygOOY_nid_no_patch); + +int APS5_VABI sceXmlUnknown_QfB2HoxkyXw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QfB2HoxkyXw", sceXmlUnknown_QfB2HoxkyXw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_sEdWzeyWzU0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sEdWzeyWzU0", sceXmlUnknown_sEdWzeyWzU0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_dqEF7A1NFSw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dqEF7A1NFSw", sceXmlUnknown_dqEF7A1NFSw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_0Am3sBbSwo0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Am3sBbSwo0", sceXmlUnknown_0Am3sBbSwo0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_DYjXSQpmafc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DYjXSQpmafc", sceXmlUnknown_DYjXSQpmafc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_HyC1lgnGOAU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HyC1lgnGOAU", sceXmlUnknown_HyC1lgnGOAU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_CT2gtjWgCN0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CT2gtjWgCN0", sceXmlUnknown_CT2gtjWgCN0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_pXRO7hiHbZw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pXRO7hiHbZw", sceXmlUnknown_pXRO7hiHbZw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_hXkNZdCO_qU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hXkNZdCO+qU", sceXmlUnknown_hXkNZdCO_qU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_FNyriqDetMY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FNyriqDetMY", sceXmlUnknown_FNyriqDetMY_nid_no_patch); + +int APS5_VABI sceXmlUnknown_CWHVjeJ6MZA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CWHVjeJ6MZA", sceXmlUnknown_CWHVjeJ6MZA_nid_no_patch); + +int APS5_VABI sceXmlUnknown_L3ZTOrvokyQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("L3ZTOrvokyQ", sceXmlUnknown_L3ZTOrvokyQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_zB_KjKIxT3U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zB+KjKIxT3U", sceXmlUnknown_zB_KjKIxT3U_nid_no_patch); + +int APS5_VABI sceXmlUnknown_goUTenum5Qg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("goUTenum5Qg", sceXmlUnknown_goUTenum5Qg_nid_no_patch); + +int APS5_VABI sceXmlUnknown_bn_QzEH_yGU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bn+QzEH+yGU", sceXmlUnknown_bn_QzEH_yGU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_xQEFvezBtjQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xQEFvezBtjQ", sceXmlUnknown_xQEFvezBtjQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_lO5WE1I_3_c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lO5WE1I+3-c", sceXmlUnknown_lO5WE1I_3_c_nid_no_patch); + +int APS5_VABI sceXmlUnknown_5_ZoQoEBymk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5-ZoQoEBymk", sceXmlUnknown_5_ZoQoEBymk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_DDYywAer5K0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DDYywAer5K0", sceXmlUnknown_DDYywAer5K0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_yHASI8VDPMo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yHASI8VDPMo", sceXmlUnknown_yHASI8VDPMo_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Cf_2kNJZ_kg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Cf-2kNJZ-kg", sceXmlUnknown_Cf_2kNJZ_kg_nid_no_patch); + +int APS5_VABI sceXmlUnknown_fAWof4n_aJk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fAWof4n+aJk", sceXmlUnknown_fAWof4n_aJk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_6WH7RBBXvNE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6WH7RBBXvNE", sceXmlUnknown_6WH7RBBXvNE_nid_no_patch); + +int APS5_VABI sceXmlUnknown_1KUSL3RcrcU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1KUSL3RcrcU", sceXmlUnknown_1KUSL3RcrcU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Y6ky5K9t_fo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Y6ky5K9t+fo", sceXmlUnknown_Y6ky5K9t_fo_nid_no_patch); + +int APS5_VABI sceXmlUnknown_hcI_VlFn00Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hcI-VlFn00Y", sceXmlUnknown_hcI_VlFn00Y_nid_no_patch); + +int APS5_VABI sceXmlUnknown_BjtxPGQ2_fQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BjtxPGQ2-fQ", sceXmlUnknown_BjtxPGQ2_fQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Jn066JNHRQc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Jn066JNHRQc", sceXmlUnknown_Jn066JNHRQc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_niOakMzo8VQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("niOakMzo8VQ", sceXmlUnknown_niOakMzo8VQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_a_xgqpSqYX8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("a-xgqpSqYX8", sceXmlUnknown_a_xgqpSqYX8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_iwGQqOKMxec_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("iwGQqOKMxec", sceXmlUnknown_iwGQqOKMxec_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Q1PLuSTqHHU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Q1PLuSTqHHU", sceXmlUnknown_Q1PLuSTqHHU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ncY4waWIyVU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ncY4waWIyVU", sceXmlUnknown_ncY4waWIyVU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cR4ywoNCJ0M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cR4ywoNCJ0M", sceXmlUnknown_cR4ywoNCJ0M_nid_no_patch); + +int APS5_VABI sceXmlUnknown_c1T_xl90CI8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c1T+xl90CI8", sceXmlUnknown_c1T_xl90CI8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_EpIfo9S64Yc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EpIfo9S64Yc", sceXmlUnknown_EpIfo9S64Yc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_E1QbfONrBrI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("E1QbfONrBrI", sceXmlUnknown_E1QbfONrBrI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_gyuVdpJihv0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gyuVdpJihv0", sceXmlUnknown_gyuVdpJihv0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_xOC0RMoxhIM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xOC0RMoxhIM", sceXmlUnknown_xOC0RMoxhIM_nid_no_patch); + +int APS5_VABI sceXmlUnknown_lT_hm4QHVUI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lT+hm4QHVUI", sceXmlUnknown_lT_hm4QHVUI_nid_no_patch); + +int APS5_VABI sceXmlUnknown_2NrQs5Oc5NU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2NrQs5Oc5NU", sceXmlUnknown_2NrQs5Oc5NU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_ztPRp3gnFeo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ztPRp3gnFeo", sceXmlUnknown_ztPRp3gnFeo_nid_no_patch); + +int APS5_VABI sceXmlUnknown_oBPtPb7GaMg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("oBPtPb7GaMg", sceXmlUnknown_oBPtPb7GaMg_nid_no_patch); + +int APS5_VABI sceXmlUnknown_h4EDPDzHiX0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("h4EDPDzHiX0", sceXmlUnknown_h4EDPDzHiX0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_GJucNrTY5kk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("GJucNrTY5kk", sceXmlUnknown_GJucNrTY5kk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_t1foZtjoF2w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("t1foZtjoF2w", sceXmlUnknown_t1foZtjoF2w_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Y_75_LnHTVo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Y+75-LnHTVo", sceXmlUnknown_Y_75_LnHTVo_nid_no_patch); + +int APS5_VABI sceXmlUnknown_sfRXuOQix9A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sfRXuOQix9A", sceXmlUnknown_sfRXuOQix9A_nid_no_patch); + +int APS5_VABI sceXmlUnknown_pCEC6vbRldc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pCEC6vbRldc", sceXmlUnknown_pCEC6vbRldc_nid_no_patch); + +int APS5_VABI sceXmlUnknown_4b_vop3WuR4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4b-vop3WuR4", sceXmlUnknown_4b_vop3WuR4_nid_no_patch); + +int APS5_VABI sceXmlUnknown_bC3O0thcHS8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bC3O0thcHS8", sceXmlUnknown_bC3O0thcHS8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_t2dW1YJCdBE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("t2dW1YJCdBE", sceXmlUnknown_t2dW1YJCdBE_nid_no_patch); + +int APS5_VABI sceXmlUnknown_UmfVPAXbsR0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UmfVPAXbsR0", sceXmlUnknown_UmfVPAXbsR0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_e9TNH6eNEnA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("e9TNH6eNEnA", sceXmlUnknown_e9TNH6eNEnA_nid_no_patch); + +int APS5_VABI sceXmlUnknown_Z9kvulfEIVU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z9kvulfEIVU", sceXmlUnknown_Z9kvulfEIVU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_B6e7H_CKcPQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("B6e7H-CKcPQ", sceXmlUnknown_B6e7H_CKcPQ_nid_no_patch); + +int APS5_VABI sceXmlUnknown_LOdAMm2YXIs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LOdAMm2YXIs", sceXmlUnknown_LOdAMm2YXIs_nid_no_patch); + +int APS5_VABI sceXmlUnknown_20wueHdL0Nk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("20wueHdL0Nk", sceXmlUnknown_20wueHdL0Nk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_UUydNdNg5Aw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UUydNdNg5Aw", sceXmlUnknown_UUydNdNg5Aw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_vlhKurmO2dM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("vlhKurmO2dM", sceXmlUnknown_vlhKurmO2dM_nid_no_patch); + +int APS5_VABI sceXmlUnknown_jO6F_B5EbzU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jO6F+B5EbzU", sceXmlUnknown_jO6F_B5EbzU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_r5YSgAZsO4o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("r5YSgAZsO4o", sceXmlUnknown_r5YSgAZsO4o_nid_no_patch); + +int APS5_VABI sceXmlUnknown_WLaUXcixYPw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WLaUXcixYPw", sceXmlUnknown_WLaUXcixYPw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_BcCD3V30xsk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BcCD3V30xsk", sceXmlUnknown_BcCD3V30xsk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_FySlXOyk40o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FySlXOyk40o", sceXmlUnknown_FySlXOyk40o_nid_no_patch); + +int APS5_VABI sceXmlUnknown_sL1oNQHcDN0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sL1oNQHcDN0", sceXmlUnknown_sL1oNQHcDN0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_6HaRzv379ew_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6HaRzv379ew", sceXmlUnknown_6HaRzv379ew_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cH8iF18qplw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cH8iF18qplw", sceXmlUnknown_cH8iF18qplw_nid_no_patch); + +int APS5_VABI sceXmlUnknown_uwiT_sz9G8Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uwiT+sz9G8Y", sceXmlUnknown_uwiT_sz9G8Y_nid_no_patch); + +int APS5_VABI sceXmlUnknown_cVQVNFBCvds_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cVQVNFBCvds", sceXmlUnknown_cVQVNFBCvds_nid_no_patch); + +int APS5_VABI sceXmlUnknown__kAtAhAVcd0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+kAtAhAVcd0", sceXmlUnknown__kAtAhAVcd0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_GTZYWYRctR0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("GTZYWYRctR0", sceXmlUnknown_GTZYWYRctR0_nid_no_patch); + +int APS5_VABI sceXmlUnknown_s9TpQC332NU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("s9TpQC332NU", sceXmlUnknown_s9TpQC332NU_nid_no_patch); + +int APS5_VABI sceXmlUnknown_UQSdfLL7qxs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UQSdfLL7qxs", sceXmlUnknown_UQSdfLL7qxs_nid_no_patch); + +int APS5_VABI sceXmlUnknown_UwgBRss72dY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UwgBRss72dY", sceXmlUnknown_UwgBRss72dY_nid_no_patch); + +int APS5_VABI sceXmlUnknown_1m2y_e811xA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1m2y+e811xA", sceXmlUnknown_1m2y_e811xA_nid_no_patch); + +int APS5_VABI sceXmlUnknown__vqI0G6ytM8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-vqI0G6ytM8", sceXmlUnknown__vqI0G6ytM8_nid_no_patch); + +int APS5_VABI sceXmlUnknown_imqT45eFHD4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("imqT45eFHD4", sceXmlUnknown_imqT45eFHD4_nid_no_patch); + +int APS5_VABI sceXmlUnknown_KdQXdHK6YPg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KdQXdHK6YPg", sceXmlUnknown_KdQXdHK6YPg_nid_no_patch); + +int APS5_VABI sceXmlUnknown_YQ3ZHtUvdAk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("YQ3ZHtUvdAk", sceXmlUnknown_YQ3ZHtUvdAk_nid_no_patch); + +int APS5_VABI sceXmlUnknown_TdntSxjU4sk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("TdntSxjU4sk", sceXmlUnknown_TdntSxjU4sk_nid_no_patch); } diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 7d485517ef..b6a3585327 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -69,6 +69,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Bessel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatBuiltins.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatingPrefix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp @@ -131,6 +132,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") if(BUILD_TESTING AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC) add_test(NAME libc_floating_point_contraction COMMAND ${CMAKE_COMMAND} -DCOMPILER=${CMAKE_CXX_COMPILER} + "-DARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}" "-DOPTIONS=$, >" -DWORK=${CMAKE_CURRENT_BINARY_DIR}/libc_floating_point_contraction -P ${LIBS_INCLUDE_DIR}/tests/LibcFloatingPointContraction.cmake) @@ -467,6 +469,18 @@ if(BUILD_TESTING) set_tests_properties(guest_random_device_seed PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_count_ones_tests ${LIBS_INCLUDE_DIR}/tests/GuestCountOnes.cpp) + target_include_directories(guest_count_ones_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_count_ones_tests PRIVATE libc) + configure_windows_unwind(guest_count_ones_tests) + foreach(width uc us ui ul ull) + add_test(NAME guest_count_ones_${width} COMMAND guest_count_ones_tests ${width}) + set_tests_properties(guest_count_ones_${width} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_count_ones_${width} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(guest_integer_builtins_tests ${LIBS_INCLUDE_DIR}/tests/GuestIntegerBuiltins.cpp) target_include_directories(guest_integer_builtins_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_integer_builtins_tests PRIVATE libc) @@ -476,6 +490,18 @@ if(BUILD_TESTING) set_tests_properties(guest_integer_builtins PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_count_zeros_tests ${LIBS_INCLUDE_DIR}/tests/GuestCountZeros.cpp) + target_include_directories(guest_count_zeros_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_count_zeros_tests PRIVATE libc) + configure_windows_unwind(guest_count_zeros_tests) + foreach(width uc us ui ul ull) + add_test(NAME guest_count_zeros_${width} COMMAND guest_count_zeros_tests ${width}) + set_tests_properties(guest_count_zeros_${width} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_count_zeros_${width} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(guest_wide_stream_scan_tests ${LIBS_INCLUDE_DIR}/tests/GuestWideStreamScan.cpp) target_include_directories(guest_wide_stream_scan_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_wide_stream_scan_tests PRIVATE libc) @@ -789,8 +815,9 @@ if(BUILD_TESTING) add_test(NAME guest_standard_streams COMMAND guest_standard_stream_tests) add_test(NAME guest_standard_streams_closed_stdout COMMAND guest_standard_stream_tests --closed-stdout) add_test(NAME guest_standard_streams_exclusive_stdout COMMAND guest_standard_stream_tests --exclusive-stdout) + add_test(NAME guest_standard_streams_reopen_closed_stdout COMMAND guest_standard_stream_tests --reopen-closed-stdout) if(WIN32) - set_tests_properties(guest_standard_streams guest_standard_streams_closed_stdout guest_standard_streams_exclusive_stdout PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_standard_streams guest_standard_streams_closed_stdout guest_standard_streams_exclusive_stdout guest_standard_streams_reopen_closed_stdout PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME guest_standard_streams_update_stdout COMMAND guest_standard_stream_tests --update-stdout) @@ -808,6 +835,16 @@ if(BUILD_TESTING) set_tests_properties(guest_gets PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(file_stream_status_tests ${LIBS_INCLUDE_DIR}/tests/FileStreamStatus.cpp) + target_include_directories(file_stream_status_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(file_stream_status_tests PRIVATE libc) + configure_windows_unwind(file_stream_status_tests) + add_test(NAME file_stream_status COMMAND file_stream_status_tests) + set_tests_properties(file_stream_status PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(file_stream_status PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(file_streams_tests ${LIBS_INCLUDE_DIR}/tests/FileStreams.cpp) target_include_directories(file_streams_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(file_streams_tests PRIVATE libc) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index cf0eb2256a..746baa5374 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -253,6 +253,7 @@ void* APS5_VABI malloc_nid_postfix(size_t size); void APS5_VABI free_nid_postfix(void* ptr); void* APS5_VABI realloc_nid_postfix(void* ptr, size_t newSize); void* APS5_VABI reallocf_nid_postfix(void* ptr, size_t newSize); +void* APS5_VABI reallocarray_nid_postfix(void* ptr, size_t nmemb, size_t size); void* APS5_VABI memalign_nid_postfix(size_t alignment, size_t size); void* APS5_VABI aligned_alloc_nid_postfix(size_t alignment, size_t size); void* APS5_VABI reallocalign_nid_postfix(void* ptr, size_t size, size_t alignment); @@ -415,12 +416,22 @@ int APS5_VABI __ctzdi2_nid_postfix(std::uint64_t value); int APS5_VABI __ctzti2_nid_postfix(Uint128 value); int APS5_VABI __ffsdi2_nid_postfix(std::uint64_t value); int APS5_VABI __ffsti2_nid_postfix(Uint128 value); +std::uint32_t APS5_VABI stdc_count_zeros_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_count_zeros_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_count_zeros_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_count_zeros_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_count_zeros_ull_nid_postfix(std::uint64_t value); int APS5_VABI __paritysi2_nid_postfix(std::uint32_t value); int APS5_VABI __paritydi2_nid_postfix(std::uint64_t value); int APS5_VABI __parityti2_nid_postfix(Uint128 value); int APS5_VABI __popcountsi2_nid_postfix(std::uint32_t value); int APS5_VABI __popcountdi2_nid_postfix(std::uint64_t value); int APS5_VABI __popcountti2_nid_postfix(Uint128 value); +std::uint32_t APS5_VABI stdc_count_ones_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_count_ones_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_count_ones_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_count_ones_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_count_ones_ull_nid_postfix(std::uint64_t value); int APS5_VABI __cmpdi2_nid_postfix(std::int64_t a, std::int64_t b); int APS5_VABI __cmpti2_nid_postfix(Int128 a, Int128 b); int APS5_VABI __ucmpdi2_nid_postfix(std::uint64_t a, std::uint64_t b); @@ -476,6 +487,9 @@ int APS5_VABI ispunct_nid_postfix(int c); int APS5_VABI isxdigit_nid_postfix(int c); int APS5_VABI isascii_nid_postfix(int c); int APS5_VABI toascii_nid_postfix(int c); +int APS5_VABI digittoint_nid_postfix(int c); +int APS5_VABI isnumber_nid_postfix(int c); +int APS5_VABI ishexnumber_nid_postfix(int c); int APS5_VABI toupper_nid_postfix(int c); int APS5_VABI tolower_nid_postfix(int c); void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self); @@ -934,6 +948,7 @@ void APS5_VABI bcopy_nid_postfix(const void* source, void* destination, size_t c void* APS5_VABI memcpy_nid_postfix(void* dest, const void* src, size_t n); void* APS5_VABI memmove_nid_postfix(void* dest, const void* src, size_t n); int APS5_VABI memcmp_nid_postfix(const void* s1, const void* s2, size_t n); +int APS5_VABI timingsafe_memcmp_nid_postfix(const void* b1, const void* b2, size_t len); const void* APS5_VABI memchr_nid_postfix(const void* s, int c, size_t n); void* APS5_VABI memrchr_nid_postfix(const void* s, int c, size_t n); void* APS5_VABI memccpy_nid_postfix(void* dest, const void* src, int c, size_t n); @@ -960,6 +975,7 @@ std::int64_t APS5_VABI strtol_nid_postfix(const char* str, char** endptr, int ba std::uint64_t APS5_VABI strtoul_nid_postfix(const char* str, char** endptr, int base); long long APS5_VABI strtoll_nid_postfix(const char* str, char** endptr, int base); unsigned long long APS5_VABI strtoull_nid_postfix(const char* str, char** endptr, int base); +long long APS5_VABI strtoq_nid_postfix(const char* str, char** endptr, int base); unsigned long long APS5_VABI strtouq_nid_postfix(const char* str, char** endptr, int base); double APS5_VABI strtod_nid_postfix(const char* str, char** endptr); double APS5_VABI atof_nid_postfix(const char* str); @@ -1012,6 +1028,8 @@ long long APS5_VABI wcstol_nid_postfix(const char16_t* str, char16_t** endptr, i long long APS5_VABI wcstoimax_nid_postfix(const char16_t* str, char16_t** endptr, int base); unsigned long long APS5_VABI wcstoumax_nid_postfix(const char16_t* str, char16_t** endptr, int base); std::uint64_t APS5_VABI _WStoul_nid_postfix(const char16_t* str, char16_t** endptr, int base); +int APS5_VABI _Stopfx_nid_postfix(const char** cursor, char** end); +int APS5_VABI _WStopfx_nid_postfix(const char16_t** cursor, char16_t** end); int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second); size_t APS5_VABI wcsxfrm_nid_postfix(char16_t* destination, const char16_t* source, size_t count); size_t APS5_VABI strxfrm_nid_postfix(char* destination, const char* source, size_t count); @@ -1113,6 +1131,7 @@ const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void* first const void* APS5_VABI __std_find_trivial_unsized_2_nid_postfix(const void* first, std::uint16_t value); const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void* first, std::uint32_t value); const void* APS5_VABI __std_find_trivial_unsized_8_nid_postfix(const void* first, std::uint64_t value); +std::int64_t APS5_VABI __std_max_8i_nid_postfix(const void* first, const void* last); std::size_t APS5_VABI __std_count_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value); std::size_t APS5_VABI __std_count_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value); std::size_t APS5_VABI __std_count_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value); @@ -1122,23 +1141,28 @@ const void* APS5_VABI __std_min_element_1_nid_postfix(const void* first, const v const void* APS5_VABI __std_min_element_2_nid_postfix(const void* first, const void* last, bool isSigned); const void* APS5_VABI __std_min_element_4_nid_postfix(const void* first, const void* last, bool isSigned); const void* APS5_VABI __std_min_element_8_nid_postfix(const void* first, const void* last, bool isSigned); +std::int32_t APS5_VABI __std_min_4i_nid_postfix(const void* first, const void* last); const void* APS5_VABI __std_max_element_1_nid_postfix(const void* first, const void* last, bool isSigned); const void* APS5_VABI __std_max_element_2_nid_postfix(const void* first, const void* last, bool isSigned); const void* APS5_VABI __std_max_element_4_nid_postfix(const void* first, const void* last, bool isSigned); const void* APS5_VABI __std_max_element_8_nid_postfix(const void* first, const void* last, bool isSigned); +std::uint64_t APS5_VABI __std_max_8u_nid_postfix(const void* first, const void* last); MinMaxPointers APS5_VABI __std_minmax_element_1_nid_postfix(const void* first, const void* last, bool isSigned); MinMaxPointers APS5_VABI __std_minmax_element_2_nid_postfix(const void* first, const void* last, bool isSigned); MinMaxPointers APS5_VABI __std_minmax_element_4_nid_postfix(const void* first, const void* last, bool isSigned); MinMaxPointers APS5_VABI __std_minmax_element_8_nid_postfix(const void* first, const void* last, bool isSigned); +std::uint32_t APS5_VABI __std_min_4u_nid_postfix(const void* first, const void* last); void APS5_VABI __std_reverse_trivially_swappable_1_nid_postfix(void* first, void* last); void APS5_VABI __std_reverse_trivially_swappable_2_nid_postfix(void* first, void* last); void APS5_VABI __std_reverse_trivially_swappable_4_nid_postfix(void* first, void* last); void APS5_VABI __std_reverse_trivially_swappable_8_nid_postfix(void* first, void* last); +std::uint32_t APS5_VABI __std_max_4u_nid_postfix(const void* first, const void* last); void APS5_VABI __std_reverse_copy_trivially_copyable_1_nid_postfix(const void* first, const void* last, void* destination); void APS5_VABI __std_reverse_copy_trivially_copyable_2_nid_postfix(const void* first, const void* last, void* destination); void APS5_VABI __std_reverse_copy_trivially_copyable_4_nid_postfix(const void* first, const void* last, void* destination); void APS5_VABI __std_reverse_copy_trivially_copyable_8_nid_postfix(const void* first, const void* last, void* destination); void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void* first1, void* last1, void* first2); +std::int64_t APS5_VABI __std_min_8i_nid_postfix(const void* first, const void* last); void APS5_VABI ignore_handler_s_nid_postfix(const char* message, void* pointer, int error); void* APS5_VABI set_constraint_handler_s_nid_postfix(void* handler); std::size_t APS5_VABI wcstombs_nid_postfix(char* destination, const std::uint16_t* source, std::size_t capacity); diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index b624bc9433..7d37193c26 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -145,8 +145,31 @@ public: return directory; } + bool Adopt(std::FILE* handle) { + _handle = handle; + _guest = {}; + guestError = false; + randomDevice = false; + if (!_handle) return false; + _guest.flags = 0x10; +#ifdef _WIN32 + const int descriptor = _fileno(_handle); +#else + const int descriptor = ::fileno(_handle); +#endif + _guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast(descriptor) : -1; + return true; + } + bool Reopen(const std::filesystem::path& filename, const char* mode, [[maybe_unused]] std::FILE* (*open)(const std::filesystem::path&, const std::string&) = nullptr) { + if (!_handle) { + if (!open || filename.empty()) { + errno = ENOENT; + return false; + } + return Adopt(open(filename, mode)); + } auto* previous = GetHandle(); _guest = {}; guestError = false; @@ -206,15 +229,7 @@ public: _handle = std::freopen(filename.c_str(), mode, previous); if (kept >= 0) RestoreHostStandard(previous, kept); #endif - if (!_handle) return false; - _guest.flags = 0x10; -#ifdef _WIN32 - const int descriptor = _fileno(_handle); -#else - const int descriptor = ::fileno(_handle); -#endif - _guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast(descriptor) : -1; - return true; + return Adopt(_handle); } void PublishStatus(bool endOfFile, bool error) { __atomic_store_n(&_guest.readRemaining, 0, __ATOMIC_RELAXED); @@ -319,7 +334,7 @@ public: } void KeepDirectoryReadError(bool failed) { - if (failed && directory && (_guest.flags & 0x40) != 0) errno = 21; + if (failed && directory && (__atomic_load_n(&_guest.flags, __ATOMIC_RELAXED) & 0x40) != 0) errno = 21; } void SetError() { diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 33499fd85e..9ce5bb8cb3 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -33,6 +33,7 @@ extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& desti extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source, std::size_t bytes); +extern "C" bool GuestCopyIn_nid_no_patch(void* destination, const void* source, std::size_t bytes); extern "C" std::filesystem::path ResolvePathOrRelinkedModule_nid_no_patch(const char* path); extern "C" std::filesystem::path ResolveArchivedPath_nid_no_patch(const char* path, bool module = false); extern "C" std::filesystem::path ResolveDiskPath_nid_no_patch(const char* path); diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 6c1805fddc..3b5c8a3a79 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -62,6 +62,8 @@ void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* pointer, std::size_t bytes); void GuestAllocationsRequireAvailable_nid_postfix(void* mutation, const void* pointer, std::size_t bytes); bool GuestAllocationsCovers_nid_postfix(void* mutation, const void* pointer, std::size_t bytes); +void GuestAllocationsTrimToRegistered_nid_postfix(void* mutation, std::size_t page, const void** pointer, std::size_t* bytes); +void GuestAllocationsGuestPages_nid_postfix(std::uintptr_t address, std::size_t bytes, std::size_t page, std::uintptr_t* first, std::uintptr_t* end); bool GuestAllocationsOverlaps_nid_postfix(void* mutation, const void* pointer, std::size_t bytes); bool GuestAllocationsForRegistered_nid_postfix(void* mutation, const void* pointer, std::size_t bytes, const std::function& visit); Range GuestAllocationsFind_nid_postfix(void* mutation, const void* pointer); @@ -102,6 +104,7 @@ public: void RequireUnpinned(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireUnpinned_nid_postfix(handle, pointer, bytes); } void RequireAvailable(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireAvailable_nid_postfix(handle, pointer, bytes); } bool Covers(const void* pointer, std::size_t bytes) const { return GuestAllocationsCovers_nid_postfix(handle, pointer, bytes); } + void TrimToRegistered(std::size_t page, const void*& pointer, std::size_t& bytes) const { GuestAllocationsTrimToRegistered_nid_postfix(handle, page, &pointer, &bytes); } bool Overlaps(const void* pointer, std::size_t bytes) const { return GuestAllocationsOverlaps_nid_postfix(handle, pointer, bytes); } bool ForRegistered(const void* pointer, std::size_t bytes, const std::function& visit) const { return GuestAllocationsForRegistered_nid_postfix(handle, pointer, bytes, visit); } Range Find(const void* pointer) const { return GuestAllocationsFind_nid_postfix(handle, pointer); } diff --git a/core/libs/prx/libc/include/GuestWriteWatch.hpp b/core/libs/prx/libc/include/GuestWriteWatch.hpp index 9afcf582db..96f09551c6 100644 --- a/core/libs/prx/libc/include/GuestWriteWatch.hpp +++ b/core/libs/prx/libc/include/GuestWriteWatch.hpp @@ -17,6 +17,7 @@ bool GuestWriteWatchCollect_nid_postfix(std::uintptr_t address, std::size_t byte void GuestWriteWatchBeginHostWrite_nid_postfix(const void* pointer, std::size_t bytes); void GuestWriteWatchEndHostWrite_nid_postfix(const void* pointer, std::size_t bytes); void GuestWriteWatchProtectionChanged_nid_postfix(const void* pointer, std::size_t bytes, int protection); +int GuestWriteWatchProtect_nid_postfix(void* pointer, std::size_t bytes, int protection); int GuestWriteWatchProtection_nid_postfix(std::uintptr_t address, std::uintptr_t limit, std::uintptr_t* runEnd); bool GuestWriteWatchCollectArmed_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context); bool GuestWriteWatchCollectFresh_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context); diff --git a/core/libs/prx/libc/src/Backtrace.cpp b/core/libs/prx/libc/src/Backtrace.cpp index 0edde79ba2..324ca0e895 100644 --- a/core/libs/prx/libc/src/Backtrace.cpp +++ b/core/libs/prx/libc/src/Backtrace.cpp @@ -55,7 +55,7 @@ struct FrameRecord { }; static_assert(sizeof(FrameRecord) == NameOffset); -[[gnu::noinline]] std::vector CaptureFrames(int limit) { +[[gnu::noinline]] std::vector CaptureFrames(int limit, const void* caller) { void* addresses[MaxFrames]; #if defined(__linux__) || defined(__APPLE__) const int captured = backtrace(addresses, MaxFrames); @@ -77,8 +77,14 @@ static_assert(sizeof(FrameRecord) == NameOffset); const int captured = state.count; #endif + int first = 2; + for (int index = 0; index < captured; ++index) { + if (addresses[index] != caller) continue; + first = index; + break; + } std::vector frames; - for (int index = 2; index < captured && (limit < 0 || static_cast(frames.size()) < limit); ++index) { + for (int index = first; index < captured && (limit < 0 || static_cast(frames.size()) < limit); ++index) { #if defined(__linux__) || defined(__APPLE__) Dl_info info{}; if (dladdr(addresses[index], &info) == 0 || info.dli_fbase == nullptr || info.dli_fname == nullptr) continue; @@ -114,7 +120,7 @@ extern "C" { int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int limit, int* count, std::size_t* size) { if (count == nullptr || size == nullptr) throw std::invalid_argument("sceLibcBacktraceGetBufferSize: null output"); - const auto frames = CaptureFrames(limit); + const auto frames = CaptureFrames(limit, __builtin_return_address(0)); std::size_t total = 0; for (const auto& frame : frames) total += RecordSize(frame); *count = static_cast(frames.size()); @@ -124,7 +130,7 @@ int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int limit, int* count, s int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int limit, void* buffer, std::size_t size, int* count) { if (buffer == nullptr || count == nullptr) throw std::invalid_argument("sceLibcBacktraceSelf: null argument"); - const auto frames = CaptureFrames(limit); + const auto frames = CaptureFrames(limit, __builtin_return_address(0)); auto* cursor = static_cast(buffer); FrameRecord* previous = nullptr; int written = 0; diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp index dc45984759..fd7ee6ec4f 100644 --- a/core/libs/prx/libc/src/DinkumwareInternals.cpp +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -205,13 +205,12 @@ short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { constexpr double HugeExp = 1842.0; constexpr double Eps = std::numeric_limits::epsilon(); constexpr double InverseLn2 = 1.4426950408889634073599246810018921; - constexpr double Ln2High = 22713.0 / 32768.0; - constexpr double Ln2Low = 1.4286068203094172321214581765680755e-6; - constexpr double P1 = 420.30235984910635; - constexpr double P2 = 15132.70094680474802; - constexpr double Q0 = 30.01511290683317; - constexpr double Q1 = 3362.72154416553028; - constexpr double Q2 = 30265.40189360949691; + constexpr double Ln2High = 0x1.62e42f8p-1; + constexpr double Ln2Low = 0x1.be8e7bcd5e4f2p-27; + constexpr double P0 = 0x1.152b741a5e84bp-15; + constexpr double P1 = 0x1.c70e8daf3bd0bp-7; + constexpr double Q0 = 0x1.03fa081572e11p-10; + constexpr double Q1 = 0x1.c718f8c124358p-4; constexpr double E = 0x1.5bf0a8b145769p+1; const short xKind = ClassifyValue(x); const short yKind = ClassifyValue(y); @@ -277,14 +276,13 @@ short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { g = static_cast(exponent); g = (x - g * Ln2High) - g * Ln2Low; double factor; - if (-Eps < g && g < Eps) { + if (-Eps * 0.5 < g && g < Eps * 0.5) { factor = 1.0; } else { const double z = g * g; - const double w = (Q0 * z + Q1) * z + Q2; - g *= (z + P1) * z + P2; - factor = (w + g) / (w - g) * 2.0; - --exponent; + const double w = (Q0 * z + Q1) * z; + g *= (P0 * z + P1) * z + 0.5; + factor = (w + g + 1.0) / (w - g + 1.0); } const short result = ScaleProduct(*value, factor, y, exponent + offset); if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { @@ -373,14 +371,13 @@ short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset) { g = static_cast(exponent); g = (x - g * Ln2High) - g * Ln2Low; float factor; - if (-Eps < g && g < Eps) { + if (-Eps * 0.5f < g && g < Eps * 0.5f) { factor = 1.0f; } else { const float z = g * g; - const float w = Q * z + 1.0f; - g *= P0 + P1 * z; - factor = (w + g) / (w - g) * 2.0f; - --exponent; + const float w = Q * z; + g *= P1 * z + P0; + factor = (w + g + 1.0f) / (w - g + 1.0f); } const short result = ScaleProduct(*value, factor, y, exponent + offset); if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 364ba40805..9078a5fe41 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -376,6 +376,7 @@ static FileStream* OpenDirectoryStream(const std::filesystem::path& path) { FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream) { if (!stream || !mode) { errno = 22; return nullptr; } + if (!filename && !stream->IsOpen()) { errno = 9; return nullptr; } if (!filename) { errno = 45; return nullptr; } // Mode-only reopening is not supported. const auto parsed = ParseFileMode(mode); if (!parsed) { errno = 22; return nullptr; } @@ -383,7 +384,7 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode std::filesystem::path path; if (const int error = *filename ? ResolvePathError_nid_no_patch(filename, path) : 2) { if (stream->IsHostStandard()) stream->Detach(); - else stream->Close(); + else if (stream->IsOpen()) stream->Close(); if (stream->IsDynamic()) delete stream; errno = error; return nullptr; @@ -649,6 +650,14 @@ void* APS5_VABI reallocf_nid_postfix(void* ptr, size_t newSize) { return result; } +void* APS5_VABI reallocarray_nid_postfix(void* ptr, size_t nmemb, size_t size) { + if (size != 0 && nmemb > std::numeric_limits::max() / size) { + errno = 12; + return nullptr; + } + return realloc_nid_postfix(ptr, nmemb * size); +} + void* APS5_VABI memalign_nid_postfix(size_t alignment, size_t size) { if (!ApplicationHeapDefault_nid_no_patch()) { if (!validAlignment(alignment)) { diff --git a/core/libs/prx/libc/src/FloatingPrefix.cpp b/core/libs/prx/libc/src/FloatingPrefix.cpp new file mode 100644 index 0000000000..bd85e4ed16 --- /dev/null +++ b/core/libs/prx/libc/src/FloatingPrefix.cpp @@ -0,0 +1,88 @@ +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" + +namespace { + +template struct CharacterRules; + +template<> struct CharacterRules { + static bool Space(char value) { return isspace_nid_postfix(static_cast(value)) != 0; } + static bool Payload(char value) { return isalnum_nid_postfix(static_cast(value)) != 0 || value == '_'; } + static bool Hex(char value) { return isxdigit_nid_postfix(static_cast(value)) != 0; } +}; + +template<> struct CharacterRules { + static bool Space(char16_t value) { return iswspace_nid_postfix(value) != 0; } + static bool Payload(char16_t value) { return iswalnum_nid_postfix(value) != 0 || value == '_'; } + static bool Hex(char16_t value) { return iswxdigit_nid_postfix(value) != 0; } +}; + +template +bool Matches(const TChar* input, const char* word) { + while (*word) { + if (*input != *word && *input != *word - ('a' - 'A')) return false; + ++input; + ++word; + } + return true; +} + +template +int ParsePrefix(const TChar** cursor, TChar** end) { + const TChar* const original = *cursor; + const TChar* current = original; + while (CharacterRules::Space(*current)) ++current; + const int negative = *current == '-' ? 8 : 0; + if (*current == '-' || *current == '+') ++current; + int result = negative | 1; + if (*current == 'n' || *current == 'N') { + if (Matches(current, "nan")) { + current += 3; + result = 4; + if (*current == '(') { + const TChar* payload = current + 1; + while (CharacterRules::Payload(*payload)) ++payload; + if (*payload == ')') current = payload + 1; + } + } else { + current = original; + result = 0; + } + if (end) *end = const_cast(current); + } else if (*current == 'i' || *current == 'I') { + if (Matches(current, "inf")) { + current += 3; + if (Matches(current, "inity")) current += 5; + result = negative | 3; + } else { + current = original; + result = 0; + } + if (end) *end = const_cast(current); + } else if (*current == '0' && (current[1] == 'x' || current[1] == 'X')) { + const TChar* digit = current + 2; + if (*digit == '.') ++digit; + if (CharacterRules::Hex(*digit)) { + current += 2; + result = negative | 2; + } + } + *cursor = current; + return result; +} + +} + +extern "C" { + +int APS5_VABI _Stopfx_nid_postfix(const char** cursor, char** end) { + if (cursor == nullptr || *cursor == nullptr) NotImplemented_nid_no_patch(__func__); + return ParsePrefix(cursor, end); +} + +int APS5_VABI _WStopfx_nid_postfix(const char16_t** cursor, char16_t** end) { + if (cursor == nullptr || *cursor == nullptr) NotImplemented_nid_no_patch(__func__); + return ParsePrefix(cursor, end); +} + +} diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index 9c68872826..972d2f2070 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -845,6 +845,13 @@ extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source, #endif } +extern "C" bool GuestCopyIn_nid_no_patch(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + const auto start = reinterpret_cast(source); + if (start == 0 || bytes > UINTPTR_MAX - start) return false; + return CopyGuestBytes(destination, source, bytes); +} + extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) { auto resolved = ResolveLocked(path, false, true); AppArchiveRefuseOnly_nid_no_patch(resolved, path, "reading"); diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index bb988c8a93..cca11ea1e4 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -32,6 +32,9 @@ #include #include #endif +#ifdef __APPLE__ +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#endif namespace GuestAllocations { namespace { @@ -524,6 +527,52 @@ bool GuestAllocationsForRegistered_nid_postfix(void*, const void* pointer, std:: return any; } +void GuestAllocationsGuestPages_nid_postfix(std::uintptr_t address, std::size_t bytes, std::size_t page, std::uintptr_t* first, std::uintptr_t* end) { + std::uintptr_t bias = 0; +#ifdef __APPLE__ + struct Search { + std::uintptr_t address; + std::uintptr_t hostPage; + std::uintptr_t bias; + } search{address, static_cast(::sysconf(_SC_PAGESIZE)), 0}; + dl_iterate_phdr([](dl_phdr_info* image, std::size_t, void* data) { + auto& found = *static_cast(data); + for (unsigned index = 0; index < image->dlpi_phnum; ++index) { + const auto& header = image->dlpi_phdr[index]; + const auto segment = static_cast(image->dlpi_addr + header.p_vaddr); + const auto start = segment & ~(found.hostPage - 1); + const auto finish = (segment + header.p_memsz + found.hostPage - 1) & ~(found.hostPage - 1); + if (header.p_type == PT_LOAD && found.address >= start && found.address < finish) { + found.bias = static_cast(image->dlpi_addr); + return 1; + } + } + return 0; + }, &search); + bias = search.bias; +#endif + const auto mask = static_cast(page - 1); + *first = bias + ((address - bias) & ~mask); + *end = bias + ((address + bytes - bias + mask) & ~mask); +} + +void GuestAllocationsTrimToRegistered_nid_postfix(void*, std::size_t page, const void** pointer, std::size_t* bytes) { +#ifdef __APPLE__ + auto start = reinterpret_cast(*pointer); + const auto end = start + *bytes; + const auto& ranges = registry().ranges; + const auto following = ranges.upper_bound(start); + const bool startCovered = following != ranges.begin() && std::prev(following)->first + std::prev(following)->second->bytes > start; + if (!startCovered && following != ranges.end() && following->first < end && following->first - start < page) start = following->first; + *pointer = reinterpret_cast(start); + *bytes = end - start; +#else + static_cast(page); + static_cast(pointer); + static_cast(bytes); +#endif +} + bool GuestAllocationsCovers_nid_postfix(void*, const void* pointer, std::size_t bytes) { const auto address = reinterpret_cast(pointer); require(address != 0 && bytes != 0 && bytes <= std::numeric_limits::max() - address, "invalid guest allocation range"); diff --git a/core/libs/prx/libc/src/GuestWriteWatch.cpp b/core/libs/prx/libc/src/GuestWriteWatch.cpp index e0a4344e67..0d83d52ccb 100644 --- a/core/libs/prx/libc/src/GuestWriteWatch.cpp +++ b/core/libs/prx/libc/src/GuestWriteWatch.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/GuestWriteWatch.hpp" #include #include +#include #include #include #include @@ -433,6 +434,20 @@ public: }); } + int Protect(std::uintptr_t begin, std::uintptr_t end, int protection) { + if (!_available) return ::mprotect(reinterpret_cast(begin), end - begin, protection) == 0 ? 0 : errno; + const Lock lock(_lock); + if (::mprotect(reinterpret_cast(begin), end - begin, protection) != 0) return errno; + const auto first = pageStart(begin); + const auto last = pageEnd(end); + if (first < last) { + forEachPage(first, last, [&](std::uintptr_t, std::uint8_t& state) { + state = static_cast(Written | (state & Fresh) | (protection & ProtectionMask)); + }); + } + return 0; + } + void BeginHostWrite(std::uintptr_t begin, std::uintptr_t end) { if (!_available || end <= begin) return; begin = pageStart(begin); @@ -749,6 +764,18 @@ void GuestWriteWatchProtectionChanged_nid_postfix(const void* pointer, std::size #endif } +int GuestWriteWatchProtect_nid_postfix(void* pointer, std::size_t bytes, int protection) { +#if defined(__APPLE__) + const auto begin = reinterpret_cast(pointer); + return Watch::Get().Protect(begin, begin + bytes, protection); +#else + static_cast(pointer); + static_cast(bytes); + static_cast(protection); + return ENOSYS; +#endif +} + int GuestWriteWatchProtection_nid_postfix(std::uintptr_t address, std::uintptr_t limit, std::uintptr_t* runEnd) { #if defined(__APPLE__) return Watch::Get().Protection(address, limit, runEnd); diff --git a/core/libs/prx/libc/src/IntegerBuiltins.cpp b/core/libs/prx/libc/src/IntegerBuiltins.cpp index 44a6e6df13..c573591b92 100644 --- a/core/libs/prx/libc/src/IntegerBuiltins.cpp +++ b/core/libs/prx/libc/src/IntegerBuiltins.cpp @@ -212,6 +212,12 @@ int APS5_VABI __ffsti2_nid_postfix(Uint128 value) { return value == 0 ? 0 : CountTrailingZeros128(value) + 1; } +std::uint32_t APS5_VABI stdc_count_zeros_uc_nid_postfix(std::uint8_t value) { return 8 - std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_zeros_us_nid_postfix(std::uint16_t value) { return 16 - std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_zeros_ui_nid_postfix(std::uint32_t value) { return 32 - std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_zeros_ul_nid_postfix(std::uint64_t value) { return 64 - std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_zeros_ull_nid_postfix(std::uint64_t value) { return 64 - std::popcount(value); } + int APS5_VABI __paritysi2_nid_postfix(std::uint32_t value) { return std::popcount(value) & 1; } int APS5_VABI __paritydi2_nid_postfix(std::uint64_t value) { return std::popcount(value) & 1; } @@ -226,6 +232,12 @@ int APS5_VABI __popcountti2_nid_postfix(Uint128 value) { return std::popcount(High(value)) + std::popcount(Low(value)); } +std::uint32_t APS5_VABI stdc_count_ones_uc_nid_postfix(std::uint8_t value) { return std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_ones_us_nid_postfix(std::uint16_t value) { return std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_ones_ui_nid_postfix(std::uint32_t value) { return std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_ones_ul_nid_postfix(std::uint64_t value) { return std::popcount(value); } +std::uint32_t APS5_VABI stdc_count_ones_ull_nid_postfix(std::uint64_t value) { return std::popcount(value); } + int APS5_VABI __cmpdi2_nid_postfix(std::int64_t a, std::int64_t b) { return Compare(a, b); } int APS5_VABI __cmpti2_nid_postfix(Int128 a, Int128 b) { return Compare(a, b); } int APS5_VABI __ucmpdi2_nid_postfix(std::uint64_t a, std::uint64_t b) { return Compare(a, b); } diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 3ed2a68736..c601586676 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -586,11 +586,20 @@ int APS5_VABI ispunct_nid_postfix(int c) { return ClassifyCharacter(c, 0x08); } int APS5_VABI isxdigit_nid_postfix(int c) { return ClassifyCharacter(c, 0x01); } int APS5_VABI isascii_nid_postfix(int c) { return (c & ~0x7F) == 0; } int APS5_VABI toascii_nid_postfix(int c) { return c & 0x7F; } +int APS5_VABI digittoint_nid_postfix(int c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return 0; +} +int APS5_VABI isnumber_nid_postfix(int c) { return isdigit_nid_postfix(c); } +int APS5_VABI ishexnumber_nid_postfix(int c) { return isxdigit_nid_postfix(c); } int APS5_VABI toupper_nid_postfix(int c) { return ConvertCharacter(c, true); } int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); } std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0; std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt5ctypeIcE_nid_postfix[14] {}; std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt7collateIcE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] { diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index eecd5815e7..9c34d18ed7 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -477,7 +477,42 @@ float APS5_VABI nearbyintf_nid_postfix(float x) { return std::isnan(x) ? x + x : long double APS5_VABI nearbyintl_nid_postfix(long double x) { return std::nearbyint(x); } double APS5_VABI nearbyint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::nearbyint(x); } -double APS5_VABI cbrt_nid_postfix(double x) { return std::cbrt(x); } +double APS5_VABI cbrt_nid_postfix(double x) { + const std::uint32_t B1 = 715094163; + const std::uint32_t B2 = 696219795; + const double P0 = 1.87595182427177009643; + const double P1 = -1.88497979543377169875; + const double P2 = 1.621429720105354466140; + const double P3 = -0.758397934778766047437; + const double P4 = 0.145996192886612446982; + const std::uint64_t bits = std::bit_cast(x); + std::uint32_t hx = static_cast(bits >> 32); + const std::uint32_t low = static_cast(bits); + const std::uint32_t sign = hx & 0x80000000u; + hx ^= sign; + if (hx >= 0x7ff00000) { + return x + x; + } + double t; + if (hx < 0x00100000) { + if ((hx | low) == 0) { + return x; + } + t = std::bit_cast(std::uint64_t{0x4350000000000000}) * x; + const std::uint32_t high = static_cast(std::bit_cast(t) >> 32); + t = std::bit_cast(static_cast(sign | ((high & 0x7fffffff) / 3 + B2)) << 32); + } else { + t = std::bit_cast(static_cast(sign | (hx / 3 + B1)) << 32); + } + double r = (t * t) * (t / x); + t = t * ((P0 + r * (P1 + r * P2)) + ((r * r) * r) * (P3 + r * P4)); + t = std::bit_cast((std::bit_cast(t) + 0x80000000u) & 0xffffffffc0000000ull); + const double s = t * t; + r = x / s; + const double w = t + t; + r = (r - t) / (w + r); + return t + t * r; +} double APS5_VABI asin_nid_postfix(double x) { if (std::isgreater(x, 1.0) || std::isless(x, -1.0)) { std::feraiseexcept(FE_INVALID); @@ -742,7 +777,7 @@ static double Expm1(double x) { } double y; if (k <= -2 || k > 56) { - y = 1.0 - (e - x); + y = 1.0 + (x - e); if (k == 1024) { y = y * 2.0 * 0x1p1023; } else { @@ -1066,7 +1101,72 @@ void APS5_VABI sincosf_nid_postfix(float x, float* sinp, float* cosp) { double APS5_VABI sin_nid_postfix(double x) { return std::sin(x); } double APS5_VABI cos_nid_postfix(double x) { return std::cos(x); } -double APS5_VABI sinh_nid_postfix(double x) { return std::sinh(x); } +/*- + * Copyright (c) 2011 David Schultz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_exp.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_exp.c */ +static double SinhLdexpExpHalf(double x) { + constexpr std::uint32_t k = 1799; + constexpr double kln2 = 1246.97177782734161156; + const double expX = exp_nid_postfix(x - kln2); + const std::uint64_t bits = std::bit_cast(expX); + const std::uint32_t hx = static_cast(bits >> 32); + const int expt = -1 + static_cast(hx >> 20) - (0x3ff + 1023) + static_cast(k); + const std::uint32_t scaledHigh = (hx & 0xfffff) | ((0x3ff + 1023) << 20); + const double scaled = std::bit_cast((bits & 0xffffffffull) | (static_cast(scaledHigh) << 32)); + return scaled * std::bit_cast(static_cast(0x3ff + expt) << 52); +} +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_sinh.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_sinh.c */ +double APS5_VABI sinh_nid_postfix(double x) { + const double one = 1.0; + const double shuge = 1.0e307; + const std::int32_t jx = static_cast(std::bit_cast(x) >> 32); + const std::int32_t ix = jx & 0x7fffffff; + if (ix >= 0x7ff00000) return x + x; + const double h = jx < 0 ? -0.5 : 0.5; + if (ix < 0x40360000) { + if (ix < 0x3e300000 && shuge + x > one) return x; + const double t = Expm1(std::fabs(x)); + if (ix < 0x3ff00000) return h * (2.0 * t - t * t / (t + one)); + return h * (t + t / (t + one)); + } + if (ix < 0x40862e42) return h * exp_nid_postfix(std::fabs(x)); + if (ix <= 0x408633ce) return h * 2.0 * SinhLdexpExpHalf(std::fabs(x)); + errno = ERANGE; + return x * shuge; +} double APS5_VABI cosh_nid_postfix(double x) { return std::cosh(x); } float APS5_VABI coshf_nid_postfix(float x) { const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; @@ -1801,27 +1901,28 @@ DinkumwareFloatCharacteristics _Ldbl_nid_postfix {18, 64, 4932, 16384, -4931, -1 {0, 0, 0, 0x8000, 0x3fc0}, {0xffff, 0xffff, 0xffff, 0xffff, 0x7ffe}, {0, 0, 0, 0x8000, 0x0001}}; static std::mutex g_randLock; -static std::uint32_t g_randState = 1; +static std::uint64_t g_randState = 1; -static int NextRand_nid_no_patch(std::uint32_t& state) { - const std::int64_t x = static_cast(state % 0x7ffffffeu) + 1; - std::int64_t next = 16807 * (x % 127773) - 2836 * (x / 127773); - if (next < 0) next += 0x7fffffff; - state = static_cast(next - 1); - return static_cast(next - 1); +static std::uint32_t MinimalStandardNext(std::int32_t state) { + if (state == 0) return 0x2ae1b767u; + const auto product = static_cast(static_cast(state)) * 16807u; + const auto high = static_cast(product >> 31u); + const auto sum = (static_cast(product) & 0x7fffffffu) + high; + if (sum < 0x7fffffffu) return sum; + if (sum == 0x7fffffffu) return 0; + return static_cast(high) <= 0 ? (sum & 0x7fffffffu) - 1u : (sum & 0x7fffffffu) + 1u; } int APS5_VABI rand_nid_postfix() { std::lock_guard lock(g_randLock); - return NextRand_nid_no_patch(g_randState); + g_randState = g_randState * 0x5851f42d4c957f2dull + 1u; + return static_cast((g_randState >> 32u) & 0x3fffffffu); } int APS5_VABI rand_r_nid_postfix(unsigned int* state) { if (state == nullptr) throw std::invalid_argument("rand_r: null state"); - std::uint32_t value = *state; - const int result = NextRand_nid_no_patch(value); - *state = value; - return result; + *state = MinimalStandardNext(static_cast(*state)); + return static_cast(*state); } void APS5_VABI srand_nid_postfix(unsigned int seed) { diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index bbfe5114ef..fa9cc26643 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -42,8 +42,8 @@ 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; + if ((bits.mantissa >> 63) != 0) return Finite; + return exponent != 0 || bits.mantissa != 0 ? Denormal : Zero; } template @@ -242,8 +242,9 @@ 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; + const int exponent = bits.signExponent & 0x7fff; + if (exponent == 0) return bits.mantissa != 0 ? Denormal : Zero; + return exponent != 0x7fff ? Finite : TestExtended(bits); } 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; } diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 128c85cd7e..c05d6c68e9 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -80,9 +80,10 @@ void ShiftNanWords(std::uint32_t* words, int from, int to, int digits) { } while (++index < to); } +template std::uint64_t HexNanPayload(const char* open, const char*& end) { - std::uint32_t words[2] = {}; - constexpr int high = 1; + std::uint32_t words[Words] = {}; + constexpr int high = Words - 1; int top = high; int group = high; int digits = 0; @@ -143,10 +144,15 @@ std::uint64_t HexNanPayload(const char* open, const char*& end) { do words[to++] = 0; while (to <= high); } else { - words[high] &= 0xffffffffu >> 12; + words[high] &= 0xffffffffu >> (Words == 2 ? 12 : 8); + } + if constexpr (Words == 2) { + if (words[0] == 0 && words[1] == 0) words[0] = 1; + return (static_cast(words[1]) << 32) | words[0]; + } else { + if (words[0] == 0) words[0] = 1; + return words[0]; } - if (words[0] == 0 && words[1] == 0) words[0] = 1; - return (static_cast(words[1]) << 32) | words[0]; } const char* NanSequenceEnd(const char* tail) { @@ -160,11 +166,12 @@ template TFloat NanValue(const NanSubject& subject, const char*& end) { if constexpr (std::is_same_v) { end = subject.tail; - const std::uint64_t payload = *subject.tail == '(' ? HexNanPayload(subject.tail, end) : 0; + const std::uint64_t payload = *subject.tail == '(' ? HexNanPayload<2>(subject.tail, end) : 0; return std::bit_cast((subject.negative ? 0x8000000000000000ull : 0) | 0x7ff8000000000000ull | payload); } else if constexpr (std::is_same_v) { - end = NanSequenceEnd(subject.tail); - return std::bit_cast((subject.negative ? 0x80000000u : 0) | 0x7fc00000u); + end = subject.tail; + const auto payload = static_cast(*subject.tail == '(' ? HexNanPayload<1>(subject.tail, end) : 0); + return std::bit_cast((subject.negative ? 0x80000000u : 0) | 0x7fc00000u | payload); } else { end = NanSequenceEnd(subject.tail); TFloat value{}; @@ -425,6 +432,10 @@ unsigned long long APS5_VABI strtoull_nid_postfix(const char* str, char** endptr return ParseInteger(str, endptr, base, std::strtoull); } +long long APS5_VABI strtoq_nid_postfix(const char* str, char** endptr, int base) { + return strtoll_nid_postfix(str, endptr, base); +} + unsigned long long APS5_VABI strtouq_nid_postfix(const char* str, char** endptr, int base) { return strtoull_nid_postfix(str, endptr, base); } @@ -830,16 +841,14 @@ int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second) size_t APS5_VABI wcsxfrm_nid_postfix(char16_t* destination, const char16_t* source, size_t count) { const size_t length = Length(source); - if (count != 0) { - const size_t copied = length < count ? length : count - 1; - std::memcpy(destination, source, copied * sizeof(char16_t)); - destination[copied] = 0; - } + if (count != 0) std::memcpy(destination, source, (length < count ? length + 1 : count) * sizeof(char16_t)); return length; } size_t APS5_VABI strxfrm_nid_postfix(char* destination, const char* source, size_t count) { - return strlcpy_nid_postfix(destination, source, count); + const size_t length = std::strlen(source); + if (count != 0) std::memcpy(destination, source, length < count ? length + 1 : count); + return length; } char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context) { @@ -954,4 +963,23 @@ int APS5_VABI wcscat_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t return wcsncat_s_nid_postfix(dest, destsz, src, static_cast(-1)); } +int APS5_VABI timingsafe_memcmp_nid_postfix(const void* b1, const void* b2, size_t len) { + const auto* p1 = static_cast(b1); + const auto* p2 = static_cast(b2); + int result = 0; + std::uint32_t done = 0; + for (size_t i = 0; i < len; ++i) { + const std::uint32_t a = p1[i]; + const std::uint32_t b = p2[i]; + const std::uint32_t lt = (a - b) >> 31; + const std::uint32_t gt = (b - a) >> 31; + const int cmp = static_cast(gt) - static_cast(lt); + const std::uint32_t d = a ^ b; + const std::uint32_t diffMask = static_cast(-static_cast((d | -d) >> 31)); + result |= cmp & static_cast(~done); + done |= diffMask; + } + return result; +} + } diff --git a/core/libs/prx/libc/src/VectorAlgorithms.cpp b/core/libs/prx/libc/src/VectorAlgorithms.cpp index 03b7a62dd3..d22a2e0f9b 100644 --- a/core/libs/prx/libc/src/VectorAlgorithms.cpp +++ b/core/libs/prx/libc/src/VectorAlgorithms.cpp @@ -116,6 +116,21 @@ const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void* first 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::int64_t APS5_VABI __std_max_8i_nid_postfix(const void* first, const void* last) { + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress <= beginAddress || (endAddress - beginAddress) % sizeof(std::int64_t) != 0) NotImplemented_nid_no_patch(__func__); + auto* current = static_cast(first); + const auto* end = static_cast(last); + std::int64_t maximum = Read(current); + for (current += sizeof(std::int64_t); current != end; current += sizeof(std::int64_t)) { + const auto value = Read(current); + if (value > maximum) maximum = value; + } + return maximum; +} + 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); } @@ -139,19 +154,76 @@ const void* APS5_VABI __std_min_element_1_nid_postfix(const void* first, const v 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; } + +std::int32_t APS5_VABI __std_min_4i_nid_postfix(const void* first, const void* last) { + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress <= beginAddress || (endAddress - beginAddress) % sizeof(std::int32_t) != 0) NotImplemented_nid_no_patch(__func__); + auto* current = static_cast(first); + const auto* end = static_cast(last); + std::int32_t minimum = Read(current); + for (current += sizeof(std::int32_t); current != end; current += sizeof(std::int32_t)) { + const auto value = Read(current); + if (value < minimum) minimum = value; + } + return 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; } +std::uint64_t APS5_VABI __std_max_8u_nid_postfix(const void* first, const void* last) { + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress <= beginAddress || (endAddress - beginAddress) % sizeof(std::uint64_t) != 0) NotImplemented_nid_no_patch(__func__); + auto* current = static_cast(first); + const auto* end = static_cast(last); + std::uint64_t maximum = Read(current); + for (current += sizeof(std::uint64_t); current != end; current += sizeof(std::uint64_t)) { + const auto value = Read(current); + if (maximum < value) maximum = value; + } + return 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); } +std::uint32_t APS5_VABI __std_min_4u_nid_postfix(const void* first, const void* last) { + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress <= beginAddress || (endAddress - beginAddress) % sizeof(std::uint32_t) != 0) NotImplemented_nid_no_patch(__func__); + auto* current = static_cast(first); + const auto* end = static_cast(last); + std::uint32_t minimum = Read(current); + for (current += sizeof(std::uint32_t); current != end; current += sizeof(std::uint32_t)) { + const auto value = Read(current); + if (value < minimum) minimum = value; + } + return minimum; +} + 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); } +std::uint32_t APS5_VABI __std_max_4u_nid_postfix(const void* first, const void* last) { + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress <= beginAddress || (endAddress - beginAddress) % sizeof(std::uint32_t) != 0) NotImplemented_nid_no_patch(__func__); + auto* current = static_cast(first); + const auto* end = static_cast(last); + std::uint32_t maximum = Read(current); + for (current += sizeof(std::uint32_t); current != end; current += sizeof(std::uint32_t)) { + const auto value = Read(current); + if (value > maximum) maximum = value; + } + return maximum; +} + 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); } @@ -168,4 +240,18 @@ void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void* f } } +std::int64_t APS5_VABI __std_min_8i_nid_postfix(const void* first, const void* last) { + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress <= beginAddress || (endAddress - beginAddress) % sizeof(std::int64_t) != 0) NotImplemented_nid_no_patch(__func__); + auto* current = static_cast(first); + const auto* end = static_cast(last); + std::int64_t minimum = Read(current); + for (current += sizeof(std::int64_t); current != end; current += sizeof(std::int64_t)) { + const auto value = Read(current); + if (value < minimum) minimum = value; + } + return minimum; +} + } diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index 41f9e3e155..e228a480e5 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include "SceTypes.hpp" @@ -16,6 +17,11 @@ #include "prx/libkernel/File/include/SharedMemory.hpp" #include "prx/libkernel/KernelErrors.hpp" +#ifdef ANYPS5_AIO_TESTING +#include +#include "tests/AioTest.hpp" +#endif + #ifndef _WIN32 #include #include @@ -25,7 +31,6 @@ namespace { constexpr std::int32_t AioProcessing = 2; constexpr std::int32_t AioCompleted = 3; -constexpr std::int32_t AioAborted = 4; constexpr std::int32_t AioReported = 0x10000; constexpr std::int32_t AioMaxQueues = 512; constexpr std::int32_t AioRequestNumMax = 128; @@ -43,51 +48,114 @@ constexpr std::int32_t AioDefaultDelayedCountLimit = 32; constexpr std::uint32_t AioDefaultSplitSize = 0x100000; std::mutex g_mutex; -std::array g_states{}; -std::int32_t g_nextId = 1; +struct RequestState { + std::int32_t id = 0; + std::int32_t state = 0; +}; +std::array g_requests{}; +std::int64_t g_nextId = 1; -bool ValidId(std::int32_t id) { - return id > 0 && id < AioMaxQueues; +#ifdef ANYPS5_AIO_TESTING +std::atomic g_processingWaiters{0}; + +class ProcessingWaiter { +public: + ~ProcessingWaiter() { + if (observed) g_processingWaiters.fetch_sub(1); + } + + void Observe() { + if (observed) return; + g_processingWaiters.fetch_add(1); + observed = true; + } + +private: + bool observed = false; +}; +#endif + +RequestState* FindLocked(std::int32_t id) { + if (id <= 0) return nullptr; + for (auto& request : g_requests) { + if (request.id == id) return &request; + } + return nullptr; } -std::int32_t AllocateId() { - std::lock_guard lock(g_mutex); - const std::int32_t id = g_nextId; - g_nextId = (g_nextId + 1) % AioMaxQueues; - if (g_nextId == 0) g_nextId = 1; - g_states[id] = AioProcessing; - return id; -} +class Reservation { +public: + explicit Reservation(std::int32_t count) : count(count) { + std::lock_guard lock(g_mutex); + std::array slots{}; + std::int32_t available = 0; + for (auto& request : g_requests) { + if (request.id == 0) slots[available++] = &request; + if (available == count) break; + } + if (available != count) throw std::runtime_error("AIO: host request registry exhausted"); + if (g_nextId + count - 1 > std::numeric_limits::max()) + throw std::runtime_error("AIO: host request identities exhausted"); + for (std::int32_t index = 0; index < count; ++index) { + ids[index] = static_cast(g_nextId++); + *slots[index] = {ids[index], AioProcessing}; + } + } -void SetState(std::int32_t id, std::int32_t state) { - std::lock_guard lock(g_mutex); - g_states[id] = state; -} + ~Reservation() { + if (published) return; + std::lock_guard lock(g_mutex); + for (std::int32_t index = 0; index < count; ++index) { + if (auto* request = FindLocked(ids[index])) *request = {}; + } + } + + Reservation(const Reservation&) = delete; + Reservation& operator=(const Reservation&) = delete; + + void Publish(std::int32_t* output) { + std::lock_guard lock(g_mutex); + for (std::int32_t index = 0; index < count; ++index) { + output[index] = ids[index]; + FindLocked(ids[index])->state = AioCompleted; + } + published = true; + } + +private: + std::array ids{}; + std::int32_t count; + bool published = false; +}; std::int32_t StateLocked(std::int32_t id) { - return ValidId(id) && g_states[id] != 0 ? g_states[id] : SCE_KERNEL_ERROR_ESRCH; + const auto* request = FindLocked(id); + return request != nullptr ? request->state : SCE_KERNEL_ERROR_ESRCH; } std::int32_t ReportLocked(std::int32_t id) { const std::int32_t state = StateLocked(id); - if (state != SCE_KERNEL_ERROR_ESRCH && state != AioProcessing) g_states[id] |= AioReported; + if (state != SCE_KERNEL_ERROR_ESRCH && state != AioProcessing) FindLocked(id)->state |= AioReported; return state; } std::int32_t CancelLocked(std::int32_t id) { const std::int32_t state = StateLocked(id); if (state == SCE_KERNEL_ERROR_ESRCH) return state; - if (state != AioProcessing) return state & ~AioReported; - g_states[id] = AioAborted; - return AioAborted; + return state & ~AioReported; } std::int32_t DeleteLocked(std::int32_t id) { - if (StateLocked(id) == SCE_KERNEL_ERROR_ESRCH) return SCE_KERNEL_ERROR_ESRCH; - g_states[id] = 0; + auto* request = FindLocked(id); + if (request == nullptr) return SCE_KERNEL_ERROR_ESRCH; + *request = {}; return 0; } +void RequireFinishedLocked(std::int32_t id, const char* function) { + if (StateLocked(id) == AioProcessing) NotImplemented_nid_no_patch(function); +} + std::int64_t NativePread(std::int32_t fd, void* buf, std::size_t nbyte, std::int64_t offset) { if (File::IsVirtualDirectoryDescriptor(fd)) { errno = EISDIR; return -1; } const GuestArena::HostWrite destination(buf, nbyte); @@ -161,22 +229,20 @@ int ValidateRequests(KernelAioRwRequest* req, std::int32_t size, std::int32_t pr int SubmitCommands(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, bool write, std::int32_t* id) { const int error = ValidateRequests(req, size, prio, id); if (error != 0) return error; - const std::int32_t queue = AllocateId(); + Reservation reservation(1); for (std::int32_t i = 0; i < size; ++i) RunRequest(req[i], write); - SetState(queue, AioCompleted); - *id = queue; + reservation.Publish(id); return 0; } int SubmitCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, bool write, std::int32_t* id) { const int error = ValidateRequests(req, size, prio, id); if (error != 0) return error; + Reservation reservation(size); for (std::int32_t i = 0; i < size; ++i) { - const std::int32_t queue = AllocateId(); RunRequest(req[i], write); - SetState(queue, AioCompleted); - id[i] = queue; } + reservation.Publish(id); return 0; } @@ -190,9 +256,23 @@ int ValidateIds(const std::int32_t* id, std::int32_t num, const void* out) { extern "C" { +#ifdef ANYPS5_AIO_TESTING +std::int32_t AioProcessingWaitersForTest_nid_no_patch() { + return g_processingWaiters.load(); +} + +void AioAdvanceIdentityForTest_nid_no_patch(std::int64_t next) { + std::lock_guard lock(g_mutex); + if (next < g_nextId || next > static_cast(std::numeric_limits::max()) + 1) + throw std::invalid_argument("AIO: invalid test identity advance"); + g_nextId = next; +} +#endif + int APS5_VABI sceKernelAioDeleteRequest(int32_t id, int32_t* ret) { if (ret == nullptr) return SCE_KERNEL_ERROR_EFAULT; std::lock_guard lock(g_mutex); + RequireFinishedLocked(id, __func__); *ret = DeleteLocked(id); return 0; } @@ -201,6 +281,7 @@ int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret) const int error = ValidateIds(id, num, ret); if (error != 0) return error; std::lock_guard lock(g_mutex); + for (int32_t i = 0; i < num; ++i) RequireFinishedLocked(id[i], __func__); for (int32_t i = 0; i < num; ++i) ret[i] = DeleteLocked(id[i]); return 0; } @@ -208,6 +289,7 @@ int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret) int APS5_VABI sceKernelAioCancelRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; std::lock_guard lock(g_mutex); + RequireFinishedLocked(id, __func__); *state = CancelLocked(id); return 0; } @@ -216,6 +298,7 @@ int APS5_VABI sceKernelAioCancelRequests(int32_t* id, int32_t num, int32_t* stat const int error = ValidateIds(id, num, state); if (error != 0) return error; std::lock_guard lock(g_mutex); + for (int32_t i = 0; i < num; ++i) RequireFinishedLocked(id[i], __func__); for (int32_t i = 0; i < num; ++i) state[i] = CancelLocked(id[i]); return 0; } @@ -276,6 +359,9 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, if (error != 0) return error; if (mode != AioWaitAnd && mode != AioWaitOr) return SCE_KERNEL_ERROR_EINVAL; const auto start = std::chrono::steady_clock::now(); +#ifdef ANYPS5_AIO_TESTING + ProcessingWaiter waiter; +#endif for (;;) { bool allDone = true; bool anyCompleted = false; @@ -291,6 +377,9 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, return 0; } } +#ifdef ANYPS5_AIO_TESTING + waiter.Observe(); +#endif if (usec != nullptr && *usec != 0) { const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); if (elapsed > static_cast(*usec)) { @@ -306,6 +395,9 @@ int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, int APS5_VABI sceKernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; const auto start = std::chrono::steady_clock::now(); +#ifdef ANYPS5_AIO_TESTING + ProcessingWaiter waiter; +#endif for (;;) { std::int32_t current; { @@ -316,6 +408,9 @@ int APS5_VABI sceKernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec *state = current; return 0; } +#ifdef ANYPS5_AIO_TESTING + waiter.Observe(); +#endif if (usec != nullptr && *usec != 0) { const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); if (elapsed > static_cast(*usec)) { diff --git a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp index 3150e1419a..fd635261f2 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp @@ -16,9 +16,10 @@ namespace { enum class JsonType { Null, Boolean, Number, String, Array, Object }; struct JsonValue; +struct JsonMember; using JsonArray = std::vector; -using JsonObject = std::vector>; +using JsonObject = std::vector; struct JsonValue { JsonType type = JsonType::Null; @@ -30,6 +31,11 @@ struct JsonValue { JsonObject objectValue; }; +struct JsonMember { + std::string key; + JsonValue value; +}; + std::string readFileToString(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); if (!file.is_open()) throw std::runtime_error("failed to open param.json"); @@ -179,7 +185,7 @@ JsonValue parseJsonObject(const std::string& text, std::size_t& position, std::s skipWhitespace(text, position); expectChar(text, position, ':'); JsonValue member = parseJsonValue(text, position, depth + 1); - value.objectValue.emplace_back(std::move(key), std::move(member)); + value.objectValue.push_back({std::move(key), std::move(member)}); skipWhitespace(text, position); const char next = peekChar(text, position); if (next == ',') { ++position; continue; } @@ -275,7 +281,7 @@ JsonValue parseJsonDocument(const std::string& text) { const JsonValue* findObjectMember(const JsonValue& object, const std::string& key) { if (object.type != JsonType::Object) throw std::runtime_error("expected JSON object in param.json"); for (const auto& member : object.objectValue) { - if (member.first == key) return &member.second; + if (member.key == key) return &member.value; } return nullptr; } @@ -308,8 +314,8 @@ ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) { if (languageObject == nullptr) languageObject = findObjectMember(*localizedParametersValue, "en-US"); if (languageObject == nullptr) { for (const auto& member : localizedParametersValue->objectValue) { - if (member.second.type == JsonType::Object) { - languageObject = &member.second; + if (member.value.type == JsonType::Object) { + languageObject = &member.value; break; } } diff --git a/core/libs/prx/libkernel/Apr/include/AmmPool.hpp b/core/libs/prx/libkernel/Apr/include/AmmPool.hpp new file mode 100644 index 0000000000..aaaba740d2 --- /dev/null +++ b/core/libs/prx/libkernel/Apr/include/AmmPool.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace AmmPool { + +constexpr std::uint64_t PageBytes = 0x4000; + +struct Page { + std::uint64_t physical; + std::int32_t type; + std::int32_t protection; + bool pooled; +}; + +struct Records { + std::map pool; + std::map pages; + std::map pooledUses; +}; + +inline void ReturnPages(Records& records, std::uint64_t offset, std::uint64_t bytes) { + auto next = records.pool.lower_bound(offset); + if (next != records.pool.end() && next->first == offset + bytes) { + bytes += next->second; + next = records.pool.erase(next); + } + if (next != records.pool.begin()) { + const auto previous = std::prev(next); + if (previous->first + previous->second == offset) { + previous->second += bytes; + return; + } + } + records.pool.emplace(offset, bytes); +} + +inline bool TryTakeRun(Records& records, std::uint64_t bytes, std::uint64_t& offset) { + for (auto it = records.pool.begin(); it != records.pool.end(); ++it) { + if (it->second < bytes) continue; + offset = it->first; + const auto remaining = it->second - bytes; + records.pool.erase(it); + if (remaining != 0) records.pool.emplace(offset + bytes, remaining); + return true; + } + return false; +} + +inline void AddPage(Records& records, std::uint64_t address, const Page& page) { + if (!page.pooled) { + records.pages[address] = page; + return; + } + const auto [uses, inserted] = records.pooledUses.try_emplace(page.physical, 0); + try { + records.pages[address] = page; + } catch (...) { + if (inserted) records.pooledUses.erase(uses); + throw; + } + ++uses->second; +} + +template +void MapScattered(Records& records, std::uint64_t address, std::uint64_t bytes, std::int32_t type, std::int32_t protection, MapRun&& mapRun) { + std::uint64_t free = 0; + for (const auto& [offset, size] : records.pool) free += size; + if (free < bytes) throw std::runtime_error("AMM: no free run of " + std::to_string(bytes) + " bytes in the direct memory given to the mapper"); + std::vector pages; + pages.reserve(bytes / PageBytes); + std::vector::iterator> runs; + runs.reserve(records.pool.size()); + std::uint64_t remainderOffset = 0; + std::uint64_t remainderBytes = 0; + std::uint64_t remaining = bytes; + for (auto it = records.pool.begin(); remaining != 0; ++it) { + const auto take = std::min(it->second, remaining); + for (std::uint64_t page = 0; page < take; page += PageBytes) pages.push_back({it->first + page, type, protection, true}); + runs.push_back(it); + if (take < it->second) { + remainderOffset = it->first + take; + remainderBytes = it->second - take; + } + remaining -= take; + } + std::map stagedPages; + std::map stagedUses; + for (std::size_t index = 0; index < pages.size(); ++index) { + stagedPages.emplace(address + index * PageBytes, pages[index]); + stagedUses.emplace(pages[index].physical, 1); + } + for (std::size_t first = 0; first < pages.size();) { + std::size_t last = first + 1; + while (last < pages.size() && pages[last].physical == pages[last - 1].physical + PageBytes) ++last; + mapRun(address + first * PageBytes, pages[first].physical, (last - first) * PageBytes); + first = last; + } + if (remainderBytes != 0) records.pool.emplace(remainderOffset, remainderBytes); + for (const auto run : runs) records.pool.erase(run); + records.pages.merge(stagedPages); + records.pooledUses.merge(stagedUses); +} + +} diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index dda99c8fbf..0ba7bbafeb 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -12,6 +12,7 @@ #include "prx/libc/include/PreciseWait.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" +#include "prx/libkernel/Apr/include/AmmPool.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" @@ -281,19 +282,13 @@ constexpr std::uint64_t AmmRangeAlignment = 64ull << 30; constexpr int GuestMapFixed = 0x10; constexpr int GuestMapNoOverwrite = 0x80; -struct AmmPage { - std::uint64_t physical; - std::int32_t type; - std::int32_t protection; - bool pooled; -}; +static_assert(AmmPool::PageBytes == PS5_PAGE_SIZE); -struct AmmState { +using AmmPage = AmmPool::Page; + +struct AmmState : AmmPool::Records { std::mutex lock; std::uintptr_t base = 0; - std::map pool; - std::map pages; - std::map pooledUses; std::set prt; }; @@ -331,36 +326,15 @@ int _ammHostProtection(std::int32_t protection) { } void _returnPoolPages(AmmState& state, std::uint64_t offset, std::uint64_t bytes) { - auto next = state.pool.lower_bound(offset); - if (next != state.pool.end() && next->first == offset + bytes) { - bytes += next->second; - next = state.pool.erase(next); - } - if (next != state.pool.begin()) { - const auto previous = std::prev(next); - if (previous->first + previous->second == offset) { - previous->second += bytes; - return; - } - } - state.pool.emplace(offset, bytes); + AmmPool::ReturnPages(state, offset, bytes); } -std::uint64_t _takePoolPages(AmmState& state, std::uint64_t bytes) { - for (auto it = state.pool.begin(); it != state.pool.end(); ++it) { - if (it->second < bytes) continue; - const auto offset = it->first; - const auto remaining = it->second - bytes; - state.pool.erase(it); - if (remaining != 0) state.pool.emplace(offset + bytes, remaining); - return offset; - } - throw std::runtime_error("AMM: no free run of " + std::to_string(bytes) + " bytes in the direct memory given to the mapper"); +bool _tryTakePoolPages(AmmState& state, std::uint64_t bytes, std::uint64_t& offset) { + return AmmPool::TryTakeRun(state, bytes, offset); } void _addPage(AmmState& state, std::uint64_t address, const AmmPage& page) { - state.pages[address] = page; - if (page.pooled) ++state.pooledUses[page.physical]; + AmmPool::AddPage(state, address, page); } void _releasePage(AmmState& state, const AmmPage& page) { @@ -491,14 +465,12 @@ void _ammAllocatePrt(const Apr::AmmProtectCommand& command) { } if (DoMprotect(reinterpret_cast(address), bytes, _ammHostProtection(command.protection)) != 0) throw std::runtime_error("AMM: protection change failed"); } else { - std::vector pages; try { - for (std::uint64_t page = 0; page < bytes; page += PS5_PAGE_SIZE) pages.push_back({_takePoolPages(state, PS5_PAGE_SIZE), command.type, command.protection, true}); + AmmPool::MapScattered(state, address, bytes, command.type, command.protection, [&](std::uint64_t at, std::uint64_t physical, std::uint64_t size) { _mapRun(at, physical, size, command.protection); }); } catch (...) { - for (const auto& page : pages) _returnPoolPages(state, page.physical, PS5_PAGE_SIZE); + _zeroPrt(address, bytes); throw; } - _mapPages(state, address, pages); } offset = end; } @@ -511,7 +483,16 @@ void _ammMap(const Apr::AmmMapCommand& command, bool direct) { _reserve(command.address, command.size); _forgetPages(state, command.address, command.size); _clearPrt(state, command.address, command.size); - const auto physical = direct ? command.directOffset : _takePoolPages(state, command.size); + std::uint64_t physical = direct ? command.directOffset : 0; + if (!direct && !_tryTakePoolPages(state, command.size, physical)) { + try { + AmmPool::MapScattered(state, command.address, command.size, command.type, command.protection, [&](std::uint64_t address, std::uint64_t physicalRun, std::uint64_t size) { _mapRun(address, physicalRun, size, command.protection); }); + } catch (...) { + _reserve(command.address, command.size); + throw; + } + return; + } try { _mapRun(command.address, physical, command.size, command.protection); } catch (...) { diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 974df3b88c..2a70b082f2 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Attr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Thread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Tsd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/GuestTlsMacOs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Rwlock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Sem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/MainThreadMacOs.cpp @@ -120,6 +121,7 @@ if(BUILD_TESTING) if(WIN32) list(APPEND aprReadCases fault stack) endif() + list(APPEND aprReadCases queries) foreach(aprReadCase IN LISTS aprReadCases) set(aprReadRun ${CMAKE_CURRENT_BINARY_DIR}/apr_read_${aprReadCase}) file(MAKE_DIRECTORY ${aprReadRun}) @@ -235,6 +237,13 @@ if(BUILD_TESTING) set_tests_properties(guest_process_time guest_process_time_counter PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME guest_process_time_tsc COMMAND guest_process_time_tests tsc) + add_test(NAME guest_process_time_tsc_scaled COMMAND guest_process_time_tests tsc) + set_tests_properties(guest_process_time_tsc_scaled PROPERTIES ENVIRONMENT "APS5_TIME_SCALE=0.5") + if(WIN32) + set_tests_properties(guest_process_time_tsc guest_process_time_tsc_scaled PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_process_arguments_tests ${LIBS_INCLUDE_DIR}/tests/GuestProcessArguments.cpp) target_include_directories(guest_process_arguments_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_process_arguments_tests PRIVATE libkernel libc) @@ -506,6 +515,24 @@ if(BUILD_TESTING) target_link_libraries(guest_aio_tests PRIVATE libkernel libc) configure_windows_unwind(guest_aio_tests) add_test(NAME guest_aio COMMAND guest_aio_tests) + add_test_executable(guest_aio_registry_tests + ${LIBS_INCLUDE_DIR}/tests/GuestAioRegistry.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Aio/src/Aio.cpp) + target_include_directories(guest_aio_registry_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_compile_definitions(guest_aio_registry_tests PRIVATE ANYPS5_AIO_TESTING) + target_link_libraries(guest_aio_registry_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_aio_registry_tests) + if(WIN32) + target_compile_definitions(guest_aio_registry_tests PRIVATE _WIN32_WINNT=0x0602) + endif() + foreach(mode identity admission rollback identity-exhaustion processing processing-rollback) + add_test(NAME guest_aio_${mode} COMMAND guest_aio_registry_tests ${mode}) + set_tests_properties(guest_aio_${mode} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_aio_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() if(WIN32) add_test(NAME guest_aio_bad_descriptors COMMAND guest_aio_tests bad-descriptors) set_tests_properties(guest_aio_bad_descriptors PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION @@ -938,6 +965,25 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_cancel PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_cancel_inactive_wait_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadCancelInactiveWait.cpp) + target_include_directories(guest_pthread_cancel_inactive_wait_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_cancel_inactive_wait_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_cancel_inactive_wait_tests) + foreach(waitState IN ITEMS cleanup destructor return) + foreach(waitMode IN ITEMS untimed timed) + foreach(requestApi IN ITEMS sce posix) + set(inactiveWaitTest guest_pthread_cancel_inactive_wait_${waitState}_${waitMode}_${requestApi}) + add_test(NAME ${inactiveWaitTest} COMMAND guest_pthread_cancel_inactive_wait_tests ${waitState} ${waitMode} ${requestApi}) + add_test(NAME ${inactiveWaitTest}_coarse COMMAND guest_pthread_cancel_inactive_wait_tests ${waitState} ${waitMode} ${requestApi}) + set_tests_properties(${inactiveWaitTest} ${inactiveWaitTest}_coarse PROPERTIES TIMEOUT 20) + set_tests_properties(${inactiveWaitTest}_coarse PROPERTIES ENVIRONMENT "APS5_COARSE_TIMED_WAITS=1") + if(WIN32) + set_tests_properties(${inactiveWaitTest} ${inactiveWaitTest}_coarse PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + endforeach() add_test_executable(guest_pthread_cancel_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadCancelRecovery.cpp) target_include_directories(guest_pthread_cancel_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_cancel_recovery_tests PRIVATE libkernel libc) @@ -1173,6 +1219,17 @@ if(BUILD_TESTING) set_tests_properties(guest_exception_handler PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_amm_pool_tests ${LIBS_INCLUDE_DIR}/tests/GuestAmmPool.cpp) + target_include_directories(guest_amm_pool_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_amm_pool_tests PRIVATE libc) + configure_windows_unwind(guest_amm_pool_tests) + add_test(NAME guest_amm_pool COMMAND guest_amm_pool_tests) + set_tests_properties(guest_amm_pool PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_amm_pool PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_raise_exception_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseException.cpp) target_include_directories(guest_raise_exception_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_raise_exception_tests PRIVATE libkernel libc) @@ -1190,6 +1247,13 @@ if(BUILD_TESTING) add_test(NAME guest_raise_nested_sleep COMMAND guest_raise_nested_sleep_tests) set_tests_properties(guest_raise_nested_sleep PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + add_test_executable(guest_raise_exiting_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseExitingThread.cpp) + target_include_directories(guest_raise_exiting_thread_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_raise_exiting_thread_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_raise_exiting_thread_tests) + add_test(NAME guest_raise_exiting_thread COMMAND guest_raise_exiting_thread_tests) + set_tests_properties(guest_raise_exiting_thread PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() if(WIN32) add_library(guest_unwind_module_info_fixture SHARED ${LIBS_INCLUDE_DIR}/tests/GuestUnwindModuleInfoFixture.cpp) @@ -1988,6 +2052,16 @@ if(BUILD_TESTING) set_tests_properties(guest_file_vector_memory PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_output_pointer_fault_tests ${LIBS_INCLUDE_DIR}/tests/GuestOutputPointerFault.cpp) + target_include_directories(guest_output_pointer_fault_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_output_pointer_fault_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_output_pointer_fault_tests) + add_test(NAME guest_output_pointer_fault COMMAND guest_output_pointer_fault_tests) + set_tests_properties(guest_output_pointer_fault PROPERTIES TIMEOUT 15) + if(WIN32) + set_tests_properties(guest_output_pointer_fault PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_fstat_fault_tests ${LIBS_INCLUDE_DIR}/tests/GuestFstatFault.cpp) target_include_directories(guest_fstat_fault_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_fstat_fault_tests PRIVATE libkernel libc) @@ -1998,6 +2072,16 @@ if(BUILD_TESTING) set_tests_properties(guest_fstat_fault PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_posix_specific_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSpecific.cpp) + target_include_directories(guest_posix_specific_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_specific_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_posix_specific_tests) + add_test(NAME guest_posix_specific COMMAND guest_posix_specific_tests) + set_tests_properties(guest_posix_specific PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_posix_specific PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_rename_dot_tests ${LIBS_INCLUDE_DIR}/tests/GuestRenameDot.cpp) target_include_directories(guest_rename_dot_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_rename_dot_tests PRIVATE libkernel libc) @@ -2194,4 +2278,28 @@ if(BUILD_TESTING) ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endforeach() + foreach(sleepApi IN ITEMS usleep nanosleep _nanosleep) + foreach(sleepCase IN ITEMS blocked disabled normal pending pending_long) + set(sleepTest guest_sleep_cancel_${sleepApi}_${sleepCase}) + add_test(NAME ${sleepTest} COMMAND guest_sleep_cancel_tests ${sleepCase} ${sleepApi}) + add_test(NAME ${sleepTest}_coarse COMMAND guest_sleep_cancel_tests ${sleepCase} ${sleepApi}) + set_tests_properties(${sleepTest} ${sleepTest}_coarse PROPERTIES TIMEOUT 20) + set_tests_properties(${sleepTest}_coarse PROPERTIES ENVIRONMENT "APS5_COARSE_TIMED_WAITS=1") + if(WIN32) + set_tests_properties(${sleepTest} ${sleepTest}_coarse PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + foreach(sleepApi IN ITEMS sce_usleep sce_nanosleep sce_sleep) + set(sleepTest guest_sleep_cancel_${sleepApi}_deferred) + add_test(NAME ${sleepTest} COMMAND guest_sleep_cancel_tests deferred ${sleepApi}) + add_test(NAME ${sleepTest}_coarse COMMAND guest_sleep_cancel_tests deferred ${sleepApi}) + set_tests_properties(${sleepTest} ${sleepTest}_coarse PROPERTIES TIMEOUT 20) + set_tests_properties(${sleepTest}_coarse PROPERTIES ENVIRONMENT "APS5_COARSE_TIMED_WAITS=1") + if(WIN32) + set_tests_properties(${sleepTest} ${sleepTest}_coarse PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + endforeach() endif() diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 22e5631f8b..5c85fec058 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -1327,10 +1327,13 @@ int DoMprotect(const void* addr, size_t len, int prot) { constexpr auto pageMask = static_cast(PS5_PAGE_SIZE - 1); const auto limit = std::numeric_limits::max(); if (address == 0 || len == 0 || len > limit - address || address + len > limit - pageMask) throw std::invalid_argument("Invalid guest memory protection range"); - const auto first = address & ~pageMask; - const auto end = (address + len + pageMask) & ~pageMask; - const auto bytes = static_cast(end - first); - const auto* pointer = reinterpret_cast(first); + auto first = address & ~pageMask; + auto end = (address + len + pageMask) & ~pageMask; +#ifdef __APPLE__ + GuestAllocations::GuestAllocationsGuestPages_nid_postfix(address, len, PS5_PAGE_SIZE, &first, &end); +#endif + auto bytes = static_cast(end - first); + const void* pointer = reinterpret_cast(first); const auto nativeProtection = LinuxProtFromSce(prot); if (GuestThreadStackHostRangeOverlaps(address, address + len)) throw std::system_error(EACCES, std::generic_category(), "mprotect overlaps host-only guest thread stack pages"); @@ -1359,6 +1362,10 @@ int DoMprotect(const void* addr, size_t len, int prot) { #ifdef _WIN32 if (nativeProtection != PROT_NONE && hostStack) CommitReservedPages(insidePointer, insideBytes); const bool protectedPages = mprotect(insidePointer, insideBytes, nativeProtection) == 0; +#elif defined(__APPLE__) + const int error = GuestWriteWatch::GuestWriteWatchProtect_nid_postfix(insidePointer, insideBytes, nativeProtection); + if (error != 0) errno = error; + const bool protectedPages = error == 0; #else bool protectedPages = mprotect(insidePointer, insideBytes, nativeProtection) == 0; if (!protectedPages && errno == ENOMEM) { @@ -1397,14 +1404,20 @@ int DoMprotect(const void* addr, size_t len, int prot) { if (!image.guestModule) throw std::invalid_argument("Memory protection of a foreign image is not supported"); mutation.RegisterImage(reinterpret_cast(image.base)); } + mutation.TrimToRegistered(PS5_PAGE_SIZE, pointer, bytes); #endif std::vector> spans; mutation.ForRegistered(pointer, bytes, [&](const void* span, std::size_t spanBytes) { spans.emplace_back(span, spanBytes); }); - if (spans.empty() || reinterpret_cast(spans.front().first) != first) return SCE_KERNEL_ERROR_EINVAL; + if (spans.empty() || spans.front().first != pointer) return SCE_KERNEL_ERROR_EINVAL; for (const auto& [span, spanBytes] : spans) mutation.RequireUnpinned(span, spanBytes); for (const auto& [span, spanBytes] : spans) { mutation.Protect(span, spanBytes, (prot & 3) != 0, (prot & 2) != 0, (prot & GuestProtGpuReadWrite) != 0, [&] { +#ifdef __APPLE__ + if (const int error = GuestWriteWatch::GuestWriteWatchProtect_nid_postfix(const_cast(span), spanBytes, nativeProtection); error != 0) + throw std::system_error(error, std::generic_category(), "mprotect failed"); +#else if (mprotect(const_cast(span), spanBytes, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "mprotect failed"); +#endif }); RecordProtection(span, spanBytes, prot); } diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 1b6b1d3dba..619cf83633 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -6,6 +6,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" @@ -36,6 +37,8 @@ #include #include #include +#include +#include extern "C" std::uint64_t GuestImageSegmentBytes_nid_no_patch(); @@ -54,6 +57,10 @@ size_t _configuredFlexibleSize() { return size; } +bool UndefinedProtection(int prot) { + return (prot & ~0x3F7) != 0; +} + constexpr int PRT_APERTURE_COUNT = 3; struct PrtAperture { @@ -172,8 +179,7 @@ void _releaseFlexible(uintptr_t start, size_t len) { std::mutex g_lockedRangesLock; std::map g_lockedRanges; -void RecordLockedRange(uintptr_t start, uintptr_t end) { - std::lock_guard lock(g_lockedRangesLock); +void RecordLockedRangeLocked(uintptr_t start, uintptr_t end) { auto it = g_lockedRanges.begin(); while (it != g_lockedRanges.end()) { if (it->first <= end && it->first + it->second >= start) { @@ -187,8 +193,12 @@ void RecordLockedRange(uintptr_t start, uintptr_t end) { g_lockedRanges[start] = end - start; } -void ForgetLockedRange(uintptr_t start, uintptr_t end) { +void RecordLockedRange(uintptr_t start, uintptr_t end) { std::lock_guard lock(g_lockedRangesLock); + RecordLockedRangeLocked(start, end); +} + +void ForgetLockedRangeLocked(uintptr_t start, uintptr_t end) { auto it = g_lockedRanges.upper_bound(start); if (it != g_lockedRanges.begin()) --it; while (it != g_lockedRanges.end() && it->first < end) { @@ -201,6 +211,11 @@ void ForgetLockedRange(uintptr_t start, uintptr_t end) { } } +void ForgetLockedRange(uintptr_t start, uintptr_t end) { + std::lock_guard lock(g_lockedRangesLock); + ForgetLockedRangeLocked(start, end); +} + } void ReleaseFlexibleMappingRange(std::uintptr_t address, size_t len) { @@ -262,6 +277,7 @@ int APS5_VABI sceKernelEnableDmemAliasing(void) { } int APS5_VABI sceKernelMapDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment) { + if (UndefinedProtection(prot)) return SCE_KERNEL_ERROR_EINVAL; (void)alignment; const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); @@ -269,6 +285,7 @@ int APS5_VABI sceKernelMapDirectMemory(void** addr, size_t len, int prot, int fl } int APS5_VABI sceKernelMapDirectMemory2(void** addr, size_t len, int type, int prot, int flags, int64_t direct_memory_start, size_t alignment) { + if (UndefinedProtection(prot)) return SCE_KERNEL_ERROR_EINVAL; (void)type; const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); @@ -276,11 +293,13 @@ int APS5_VABI sceKernelMapDirectMemory2(void** addr, size_t len, int type, int p } int APS5_VABI sceKernelMapFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags) { + if (UndefinedProtection(prot)) return SCE_KERNEL_ERROR_EINVAL; constexpr int IgnoredHeapFlag = 0x8000; return _mapFlexible(addr_in_out, len, prot, flags & ~IgnoredHeapFlag); } int APS5_VABI sceKernelMapNamedDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment, const char* name) { + if (UndefinedProtection(prot)) return SCE_KERNEL_ERROR_EINVAL; const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); if (result == 0 && name) sceKernelSetVirtualRangeName(*addr, len, name); @@ -288,6 +307,7 @@ int APS5_VABI sceKernelMapNamedDirectMemory(void** addr, size_t len, int prot, i } int32_t APS5_VABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags, const char* name) { + if (UndefinedProtection(prot)) return SCE_KERNEL_ERROR_EINVAL; const int result = _mapFlexible(addr_in_out, len, prot, flags); if (result == 0 && name) sceKernelSetVirtualRangeName(*addr_in_out, len, name); return result; @@ -389,6 +409,29 @@ int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInf info->end = info->start + host.RegionSize; const bool writable = (host.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)) != 0; const bool executable = (host.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; + #elif defined(__APPLE__) + mach_vm_address_t region = address; + mach_vm_size_t regionSize = 0; + vm_region_basic_info_data_64_t host{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + if (mach_vm_region(mach_task_self(), ®ion, ®ionSize, VM_REGION_BASIC_INFO_64, reinterpret_cast(&host), &count, &object) != KERN_SUCCESS || region > address || (host.protection & VM_PROT_READ) == 0) return SCE_KERNEL_ERROR_EACCES; + info->start = region; + info->end = region + regionSize; + int watched = -1; + for (std::uintptr_t cursor = info->start; cursor < info->end;) { + std::uintptr_t runEnd = info->end; + const int protection = GuestWriteWatch::GuestWriteWatchProtection_nid_postfix(cursor, info->end, &runEnd); + if (address < runEnd) { + info->start = cursor; + info->end = runEnd; + watched = protection; + break; + } + cursor = runEnd; + } + const bool writable = watched >= 0 ? (watched & PROT_WRITE) != 0 : (host.protection & VM_PROT_WRITE) != 0; + const bool executable = (host.protection & VM_PROT_EXECUTE) != 0; #else std::ifstream maps("/proc/self/maps"); std::string line; @@ -691,9 +734,58 @@ int UnlockHostPages(std::uintptr_t start, std::uintptr_t end) { return 0; } #else +#ifdef __APPLE__ +void AddUnwiredHostPages(std::uintptr_t start, std::uintptr_t end, std::vector>& unwired) { + for (auto cursor = start; cursor < end;) { + mach_vm_address_t region = cursor; + mach_vm_size_t size = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + if (mach_vm_region(mach_task_self(), ®ion, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast(&info), &count, &object) != KERN_SUCCESS || region >= end) { + unwired.emplace_back(cursor, end); + return; + } + if (region > cursor) unwired.emplace_back(cursor, region); + const auto last = std::min(end, region + size); + if (info.user_wired_count == 0) unwired.emplace_back(std::max(cursor, region), last); + cursor = last; + } +} + +int WireHostPages(std::uintptr_t start, std::uintptr_t end) { + std::lock_guard lock(g_lockedRangesLock); + std::vector> unwired; + auto cursor = start; + auto it = g_lockedRanges.upper_bound(start); + if (it != g_lockedRanges.begin()) --it; + for (; it != g_lockedRanges.end() && it->first < end; ++it) { + const auto lockedEnd = it->first + it->second; + if (lockedEnd <= cursor) continue; + if (it->first > cursor) unwired.emplace_back(cursor, it->first); + AddUnwiredHostPages(std::max(cursor, it->first), std::min(end, lockedEnd), unwired); + cursor = std::max(cursor, lockedEnd); + } + if (cursor < end) unwired.emplace_back(cursor, end); + for (std::size_t index = 0; index < unwired.size(); ++index) { + if (::mlock(reinterpret_cast(unwired[index].first), unwired[index].second - unwired[index].first) != 0) { + const int error = errno; + for (std::size_t wired = 0; wired < index; ++wired) ::munlock(reinterpret_cast(unwired[wired].first), unwired[wired].second - unwired[wired].first); + return error; + } + } + RecordLockedRangeLocked(start, end); + return 0; +} +#else +int WireHostPages(std::uintptr_t start, std::uintptr_t end) { + if (::mlock(reinterpret_cast(start), end - start) != 0) return errno; + RecordLockedRange(start, end); + return 0; +} +#endif + int LockHostPages(std::uintptr_t start, std::uintptr_t end) { - auto* const address = reinterpret_cast(start); - const std::size_t bytes = end - start; #ifdef __APPLE__ for (mach_vm_address_t cursor = start; cursor < end;) { mach_vm_address_t region = cursor; @@ -708,20 +800,14 @@ int LockHostPages(std::uintptr_t start, std::uintptr_t end) { cursor = region + size; } #endif - if (::mlock(address, bytes) == 0) { - RecordLockedRange(start, end); - return 0; - } - int error = errno; + int error = WireHostPages(start, end); + if (error == 0) return 0; rlimit limit{}; if ((error == ENOMEM || error == EPERM || error == EAGAIN) && getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur < limit.rlim_max) { limit.rlim_cur = limit.rlim_max; if (setrlimit(RLIMIT_MEMLOCK, &limit) == 0) { - if (::mlock(address, bytes) == 0) { - RecordLockedRange(start, end); - return 0; - } - error = errno; + error = WireHostPages(start, end); + if (error == 0) return 0; } } switch (error) { @@ -734,10 +820,18 @@ int LockHostPages(std::uintptr_t start, std::uintptr_t end) { int UnlockHostPages(std::uintptr_t start, std::uintptr_t end) { if (!GuestRangeMapped(start, end)) return SCE_KERNEL_ERROR_ENOMEM; +#ifdef __APPLE__ + std::lock_guard lock(g_lockedRangesLock); + if (::munlock(reinterpret_cast(start), end - start) == 0) { + ForgetLockedRangeLocked(start, end); + return 0; + } +#else if (::munlock(reinterpret_cast(start), end - start) == 0) { ForgetLockedRange(start, end); return 0; } +#endif const int error = errno; if (error == ENOMEM) return SCE_KERNEL_ERROR_ENOMEM; throw std::system_error(error, std::generic_category(), "munlock failed"); @@ -765,13 +859,18 @@ int APS5_VABI sceKernelMunlock_nid_postfix(void* address, std::uint64_t length) } int APS5_VABI sceKernelMunlockall_nid_postfix() { -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(__APPLE__) ::munlockall(); #endif std::map ranges; { std::lock_guard lock(g_lockedRangesLock); ranges.swap(g_lockedRanges); +#ifdef __APPLE__ + const int saved = errno; + for (const auto& [start, len] : ranges) ::munlock(reinterpret_cast(start), len); + errno = saved; +#endif } #ifdef _WIN32 for (const auto& [start, len] : ranges) { diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index 1bee0fd94a..c3d1691407 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -167,9 +167,28 @@ void KernelEqueuePrivate::MeasureFiles(std::unique_lock& lock) { if (files.empty()) { return; } - lock.unlock(); std::vector measured; measured.reserve(files.size()); + m_measuring.reserve(m_measuring.size() + files.size()); + struct Measuring { + KernelEqueuePrivate& queue; + std::unique_lock& lock; + const std::vector>>& files; + ~Measuring() { + if (!lock.owns_lock()) { + lock.lock(); + } + for (const auto& [file, owner] : files) { + queue.m_measuring.erase(std::find(queue.m_measuring.begin(), queue.m_measuring.end(), file)); + } + queue.m_measured.notify_all(); + } + }; + for (const auto& [file, owner] : files) { + m_measuring.push_back(file); + } + const Measuring measuring{*this, lock, files}; + lock.unlock(); for (const auto& [file, owner] : files) { measured.push_back(UnreadFileBytes(file)); } @@ -582,6 +601,7 @@ void KernelEqueuePrivate::RemoveDescriptorEvents(uintptr_t ident) { if (removed) { WakePollers(); } + m_measured.wait(lock, [&] { return std::find(m_measuring.begin(), m_measuring.end(), static_cast(ident)) == m_measuring.end(); }); } void KernelEqueuePrivate::WakeReadWaiters(uintptr_t ident) { diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index f7aab3004a..4cace734bd 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -115,7 +116,9 @@ private: std::list m_events; std::vector m_pollers; + std::vector m_measuring; std::mutex m_mutex; + std::condition_variable m_measured; TimedWait::Condition m_cond; std::string m_name; KernelEqueue m_handle; @@ -128,7 +131,7 @@ void EqueueDescriptorClosed(int descriptor); void KqueueDescriptorClosed(int descriptor); void EqueueDescriptorReadShut(int descriptor); void KqueueDescriptorReadShut(int descriptor); -bool IsKqueueDescriptor(int descriptor); +extern "C" bool IsKqueueDescriptor_nid_no_patch(int descriptor); std::optional KqueueReadable(int descriptor); KernelEqueueEvent EqueueDescriptorEvent(int descriptor, int16_t filter, uint16_t flags, void* udata); void RequireVnodeNotes(uint32_t fflags); diff --git a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp index a0b5e49fd3..a9126e1e7e 100644 --- a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp +++ b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp @@ -695,7 +695,7 @@ int VnodeDescriptorError(int descriptor) { if (!DescriptorIsOpen_nid_no_patch(descriptor)) { return GUEST_EBADF; } - return IsKqueueDescriptor(descriptor) ? GUEST_EINVAL : 0; + return IsKqueueDescriptor_nid_no_patch(descriptor) ? GUEST_EINVAL : 0; } } diff --git a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp index a5f6311d5b..eea3d985ab 100644 --- a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp +++ b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp @@ -363,20 +363,20 @@ int ModifyTimerKnote(const KernelEqueueRef& queue, KernelEvent& change) { if ((change.flags & ~(EV_ADD | EV_ONESHOT)) != 0) { NotImplemented_nid_no_patch("kevent: re-adding an EVFILT_TIMER with flags other than EV_ONESHOT"); } - if (change.data <= 0 || change.fflags != 0) { - NotImplemented_nid_no_patch("kevent: re-adding an EVFILT_TIMER with nonzero fflags or a period of 0 or less"); - } const auto periodNs = TimerPeriodNanos(change.data, change.fflags); + if (change.data <= 0 || (change.fflags & ~NOTE_TIMER_PRECMASK) != 0 || !periodNs) { + NotImplemented_nid_no_patch("kevent: re-adding an EVFILT_TIMER with a period of 0 or less or an unknown unit"); + } bool oneShot = false; - bool disabled = false; bool overflow = false; - const int error = GuestErrno(queue->EditEvent(change.ident, change.filter, [&change, &oneShot, &disabled, &overflow, periodNs](KernelEqueueEvent& event) { + const int error = GuestErrno(queue->EditEvent(change.ident, change.filter, [&change, &oneShot, &overflow, periodNs](KernelEqueueEvent& event) { if (event.intervalNs == 0) { oneShot = true; return; } if (!event.enabled) { - disabled = true; + event.intervalNs = *periodNs; + event.event.udata = change.udata; return; } if (*periodNs > std::numeric_limits::max() - event.deadlineNs) { @@ -389,9 +389,6 @@ int ModifyTimerKnote(const KernelEqueueRef& queue, KernelEvent& change) { if (oneShot) { NotImplemented_nid_no_patch("kevent: re-adding a one-shot EVFILT_TIMER"); } - if (disabled) { - NotImplemented_nid_no_patch("kevent: re-adding a disabled EVFILT_TIMER"); - } if (overflow) { NotImplemented_nid_no_patch("kevent: re-adding an EVFILT_TIMER with a period that overflows the armed expiry"); } @@ -514,7 +511,7 @@ std::optional ScanDeadline(const KernelTimespec& timeout) { } -bool IsKqueueDescriptor(int descriptor) { +bool IsKqueueDescriptor_nid_no_patch(int descriptor) { return static_cast(FindKqueue(descriptor)); } diff --git a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp index 29f13e6df2..65ae972430 100644 --- a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp +++ b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp @@ -1,4 +1,3 @@ -#include "prx/libSceSaveData/SaveDataCore.hpp" #include "prx/libkernel/include/Api.hpp" #include #include diff --git a/core/libs/prx/libkernel/File/include/SharedMemory.hpp b/core/libs/prx/libkernel/File/include/SharedMemory.hpp index 7d9be97385..8890cc4ab4 100644 --- a/core/libs/prx/libkernel/File/include/SharedMemory.hpp +++ b/core/libs/prx/libkernel/File/include/SharedMemory.hpp @@ -9,7 +9,7 @@ namespace File { -bool IsSharedMemoryDescriptor_nid_no_patch(int descriptor); +extern "C" bool IsSharedMemoryDescriptor_nid_no_patch(int descriptor); std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size); std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size, std::int64_t offset); std::int64_t SeekSharedMemory_nid_no_patch(int descriptor, std::int64_t offset, int whence); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index fa7e9ac461..b94988ef1f 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -490,7 +490,7 @@ static bool IsSeekableDescriptor(int descriptor) { } static int PositionedArgumentError(int descriptor, bool write, bool nullBuffer, std::int64_t offset) { - if (IsKqueueDescriptor(descriptor)) return GUEST_ESPIPE; + if (IsKqueueDescriptor_nid_no_patch(descriptor)) return GUEST_ESPIPE; if (!nullBuffer && offset >= 0) return 0; if (!GuestSockets::IsOpen(descriptor) && !DescriptorIsOpen_nid_no_patch(descriptor)) return GUEST_EBADF; if (!IsSeekableDescriptor(descriptor)) return GUEST_ESPIPE; @@ -753,20 +753,26 @@ int ChmodNative_nid_no_patch(const std::filesystem::path& native, int mode) { return NativeChmod(native, mode); } -int APS5_VABI chflags_nid_postfix(const char* path, std::uint64_t flags) { +int ChangeFileFlags_nid_no_patch(const char* function, const char* path, std::uint64_t flags, bool follow) { std::string copy; if (const int error = PathError(path, copy)) return PosixFailure(error); path = copy.c_str(); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error); - if (const int error = LookupError(native)) return PosixFailure(error); - if (const int error = SetFileFlags("chflags", path, flags)) return PosixFailure(error); + FileStat linkStatus{}; + const bool link = !follow && native.has_filename() && File::FillLinkStat(native, &linkStatus) && (linkStatus.st_mode & 0170000) == 0120000; + if (const int error = link ? 0 : LookupError(native)) return PosixFailure(error); + if (const int error = SetFileFlags(function, path, flags)) return PosixFailure(error); return 0; } +int APS5_VABI chflags_nid_postfix(const char* path, std::uint64_t flags) { + return ChangeFileFlags_nid_no_patch("chflags", path, flags, true); +} + int APS5_VABI fchflags_nid_postfix(int d, std::uint64_t flags) { if (File::IsVirtualDirectoryDescriptor(d)) return PosixFailure(GUEST_EISDIR); - if (GuestSockets::IsOpen(d) || IsKqueueDescriptor(d) || File::IsSharedMemoryDescriptor_nid_no_patch(d)) return PosixFailure(GUEST_EINVAL); + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor_nid_no_patch(d) || File::IsSharedMemoryDescriptor_nid_no_patch(d)) return PosixFailure(GUEST_EINVAL); File::RefuseArchivedFile(d, "fchflags"); if (!File::IsVnode(d)) { if (!DescriptorIsOpen_nid_no_patch(d)) return PosixFailure(GUEST_EBADF); @@ -842,7 +848,7 @@ extern "C" { int APS5_VABI dup_nid_postfix(int d) { if (GuestSockets::IsOpen(d)) return GuestSockets::Duplicate(d); if (d < 0) return PosixFailure(GUEST_EBADF); - if (IsKqueueDescriptor(d)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported"); + if (IsKqueueDescriptor_nid_no_patch(d)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported"); #ifdef _WIN32 const int duplicate = WithoutParameterHandler([d] { return ::_dup(d); }); if (duplicate >= 0) File::DuplicateSharedMemoryDescriptor_nid_no_patch(d, duplicate); @@ -873,19 +879,19 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { return to; } const bool sourceIsRandom = File::IsRandomDevice(from); - if (from != to && IsKqueueDescriptor(from)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported"); + if (from != to && IsKqueueDescriptor_nid_no_patch(from)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported"); #ifdef _WIN32 if (WithoutParameterHandler([from] { return ::_get_osfhandle(from) == -1 ? -1 : 0; }) != 0) return PosixFailure(GUEST_EBADF); if (from == to) return to; if (to == 1) PreserveHostStandardOutput(); if (to == 2) PreserveHostStandardError(); + EqueueDescriptorClosed(to); auto positioned = File::LockPositionedTransfer(to); auto* const standard = HostStandardHandle_nid_no_patch(to); int duplicated = File::ReplaceSharedMemoryDescriptor_nid_no_patch(from, to); if (duplicated == -2) duplicated = WithoutParameterHandler([from, to] { return ::_dup2(from, to); }); KeepStandardHandle_nid_no_patch(to, standard); if (duplicated != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); - EqueueDescriptorClosed(to); GuestSockets::Forget(to); File::ForgetDirectoryDescriptor(to); File::DuplicateDirectoryDescriptor(from, to); @@ -953,7 +959,7 @@ int APS5_VABI flock_nid_postfix(int d, int operation) { int APS5_VABI _fstatfs_nid_postfix(int d, KernelStatfs* buf) { if (File::IsVirtualDirectoryDescriptor(d)) return PosixFailure(GUEST_EOPNOTSUPP); - if (GuestSockets::IsOpen(d) || IsKqueueDescriptor(d)) return PosixFailure(GUEST_EINVAL); + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor_nid_no_patch(d)) return PosixFailure(GUEST_EINVAL); if (d < 0) return PosixFailure(GUEST_EBADF); File::RefuseArchivedFile(d, "fstatfs"); KernelStatfs result{}; @@ -1639,8 +1645,10 @@ int APS5_VABI sceKernelRmdir(const char* path) { if (const int dot = RenameDotError_nid_no_patch(path)) return SceKernelError(dot); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); + FileStat linkStatus{}; + if (native.has_filename() && File::FillLinkStat(native, &linkStatus) && (linkStatus.st_mode & 0170000) == 0120000) return SceErrorFromErrno(GUEST_ENOTDIR); std::error_code error; - if (std::filesystem::is_symlink(native, error)) return SceErrorFromErrno(GUEST_ENOTDIR); + const auto status = std::filesystem::status(native, error); if (const int failure = PathLookupFailure(error)) return failure; #ifdef _WIN32 const DWORD attributes = GetFileAttributesW(native.c_str()); @@ -1649,7 +1657,7 @@ int APS5_VABI sceKernelRmdir(const char* path) { if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "rmdir")) return SceErrorFromErrno(GUEST_ENOTDIR); } #endif - if (!std::filesystem::is_directory(native, error)) { + if (!std::filesystem::is_directory(status)) { #ifdef _WIN32 if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(native).parent_path())) return SceKernelError(GUEST_ELOOP); #endif @@ -1716,7 +1724,12 @@ int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t lengt std::error_code error; const auto status = std::filesystem::status(native, error); if (const int failure = PathLookupFailure(error)) return failure; - if (!std::filesystem::exists(status)) return SceErrorFromErrno(NotFoundError_nid_no_patch(native)); + if (!std::filesystem::exists(status)) { +#ifdef _WIN32 + if (File::WindowsSymbolicLinkLoop(native)) return SceKernelError(GUEST_ELOOP); +#endif + return SceErrorFromErrno(NotFoundError_nid_no_patch(native)); + } if (std::filesystem::is_directory(status)) return SceErrorFromErrno(GUEST_EISDIR); if (const int failure = NativeTruncate(native, length)) { #ifdef _WIN32 diff --git a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp index 4c6d721513..35db4e3053 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp @@ -73,7 +73,8 @@ int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, Kerne } int APS5_VABI pthread_condattr_getclock_nid_postfix(const PthreadCondattr* attr, KernelClockid* clockId) { - if (!attr || !*attr || !clockId) NotImplemented_nid_no_patch(__func__); + if (!attr || !*attr) return PosixThread::GUEST_EINVAL; + if (!clockId) NotImplemented_nid_no_patch(__func__); *clockId = (*attr)->_clockid; return 0; } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp index f1fb10ee94..f1bc6ec04b 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp @@ -165,6 +165,7 @@ int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int int APS5_VABI pthread_mutexattr_gettype_nid_postfix(const PthreadMutexattr* attr, int* type) { if (!attr || !*attr) return POSIX_EINVAL; if (!type) throw std::runtime_error("pthread_mutexattr_gettype: null type"); + if (static_cast((*attr)->type) > static_cast(MutexType::Adaptive)) return POSIX_EINVAL; *type = static_cast((*attr)->type); return 0; } @@ -174,9 +175,7 @@ int APS5_VABI pthread_mutexattr_setkind_np_nid_postfix(PthreadMutexattr* attr, i *__error_nid_postfix() = POSIX_EINVAL; return -1; } - if (kind < static_cast(MutexType::ErrorCheck) || kind > static_cast(MutexType::Adaptive)) - throw std::runtime_error("pthread_mutexattr_setkind_np: kind " + std::to_string(kind) + " is not a mutex type, and pthread_mutex_init does not reject it with EINVAL"); - (*attr)->type = static_cast(kind); + (*attr)->type = static_cast(static_cast(static_cast(kind))); return 0; } diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index d7a80424cb..a7b152207f 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -170,6 +170,7 @@ struct PthreadPrivate { std::atomic cancelPending{false}; std::mutex cancelLock; TimedWait::Condition* cancelWait = nullptr; + bool cancelWaitExiting = false; explicit PthreadPrivate(const PthreadAttrPrivate* attr = nullptr); ~PthreadPrivate(); diff --git a/core/libs/prx/libkernel/Pthread/src/GuestTlsMacOs.cpp b/core/libs/prx/libkernel/Pthread/src/GuestTlsMacOs.cpp new file mode 100644 index 0000000000..49596b9cb3 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/src/GuestTlsMacOs.cpp @@ -0,0 +1,183 @@ +#ifdef __APPLE__ + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct GuestTlsDescriptor { + std::uint64_t version; + std::uint64_t templateAddress; + std::uint64_t templateSize; + std::uint64_t blockSize; + std::uint64_t alignment; + std::uint64_t key; +}; + +struct TlsIndex { + const GuestTlsDescriptor* module; + std::uint64_t offset; +}; + +constexpr std::size_t ThreadControlBlockBytes = 0x100; +constexpr std::size_t AllocationSlot = ThreadControlBlockBytes - sizeof(void*); + +[[noreturn]] void Fail(const char* reason) { + std::fprintf(stderr, "guest TLS: %s\n", reason); + std::abort(); +} + +void Release(void* threadPointer) { + std::free(*reinterpret_cast(static_cast(threadPointer) + AllocationSlot)); +} + +} + +extern "C" { + +std::uint8_t Aps5GuestTlsAvx_nid_no_patch = [] { + __builtin_cpu_init(); + return __builtin_cpu_supports("avx") ? 1 : 0; +}(); + +void Aps5GuestTlsRegister_nid_no_patch(GuestTlsDescriptor* descriptor) { + if (descriptor == nullptr || descriptor->version != 1) Fail("unknown TLS descriptor"); + if (!std::has_single_bit(descriptor->alignment) || descriptor->blockSize % descriptor->alignment != 0 || descriptor->templateSize > descriptor->blockSize) + Fail("invalid TLS descriptor layout"); + pthread_key_t key; + if (pthread_key_create(&key, Release) != 0) Fail("cannot create a thread pointer key"); + descriptor->key = key; +} + +void* Aps5GuestTlsAllocateThreadPointer_nid_no_patch(const GuestTlsDescriptor* descriptor) { + if (descriptor == nullptr || descriptor->version != 1) Fail("unknown TLS descriptor"); + void* base = nullptr; + const auto alignment = std::max(descriptor->alignment, 16); + if (posix_memalign(&base, alignment, descriptor->blockSize + ThreadControlBlockBytes) != 0) Fail("cannot allocate a TLS block"); + auto* bytes = static_cast(base); + if (descriptor->templateSize != 0) + std::memcpy(bytes, reinterpret_cast(descriptor->templateAddress), descriptor->templateSize); + std::memset(bytes + descriptor->templateSize, 0, descriptor->blockSize - descriptor->templateSize + ThreadControlBlockBytes); + auto* threadPointer = bytes + descriptor->blockSize; + *reinterpret_cast(threadPointer) = threadPointer; + *reinterpret_cast(threadPointer + AllocationSlot) = base; + if (pthread_setspecific(static_cast(descriptor->key), threadPointer) != 0) Fail("cannot store the thread pointer"); + return threadPointer; +} + +void* Aps5GuestTlsGetAddr_nid_no_patch(const TlsIndex* index) { + const auto* descriptor = index->module; + auto* threadPointer = static_cast(pthread_getspecific(static_cast(descriptor->key))); + if (threadPointer == nullptr) threadPointer = static_cast(Aps5GuestTlsAllocateThreadPointer_nid_no_patch(descriptor)); + return threadPointer - descriptor->blockSize + index->offset; +} + +} + +asm(R"( + .text + .globl _Aps5GuestTlsAllocate_nid_no_patch +_Aps5GuestTlsAllocate_nid_no_patch: + push %rbp + mov %rsp, %rbp + push %rcx + push %rdx + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + and $-64, %rsp + sub $512, %rsp + mov %rax, %rdi + testb $1, _Aps5GuestTlsAvx_nid_no_patch(%rip) + jz 1f + vmovdqu %ymm0, 0(%rsp) + vmovdqu %ymm1, 32(%rsp) + vmovdqu %ymm2, 64(%rsp) + vmovdqu %ymm3, 96(%rsp) + vmovdqu %ymm4, 128(%rsp) + vmovdqu %ymm5, 160(%rsp) + vmovdqu %ymm6, 192(%rsp) + vmovdqu %ymm7, 224(%rsp) + vmovdqu %ymm8, 256(%rsp) + vmovdqu %ymm9, 288(%rsp) + vmovdqu %ymm10, 320(%rsp) + vmovdqu %ymm11, 352(%rsp) + vmovdqu %ymm12, 384(%rsp) + vmovdqu %ymm13, 416(%rsp) + vmovdqu %ymm14, 448(%rsp) + vmovdqu %ymm15, 480(%rsp) + call _Aps5GuestTlsAllocateThreadPointer_nid_no_patch + vmovdqu 0(%rsp), %ymm0 + vmovdqu 32(%rsp), %ymm1 + vmovdqu 64(%rsp), %ymm2 + vmovdqu 96(%rsp), %ymm3 + vmovdqu 128(%rsp), %ymm4 + vmovdqu 160(%rsp), %ymm5 + vmovdqu 192(%rsp), %ymm6 + vmovdqu 224(%rsp), %ymm7 + vmovdqu 256(%rsp), %ymm8 + vmovdqu 288(%rsp), %ymm9 + vmovdqu 320(%rsp), %ymm10 + vmovdqu 352(%rsp), %ymm11 + vmovdqu 384(%rsp), %ymm12 + vmovdqu 416(%rsp), %ymm13 + vmovdqu 448(%rsp), %ymm14 + vmovdqu 480(%rsp), %ymm15 + jmp 2f +1: + movdqu %xmm0, 0(%rsp) + movdqu %xmm1, 32(%rsp) + movdqu %xmm2, 64(%rsp) + movdqu %xmm3, 96(%rsp) + movdqu %xmm4, 128(%rsp) + movdqu %xmm5, 160(%rsp) + movdqu %xmm6, 192(%rsp) + movdqu %xmm7, 224(%rsp) + movdqu %xmm8, 256(%rsp) + movdqu %xmm9, 288(%rsp) + movdqu %xmm10, 320(%rsp) + movdqu %xmm11, 352(%rsp) + movdqu %xmm12, 384(%rsp) + movdqu %xmm13, 416(%rsp) + movdqu %xmm14, 448(%rsp) + movdqu %xmm15, 480(%rsp) + call _Aps5GuestTlsAllocateThreadPointer_nid_no_patch + movdqu 0(%rsp), %xmm0 + movdqu 32(%rsp), %xmm1 + movdqu 64(%rsp), %xmm2 + movdqu 96(%rsp), %xmm3 + movdqu 128(%rsp), %xmm4 + movdqu 160(%rsp), %xmm5 + movdqu 192(%rsp), %xmm6 + movdqu 224(%rsp), %xmm7 + movdqu 256(%rsp), %xmm8 + movdqu 288(%rsp), %xmm9 + movdqu 320(%rsp), %xmm10 + movdqu 352(%rsp), %xmm11 + movdqu 384(%rsp), %xmm12 + movdqu 416(%rsp), %xmm13 + movdqu 448(%rsp), %xmm14 + movdqu 480(%rsp), %xmm15 +2: + lea -64(%rbp), %rsp + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rdx + pop %rcx + pop %rbp + ret +)"); + +#endif diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index 8525c3d088..3c3b7b8b89 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -5,7 +5,6 @@ #include "../include/Mutex.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" -#include "prx/libc/include/General.hpp" #include #include #include @@ -13,7 +12,6 @@ #include #include #include -#include namespace { @@ -144,10 +142,7 @@ int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) { } int APS5_VABI scePthreadMutexattrSetkind(PthreadMutexattr* attr, int kind) { - if (!attr || !*attr) return sceKernelError(posixFailure); - if (kind < static_cast(MutexType::ErrorCheck) || kind > static_cast(MutexType::Adaptive)) - NotImplemented_nid_no_patch("scePthreadMutexattrSetkind with a kind other than 1 to 4"); - return scePthreadMutexattrSettype(attr, kind); + return sceKernelError(pthread_mutexattr_setkind_np_nid_postfix(attr, kind)); } int APS5_VABI scePthreadMutexattrGetkind(PthreadMutexattr attr) { @@ -170,6 +165,8 @@ int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) { int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) { if (!mutex) throw std::invalid_argument("Mutex pointer is null"); + if (attr && *attr && ((*attr)->type < MutexType::ErrorCheck || (*attr)->type > MutexType::Adaptive)) + return SCE_KERNEL_ERROR_EINVAL; auto replacement = std::make_unique(); if (attr && *attr) replacement->SetType((*attr)->type); @@ -279,8 +276,10 @@ int APS5_VABI scePthreadMutexGetyieldloops(PthreadMutex* mutex, int* count) { } int APS5_VABI scePthreadMutexSetyieldloops(PthreadMutex* mutex, int count) { - if (auto* current = resolveMutex(mutex, true)) - current->_yieldloops = count; + auto* current = resolveMutex(mutex, true); + if (!current) + return SCE_KERNEL_ERROR_EINVAL; + current->_yieldloops = count; return 0; } diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 62dc543ec9..ce41edb7ce 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -751,9 +751,11 @@ void ThreadCancel::CleanupRestore(void* mark) { } void ThreadCancel::BeginWait(TimedWait::Condition* condition) { - if (!CurrentGuestState().current) return; - std::lock_guard lock(CurrentGuestState().current->cancelLock); - CurrentGuestState().current->cancelWait = condition; + auto& state = CurrentGuestState(); + if (!state.current) return; + std::lock_guard lock(state.current->cancelLock); + state.current->cancelWait = condition; + state.current->cancelWaitExiting = condition && state.exiting; } void ThreadCancel::EndWait() { @@ -1227,7 +1229,7 @@ int APS5_VABI scePthreadCancel(Pthread thread) { for (;;) { { std::lock_guard lock(thread->cancelLock); - if (!thread->cancelWait) return SCE_OK; + if (!thread->cancelWait || thread->cancelWaitExiting) return SCE_OK; thread->cancelWait->NotifyAll(); } scePthreadYield(); diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 399e530a79..58d350bf50 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -210,7 +210,7 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* kernel.entries.push_back({descriptor, events, 0}); kernel.slots.push_back(requested.size()); requested.push_back({descriptor, events, 0}); - } else if (IsKqueueDescriptor(descriptor)) { + } else if (IsKqueueDescriptor_nid_no_patch(descriptor)) { kqueues.entries.push_back({descriptor, events, 0}); kqueues.slots.push_back(requested.size()); requested.push_back({descriptor, events, 0}); diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 1131f91a2a..de7b3a11d1 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -1869,7 +1869,7 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c ++ready; continue; } - if (IsKqueueDescriptor(entry.descriptor)) { + if (IsKqueueDescriptor_nid_no_patch(entry.descriptor)) { kqueues.push_back(i); continue; } diff --git a/core/libs/prx/libkernel/System/include/UserContext.hpp b/core/libs/prx/libkernel/System/include/UserContext.hpp index 485c68cf3c..b6205b21c4 100644 --- a/core/libs/prx/libkernel/System/include/UserContext.hpp +++ b/core/libs/prx/libkernel/System/include/UserContext.hpp @@ -67,4 +67,6 @@ static_assert(offsetof(UserUcontext, stackSize) == 0x340); static_assert(offsetof(UserUcontext, flags) == 0x350); static_assert(sizeof(UserUcontext) == 0x370); +extern "C" int SigreturnContext_nid_no_patch(const UserUcontext* context); + #endif diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index b0ec9a9df8..363606d882 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -319,6 +319,19 @@ bool Exited(HANDLE native) { return WaitForSingleObject(native, 0) == WAIT_OBJECT_0; } +using NtSuspendThreadFunction = LONG(NTAPI*)(HANDLE, PULONG); +constexpr LONG ThreadIsTerminating = static_cast(0xc000004bu); + +LONG SuspendTarget(HANDLE native) { + static const auto suspend = [] { + const auto found = reinterpret_cast(reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtSuspendThread"))); + if (found == nullptr) throw std::runtime_error("sceKernelRaiseException: ntdll.dll has no NtSuspendThread"); + return found; + }(); + ULONG previous = 0; + return suspend(native, &previous); +} + bool RestoringContext(DWORD64 rip) { static const std::array stubs = [] { const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); @@ -695,7 +708,9 @@ Attempt TryDeliver(Pthread thread, HANDLE native, GuestExceptionHandler handler, alignas(16) Delivery delivery{handler, signum, {}, {}, SaveVectors ? 1u : 0u}; const auto contextDeadline = std::chrono::steady_clock::now() + RetryLimit; for (;;) { - if (SuspendThread(native) == static_cast(-1)) { + const LONG suspended = SuspendTarget(native); + if (suspended == ThreadIsTerminating) return Attempt::Exited; + if (suspended < 0) { if (Exited(native)) return Attempt::Exited; throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread"); } diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index f5493eb570..c3cbccd2e1 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -461,7 +461,7 @@ extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); -extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output) { +static int GetitimerInto(int which, GuestItimerval* output) { if (which < 0 || which > 2) { *__error_nid_postfix() = 22; return -1; @@ -492,6 +492,16 @@ extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output return 0; } +extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output) { + GuestItimerval result{}; + const int status = GetitimerInto(which, output == nullptr ? nullptr : &result); + if (status == 0 && !GuestCopyOut_nid_no_patch(output, &result, sizeof(result))) { + *__error_nid_postfix() = 14; + return -1; + } + return status; +} + namespace { constexpr int ItimerReal = 0; constexpr int ItimerProf = 2; @@ -519,7 +529,7 @@ extern "C" int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* if (value == nullptr && previous == nullptr) { *__error_nid_postfix() = 14; return -1; } GuestItimerval requested{}; if (value != nullptr) { - requested = *value; + if (!GuestCopyIn_nid_no_patch(&requested, value, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } if (!ValidItimer(requested.value)) { *__error_nid_postfix() = 22; return -1; } if (!ItimerSet(requested.value)) requested.interval = {}; else if (!ValidItimer(requested.interval)) { *__error_nid_postfix() = 22; return -1; } @@ -527,7 +537,10 @@ extern "C" int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* if (which != ItimerReal) { if (ItimerSet(requested.value)) throw std::runtime_error(std::string("setitimer: ") + (which == ItimerProf ? "ITIMER_PROF" : "ITIMER_VIRTUAL") + " needs process CPU time accounting, which is not implemented"); - if (previous != nullptr) *previous = {}; + if (previous != nullptr) { + const GuestItimerval cleared{}; + if (!GuestCopyOut_nid_no_patch(previous, &cleared, sizeof(cleared))) { *__error_nid_postfix() = 14; return -1; } + } return 0; } if (requested.interval.tv_sec == 0 && requested.interval.tv_usec != 0 && requested.interval.tv_usec < MinExactIntervalMicros) @@ -546,7 +559,7 @@ extern "C" int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* NotifyAlarms(alarms); } } - if (previous != nullptr) *previous = old; + if (previous != nullptr && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; } return 0; } @@ -559,7 +572,7 @@ thread_local GuestStack alternateStack{nullptr, 0, SsDisable}; namespace { -int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr) { +int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr, GuestSignalSet* guestPrevious = nullptr) { if (set != nullptr && (how < 1 || how > 3)) { *__error_nid_postfix() = 22; return -1; } { std::lock_guard lock(registration); @@ -593,8 +606,10 @@ int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previo } } } + const bool faulted = guestPrevious != nullptr && !GuestCopyOut_nid_no_patch(guestPrevious, previousSet, sizeof(*previousSet)); std::atomic_thread_fence(std::memory_order_seq_cst); if (DeliverUnblockedPending() && caught != nullptr) *caught = true; + if (faulted) { *__error_nid_postfix() = 14; return -1; } return 0; } @@ -620,6 +635,11 @@ GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) { return previous; } int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) { + GuestSigaction requested{}; + if (action != nullptr) { + if (!GuestCopyIn_nid_no_patch(&requested, action, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } + action = &requested; + } if (guest < 1 || guest > MaxSignal) { *__error_nid_postfix() = 22; return -1; } if (action && action->handler > 1 && (action->flags & ~SaDefinedFlags) != 0) { *__error_nid_postfix() = 22; return -1; } if (action && (guest == GuestSigkill || guest == GuestSigstop) && action->handler != 0) { *__error_nid_postfix() = 22; return -1; } @@ -631,7 +651,7 @@ int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, Gue dispositions[guest] = *action; DiscardPendingIfIgnored(guest, *action); } - if (previous) *previous = old; + if (previous && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; } return 0; } int APS5_VABI _sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) { @@ -681,18 +701,27 @@ int APS5_VABI pthread_kill_nid_postfix(Pthread thread, int guest) { } int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previous) { GuestStack replacement = alternateStack; + GuestStack requested{}; if (stack != nullptr) { + if (!GuestCopyIn_nid_no_patch(&requested, stack, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } + stack = &requested; if ((stack->flags & ~SsDisable) != 0) { *__error_nid_postfix() = 22; return -1; } if (stack->flags & SsDisable) replacement.flags = SsDisable; else if (stack->size < MinSignalStackSize) { *__error_nid_postfix() = 12; return -1; } else replacement = {stack->sp, stack->size, 0}; } - if (previous != nullptr) *previous = alternateStack; + const GuestStack old = alternateStack; alternateStack = replacement; + if (previous != nullptr && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; } return 0; } int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) { - return ChangeBlockedMask(how, set, previousSet, true); + GuestSignalSet requested{}, old{}; + if (set != nullptr) { + if (!GuestCopyIn_nid_no_patch(&requested, set, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; } + set = &requested; + } + return ChangeBlockedMask(how, set, previousSet != nullptr ? &old : nullptr, true, nullptr, previousSet); } int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet) { @@ -701,8 +730,10 @@ int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSe } int APS5_VABI pthread_sigmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) { - if (set != nullptr && (how < 1 || how > 3)) return 22; - return _sigprocmask_nid_postfix(how, set, previousSet); + const int saved = *__error_nid_postfix(); + const int error = _sigprocmask_nid_postfix(how, set, previousSet) == 0 ? 0 : *__error_nid_postfix(); + *__error_nid_postfix() = saved; + return error; } int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const KernelTimespec* timeout) { @@ -762,10 +793,14 @@ int APS5_VABI _sigsuspend_nid_postfix(const GuestSignalSet* set) { int APS5_VABI sigpending_nid_postfix(GuestSignalSet* set) { if (set == nullptr) { *__error_nid_postfix() = 14; return -1; } - auto& pending = Pending(); - std::lock_guard lock(pending.lock); - for (int word = 0; word < 4; ++word) - set->bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word); + GuestSignalSet result{}; + { + auto& pending = Pending(); + std::lock_guard lock(pending.lock); + for (int word = 0; word < 4; ++word) + result.bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word); + } + if (!GuestCopyOut_nid_no_patch(set, &result, sizeof(result))) { *__error_nid_postfix() = 14; return -1; } return 0; } } diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index b6c40105c9..9a11a06d82 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -27,14 +28,21 @@ namespace { +int Fault() { + *__error_nid_postfix() = 14; + return -1; +} + int CopyOut(const void* value, std::size_t size, void* oldValue, std::size_t* oldLength) { if (oldValue == nullptr) { - if (oldLength != nullptr) *oldLength = size; + if (oldLength != nullptr && !GuestCopyOut_nid_no_patch(oldLength, &size, sizeof(size))) return Fault(); return 0; } - const std::size_t copied = std::min(oldLength != nullptr ? *oldLength : 0, size); - if (copied != 0) std::memcpy(oldValue, value, copied); - if (oldLength != nullptr) *oldLength = copied; + std::size_t capacity = 0; + if (oldLength != nullptr && !GuestCopyIn_nid_no_patch(&capacity, oldLength, sizeof(capacity))) return Fault(); + const std::size_t copied = std::min(capacity, size); + if (!GuestCopyOut_nid_no_patch(oldValue, value, copied)) return Fault(); + if (oldLength != nullptr && !GuestCopyOut_nid_no_patch(oldLength, &copied, sizeof(copied))) return Fault(); if (copied < size) { *__error_nid_postfix() = 12; return -1; @@ -191,6 +199,32 @@ bool IsUnansweredMib(const int* name, std::uint32_t nameLength) { return (name[0] == 1 && (name[1] == 6 || name[1] == 7 || name[1] == 12 || name[1] == 27 || name[1] == 32)) || (name[0] == 6 && (name[1] == 4 || name[1] == 11)); } +struct SysconfConstant { + int name; + std::int64_t value; +}; + +constexpr SysconfConstant kSysconfConstants[] = { + {6, 1}, {7, -1}, {8, 200112}, {9, 99}, {10, 2048}, {11, 99}, {12, 1000}, {13, 0}, {14, 32}, + {15, 2048}, {16, 255}, {17, 199212}, {18, 200112}, {19, -1}, {20, 1}, {21, -1}, {22, 200112}, + {23, -1}, {24, -1}, {25, 200112}, {28, 200112}, {29, 200112}, {30, -1}, {31, 200112}, {32, 200112}, + {33, 200112}, {34, -1}, {35, 200112}, {36, 200112}, {37, 200112}, {38, 200112}, {39, 200112}, + {40, -1}, {41, 200112}, {42, 16}, {43, 1024}, {44, 0}, {45, 2147483647}, {46, -1}, {48, 62}, + {49, 30}, {50, 2147483647}, {51, 128}, {52, 32}, {59, -1}, {60, -1}, {61, -1}, {62, -1}, {63, -1}, + {64, -1}, {65, 200112}, {66, 200112}, {67, -1}, {68, 200112}, {74, 200112}, {75, 64}, {76, 200112}, + {77, 1}, {78, 1}, {79, 200112}, {80, 200112}, {81, -1}, {82, 200112}, {83, 200112}, {84, 200112}, + {85, 4}, {86, 256}, {87, 200112}, {88, 200112}, {89, 200112}, {90, -1}, {91, -1}, {93, 16384}, + {94, -1}, {95, 200112}, {96, 200112}, {97, -1}, {101, 255}, {102, -1}, {103, -1}, {104, -1}, + {105, 1}, {106, -1}, {107, 64}, {108, -1}, {109, -1}, {110, -1}, {111, -1}, {112, -1}, {113, -1}, + {114, -1}, {115, -1}, {118, 200112}, {119, 200112}, {120, 32}, {122, 8}, +}; + +const SysconfConstant* FindSysconfConstant(int name) { + const auto* end = std::end(kSysconfConstants); + const auto* entry = std::lower_bound(std::begin(kSysconfConstants), end, name, [](const SysconfConstant& constant, int key) { return constant.name < key; }); + return entry != end && entry->name == name ? entry : nullptr; +} + bool IsUnansweredName(const char* name) { static constexpr const char* unanswered[] = { "hw.byteorder", @@ -285,9 +319,18 @@ std::int64_t APS5_VABI sysconf_nid_postfix(int name) { #endif break; } - default: - *__error_nid_postfix() = 22; + case 27: + *__error_nid_postfix() = 2; return -1; + default: { + const SysconfConstant* constant = FindSysconfConstant(name); + if (!constant) { + *__error_nid_postfix() = 22; + return -1; + } + *__error_nid_postfix() = saved; + return constant->value; + } } if (result <= 0) { *__error_nid_postfix() = 5; return -1; } *__error_nid_postfix() = saved; @@ -299,10 +342,11 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void *__error_nid_postfix() = 22; return -1; } - if (name == nullptr) { - *__error_nid_postfix() = 14; - return -1; - } + int guestName[24]; + if (!GuestCopyIn_nid_no_patch(guestName, name, nameLength * sizeof(int))) return Fault(); + name = guestName; + std::size_t capacity = 0; + if (oldLength != nullptr && !GuestCopyIn_nid_no_patch(&capacity, oldLength, sizeof(capacity))) return Fault(); if (IsMeasuredAbsentMib(name, nameLength)) { if (newValue != nullptr) throw std::runtime_error(std::string(__func__) + ": writing an unmeasured absent MIB"); *__error_nid_postfix() = 2; @@ -439,13 +483,14 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void return -1; } if (oldLength == nullptr) return 0; - const std::size_t count = std::min(*oldLength, static_cast(256)); + const std::size_t count = std::min(capacity, static_cast(256)); if (oldValue != nullptr) { static std::random_device device; - auto* bytes = static_cast(oldValue); + std::array bytes{}; for (std::size_t i = 0; i < count; ++i) bytes[i] = static_cast(device()); + if (!GuestCopyOut_nid_no_patch(oldValue, bytes.data(), count)) return Fault(); } - *oldLength = count; + if (!GuestCopyOut_nid_no_patch(oldLength, &count, sizeof(count))) return Fault(); return 0; } if (nameLength == 2 && name[0] == 1 && name[1] == 50) { @@ -472,14 +517,12 @@ int APS5_VABI sysctl_nid_postfix(const int* name, std::uint32_t nameLength, void } int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::size_t* oldLength, const void* newValue, std::size_t newLength) { - if (name == nullptr) { - *__error_nid_postfix() = 14; - return -1; - } - if (strnlen(name, MaximumSysctlName) == MaximumSysctlName) { - *__error_nid_postfix() = 63; + std::string guestName; + if (const int error = CopyGuestPath_nid_no_patch(name, guestName)) { + *__error_nid_postfix() = error; return -1; } + name = guestName.c_str(); const std::string trimmed = WithoutTrailingDot(name); name = trimmed.c_str(); if (TopLevelNode(trimmed) >= 0) { diff --git a/core/libs/prx/libkernel/System/src/UserContext.cpp b/core/libs/prx/libkernel/System/src/UserContext.cpp index 0d3d7f5937..1445055be5 100644 --- a/core/libs/prx/libkernel/System/src/UserContext.cpp +++ b/core/libs/prx/libkernel/System/src/UserContext.cpp @@ -1,5 +1,6 @@ #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestStacks.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -39,13 +40,22 @@ static_assert(offsetof(RegisterFrame, rsp) == 0x38 && offsetof(RegisterFrame, r8 static_assert(offsetof(RegisterFrame, rip) == 0x80 && offsetof(RegisterFrame, rflags) == 0x88 && offsetof(RegisterFrame, cs) == 0x90 && offsetof(RegisterFrame, ss) == 0x98); static_assert(offsetof(RegisterFrame, fxsave) == 0xa0 && sizeof(RegisterFrame) == 0x2a0); +constexpr int GuestEfault = 14; constexpr int GuestEinval = 22; constexpr int GuestSigBlock = 1; constexpr int GuestSigSetmask = 3; constexpr std::uint64_t CarryFlag = 0x1; constexpr std::uint32_t McontextFlagMask = 0x7; +constexpr std::uint32_t McontextHasFpxstate = 0x4; +constexpr std::uint64_t UserChangeableFlags = 0x254dd5; +constexpr std::uint64_t UserPrivilegeLevel = 3; +constexpr std::uint64_t FpFormatNodev = 0x10000; constexpr std::uint64_t FpFormatXmm = 0x10002; +constexpr std::uint64_t FpOwnedNone = 0x20000; constexpr std::uint64_t FpOwnedFpu = 0x20001; +constexpr std::uint64_t FpOwnedPcb = 0x20002; +constexpr std::uint16_t InitialFpuControlWord = 0x37f; +constexpr std::uint32_t InitialMxcsr = 0x1f80; constexpr std::size_t FxsaveMxcsr = 24; constexpr std::size_t FxsaveMxcsrMask = 28; constexpr std::uint32_t DefaultMxcsrMask = 0xffbf; @@ -195,10 +205,14 @@ int APS5_VABI Aps5GetcontextFinish_nid_no_patch(UserUcontext* context, const Reg return 0; } -int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) { - if (context == nullptr) return Fail(GuestEinval); +} + +namespace { + +enum class FpuSource { Context, Current, Initial }; + +[[noreturn]] void RestoreContext(const UserUcontext* context, FpuSource fpu) { const auto& machine = context->mcontext; - if (machine.len != sizeof(UserMcontext) || (machine.flags & ~McontextFlagMask) != 0) return Fail(GuestEinval); RegisterFrame frame{}; frame.rax = machine.rax; frame.rbx = machine.rbx; @@ -218,9 +232,16 @@ int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) { frame.r15 = machine.r15; frame.rip = machine.rip; frame.rflags = machine.rflags; - std::memcpy(frame.fxsave, machine.fpstate, sizeof(frame.fxsave)); - std::uint32_t mxcsr = 0; - std::memcpy(&mxcsr, frame.fxsave + FxsaveMxcsr, sizeof(mxcsr)); + std::uint32_t mxcsr = InitialMxcsr; + if (fpu == FpuSource::Context) { + std::memcpy(frame.fxsave, machine.fpstate, sizeof(frame.fxsave)); + std::memcpy(&mxcsr, frame.fxsave + FxsaveMxcsr, sizeof(mxcsr)); + } else if (fpu == FpuSource::Current) { + _fxsave64(frame.fxsave); + std::memcpy(&mxcsr, frame.fxsave + FxsaveMxcsr, sizeof(mxcsr)); + } else { + std::memcpy(frame.fxsave, &InitialFpuControlWord, sizeof(InitialFpuControlWord)); + } mxcsr &= HostMxcsrMask(); std::memcpy(frame.fxsave + FxsaveMxcsr, &mxcsr, sizeof(mxcsr)); sigprocmask_nid_postfix(GuestSigSetmask, context->sigmask, nullptr); @@ -228,6 +249,31 @@ int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) { Aps5ContextRestore_nid_no_patch(&frame); } +} + +extern "C" { + +int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) { + if (context == nullptr) return Fail(GuestEinval); + const auto& machine = context->mcontext; + if (machine.len != sizeof(UserMcontext) || (machine.flags & ~McontextFlagMask) != 0) return Fail(GuestEinval); + RestoreContext(context, FpuSource::Context); +} + +int SigreturnContext_nid_no_patch(const UserUcontext* context) { + if (context == nullptr) return Fail(GuestEfault); + const auto& machine = context->mcontext; + if ((machine.flags & ~McontextFlagMask) != 0) return Fail(GuestEinval); + if (((machine.rflags ^ __builtin_ia32_readeflags_u64()) & ~UserChangeableFlags) != 0) return Fail(GuestEinval); + if ((machine.cs & 3) != UserPrivilegeLevel) NotImplemented_nid_no_patch("sigreturn: a %cs that is not a user selector, for which FreeBSD raises SIGBUS"); + if ((machine.flags & McontextHasFpxstate) != 0) NotImplemented_nid_no_patch("sigreturn: an extended FPU state (_MC_HASFPXSTATE)"); + if (machine.fpformat == FpFormatNodev) RestoreContext(context, FpuSource::Current); + if (machine.fpformat != FpFormatXmm) return Fail(GuestEinval); + if (machine.ownedfp == FpOwnedNone) RestoreContext(context, FpuSource::Initial); + if (machine.ownedfp != FpOwnedFpu && machine.ownedfp != FpOwnedPcb) return Fail(GuestEinval); + RestoreContext(context, FpuSource::Context); +} + int APS5_VABI Aps5SwapcontextFinish_nid_no_patch(UserUcontext* saved, const UserUcontext* next, const RegisterFrame* frame) { if (saved == nullptr || next == nullptr) return Fail(GuestEinval); Aps5GetcontextFinish_nid_no_patch(saved, frame); diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 103338609e..d97b8b752a 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -334,6 +335,25 @@ void KernelTraceWait_nid_postfix(const char* kind, const void* caller, std::uint } static void CancellableSleep(std::uint64_t nanos) { + ThreadCancel::Check(); + const int savedErrno = errno; + if (nanos <= SleepWake::MAX_WAKEABLE_NANOS) { + SleepWake::Sleep(nanos); + } else { + const auto now = TimedWait::NowNanos(); + const auto deadline = now + std::min(nanos, std::numeric_limits::max() - now); + std::mutex mutex; + TimedWait::Condition condition; + std::unique_lock lock(mutex); + while (!ThreadCancel::Requested() && TimedWait::NowNanos() < deadline) { + ThreadCancel::Wait(condition, lock, deadline); + } + } + errno = savedErrno; + ThreadCancel::Check(); +} + +static void SleepUntilComplete(std::uint64_t nanos) { ThreadCancel::Check(); SleepWake::Sleep(nanos); ThreadCancel::Check(); @@ -341,30 +361,33 @@ static void CancellableSleep(std::uint64_t nanos) { int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds) { TraceSleep(__builtin_return_address(0), microseconds); - CancellableSleep(static_cast(microseconds) * 1000ULL); + SleepUntilComplete(static_cast(microseconds) * 1000ULL); return 0; } -static int SleepForRequest(const KernelTimespec* rqtp) { +static int SleepForRequest(const KernelTimespec* rqtp, bool cancellable) { constexpr int guestFault = 14; constexpr int guestInvalid = 22; if (rqtp == nullptr) return guestFault; if (rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000LL) return guestInvalid; constexpr std::int64_t longestSleepSeconds = INT32_MAX / 2; if (rqtp->tv_sec >= 0) { - CancellableSleep(static_cast(std::min(static_cast(rqtp->tv_sec), longestSleepSeconds)) * 1000000000ULL + - static_cast(rqtp->tv_nsec)); + const auto nanos = static_cast( + std::min(static_cast(rqtp->tv_sec), longestSleepSeconds)) * + 1000000000ULL + static_cast(rqtp->tv_nsec); + if (cancellable) CancellableSleep(nanos); + else SleepUntilComplete(nanos); } return 0; } int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp) { - const int error = SleepForRequest(rqtp); + const int error = SleepForRequest(rqtp, false); return error == 0 ? 0 : SceKernelError(error); } int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { - const int error = SleepForRequest(rqtp); + const int error = SleepForRequest(rqtp, true); if (error == 0) return 0; *__error_nid_postfix() = error; return -1; @@ -572,9 +595,11 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { static std::mutex guestTimezoneMutex; static KernelTimezone guestTimezone{0, 0}; -int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz) { - KernelTimeval ignoredTime{}; - if (tv == nullptr) tv = &ignoredTime; +int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* output, KernelTimezone* zoneOutput) { + KernelTimeval current{}; + auto* tv = ¤t; + KernelTimezone zone{}; + auto* tz = zoneOutput == nullptr ? nullptr : &zone; #ifdef _WIN32 FILETIME ft{}; GetSystemTimePreciseAsFileTime(&ft); @@ -594,6 +619,14 @@ int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz) { std::lock_guard lock(guestTimezoneMutex); *tz = guestTimezone; } + if (output != nullptr && !GuestCopyOut_nid_no_patch(output, ¤t, sizeof(current))) { + *__error_nid_postfix() = 14; + return -1; + } + if (zoneOutput != nullptr && !GuestCopyOut_nid_no_patch(zoneOutput, &zone, sizeof(zone))) { + *__error_nid_postfix() = 14; + return -1; + } return 0; } @@ -804,14 +837,15 @@ int APS5_VABI sceKernelConvertUtcToLocaltime(int64_t utc_time, int64_t* local_ti int APS5_VABI sceKernelGettimeofday(KernelTimeval* tp) { if (tp == nullptr) return SceKernelErrorEfault; - return gettimeofday_nid_postfix(tp, nullptr); + const int result = gettimeofday_nid_postfix(tp, nullptr); + return result != 0 && *__error_nid_postfix() == 14 ? SceKernelErrorEfault : result; } int APS5_VABI sceKernelGettimezone(KernelTimezone* tz) { if (tz == nullptr) return SceKernelErrorEfault; const auto now = static_cast(std::time(nullptr)); - *tz = {static_cast(HostZoneAt(now).standardEast / 60), static_cast(HostDaylightAmount(now) / 60)}; - return 0; + const KernelTimezone zone{static_cast(HostZoneAt(now).standardEast / 60), static_cast(HostDaylightAmount(now) / 60)}; + return GuestCopyOut_nid_no_patch(tz, &zone, sizeof(zone)) ? 0 : SceKernelErrorEfault; } uint64_t APS5_VABI sceKernelReadTsc(void) { @@ -838,7 +872,7 @@ std::int64_t APS5_VABI sceKernelGetCpuFrequency(void) { } unsigned int APS5_VABI sceKernelSleep(unsigned int seconds) { - CancellableSleep(static_cast(seconds) * 1000000000ULL); + SleepUntilComplete(static_cast(seconds) * 1000000000ULL); return 0; } diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index bb5ba2cdca..b60cfb995d 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.cpp @@ -619,6 +619,11 @@ void SleepNanos(std::uint64_t nanos) { } SleepUntil(NowNanos() + nanos); #else + struct SleepRegistration { + std::atomic* state = WaitState().bound; + SleepRegistration() { state->fetch_add(1, std::memory_order_seq_cst); } + ~SleepRegistration() { state->fetch_sub(1, std::memory_order_seq_cst); } + } registration; struct timespec req{}; req.tv_sec = static_cast(nanos / 1000000000ULL); req.tv_nsec = static_cast(nanos % 1000000000ULL); diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index db9612410e..fb176e6b3d 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -110,11 +110,6 @@ int APS5_VABI is_in_sandbox_nid_postfix(void) { return 0; } -int APS5_VABI kenv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI kldload_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -125,11 +120,6 @@ int APS5_VABI kldstat_nid_postfix(void) { return 0; } -int APS5_VABI kldsym_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI kmq_notify_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -215,11 +205,6 @@ int APS5_VABI ktrace_nid_postfix(void) { return 0; } -int APS5_VABI lchflags_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI mdbg_call_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -800,11 +785,6 @@ int APS5_VABI sceKernelGetCpuUsageThread(void) { return 0; } -int APS5_VABI sceKernelGetCpumodeGame(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetCurrentFanDuty(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1695,11 +1675,6 @@ int APS5_VABI sceKernelSetCoredumpAppInfo(void) { return 0; } -int APS5_VABI sceKernelSetCpumodeGame(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelSetDataTransferMode(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2165,8 +2140,17 @@ int APS5_VABI scePthreadTimedjoin(void) { return 0; } -int APS5_VABI sched_setscheduler_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sched_setscheduler_nid_postfix(int pid, int policy, const KernelSchedParam* param) { + const auto fail = [](int error) { + *__error_nid_postfix() = error; + return -1; + }; + if (param == nullptr) return fail(14); + if (pid != 0 && pid == getpid_nid_postfix()) NotImplemented_nid_no_patch("sched_setscheduler: the first thread of the process, which its own pid selects, is not tracked"); + if (pid != 0) return fail(3); + if (policy != 1 && policy != 2 && policy != 3) return fail(22); + if (scePthreadSetschedparam(scePthreadSelf(), policy, param) != 0) + NotImplemented_nid_no_patch("sched_setscheduler: a policy and priority scePthreadSetschedparam does not accept, where FreeBSD answers EPERM or EINVAL and the console's answer is unverified"); return 0; } @@ -2185,9 +2169,8 @@ int APS5_VABI sigqueue_nid_postfix(void) { return 0; } -int APS5_VABI sigreturn_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sigreturn_nid_postfix(const UserUcontext* context) { + return SigreturnContext_nid_no_patch(context); } int APS5_VABI statfs_nid_postfix(void) { diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 195204da66..69e3b4f4db 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" #include +#include #include #include #include @@ -9,10 +10,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #ifndef _WIN32 @@ -32,6 +35,8 @@ #include "prx/libkernel/File/include/DescriptorControl.hpp" #include "prx/libc/include/CreationMask.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/File/include/SharedMemory.hpp" +#include "prx/libkernel/Equeue/Equeue.hpp" #ifdef _WIN32 #ifndef NOMINMAX @@ -72,7 +77,9 @@ constexpr int GuestEinval = 22; constexpr int GuestEexist = 17; constexpr int GuestExdev = 18; constexpr int GuestEdeadlk = 11; +constexpr int GuestEopnotsupp = 45; constexpr int GuestEagain = 35; +std::atomic g_gameCpumode{1}; constexpr int GuestEloop = 62; constexpr int GuestEnametoolong = 63; constexpr int GuestEnotempty = 66; @@ -129,6 +136,35 @@ std::int64_t Fail(int error) { return -1; } +constexpr int KenvGet = 0; +constexpr int KenvSet = 1; +constexpr int KenvUnset = 2; +constexpr int KenvDump = 3; +constexpr std::size_t KenvNameLength = 128; +constexpr std::size_t KenvValueLength = 128; +constexpr std::size_t KenvEntries = 512; + +struct KernelEnvironment { + std::mutex mutex; + std::vector entries; +}; + +KernelEnvironment& KernelEnv() { + static auto* environment = new KernelEnvironment; + return *environment; +} + +std::vector::iterator FindKernelEnv(std::vector& entries, const std::string& name) { + return std::find_if(entries.begin(), entries.end(), [&](const std::string& entry) { + return entry.size() > name.size() && entry.compare(0, name.size(), name) == 0 && entry[name.size()] == '='; + }); +} + +int CopyKernelEnvString(const char* source, std::size_t limit, std::string& destination) { + if (const int error = CopyGuestPath_nid_no_patch(source, destination)) return error; + return destination.size() + 1 > limit ? GuestEnametoolong : 0; +} + std::int64_t PathConfigurationValue(int name) { switch (name) { case PcLinkMax: return 32767; @@ -345,6 +381,19 @@ int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { return sceKernelGetOpenPsId(id); } +int APS5_VABI sceKernelGetCpumodeGame(int* mode) { + if (mode == nullptr) throw std::runtime_error("sceKernelGetCpumodeGame: a null output faults on the console"); + *mode = g_gameCpumode.load(std::memory_order_relaxed); + return 0; +} + +int APS5_VABI sceKernelSetCpumodeGame(int mode) { + if (mode < 0 || mode > 7) NotImplemented_nid_no_patch("sceKernelSetCpumodeGame with a mode outside 0 to 7"); + if (mode > 1) return SCE_KERNEL_ERROR_EINVAL; + g_gameCpumode.store(mode, std::memory_order_relaxed); + return 0; +} + int APS5_VABI sceKernelIsCEX(void) { return 1; } @@ -375,6 +424,61 @@ int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf) { return _fstatfs_nid_postfix(d, buf); } +int APS5_VABI kenv_nid_postfix(int what, const char* name, char* value, int len) { + auto& environment = KernelEnv(); + if (what == KenvDump) { + std::size_t capacity = len > 0 ? static_cast(len) : 0; + if (capacity > KenvEntries * (KenvNameLength + KenvValueLength + 2)) capacity = KenvEntries * (KenvNameLength + KenvValueLength + 2); + const bool copy = len > 0 && value != nullptr; + std::string buffer; + std::size_t needed = 0; + { + const std::lock_guard lock(environment.mutex); + for (const auto& entry : environment.entries) { + const std::size_t size = entry.size() + 1; + needed += size; + if (copy) { + const std::size_t taken = std::min(size, capacity - buffer.size()); + buffer.append(entry.c_str(), taken); + } + } + } + if (copy && !GuestCopyOut_nid_no_patch(value, buffer.data(), buffer.size())) return static_cast(Fail(GuestEfault)); + return buffer.size() == needed ? 0 : static_cast(needed); + } + std::string key; + if (const int error = CopyKernelEnvString(name, KenvNameLength + 1, key)) return static_cast(Fail(error)); + const std::lock_guard lock(environment.mutex); + auto& entries = environment.entries; + switch (what) { + case KenvGet: { + const auto found = FindKernelEnv(entries, key); + if (found == entries.end()) return static_cast(Fail(GuestEnoent)); + const std::size_t size = found->size() - key.size(); + const std::size_t limit = static_cast(static_cast(len)); + const std::size_t taken = std::min(size, limit); + if (!GuestCopyOut_nid_no_patch(value, found->c_str() + key.size() + 1, taken)) return static_cast(Fail(GuestEfault)); + return static_cast(taken); + } + case KenvSet: { + if (len == 0) return static_cast(Fail(GuestEinval)); + std::string text; + if (const int error = CopyKernelEnvString(value, std::min(len, KenvValueLength + 1), text)) return static_cast(Fail(error)); + const auto found = FindKernelEnv(entries, key); + if (found != entries.end()) *found = key + "=" + text; + else if (entries.size() < KenvEntries) entries.push_back(key + "=" + text); + return 0; + } + case KenvUnset: { + const auto found = FindKernelEnv(entries, key); + if (found == entries.end()) return static_cast(Fail(GuestEnoent)); + entries.erase(found); + return 0; + } + default: return static_cast(Fail(GuestEinval)); + } +} + int GetModuleInfoExByHandle_nid_no_patch(KernelModule handle, ModuleInfoEx* info); int APS5_VABI sceKernelGetModuleInfoInternal(KernelModule handle, ModuleInfoEx* info) { @@ -439,6 +543,7 @@ std::int64_t APS5_VABI _fpathconf_nid_postfix(int fd, int name) { #endif if (name == PcAsyncIo) return PosixAsynchronousIo; if (withoutVnode) return name == PcPipeBuf ? 512 : Fail(GuestEinval); + if (IsKqueueDescriptor_nid_no_patch(fd) || File::IsSharedMemoryDescriptor_nid_no_patch(fd)) return Fail(GuestEopnotsupp); return PathConfigurationValue(name); } @@ -488,10 +593,16 @@ static int LinkSourceError(const std::filesystem::path& source) { return std::filesystem::is_directory(status) ? GuestEperm : 0; } +static int LinkDestinationError(const std::filesystem::path& destination) { + std::error_code error; + if (std::filesystem::exists(std::filesystem::symlink_status(destination, error))) return GuestEexist; + if (!std::filesystem::is_directory(destination.parent_path(), error)) return HasNonDirectoryPrefix(destination) ? GuestEnotdir : GuestEnoent; + return 0; +} + static int LinkNative(const std::filesystem::path& source, const std::filesystem::path& destination) { + if (const int destinationError = LinkDestinationError(destination)) return static_cast(Fail(destinationError)); std::error_code error; - if (std::filesystem::exists(std::filesystem::symlink_status(destination, error))) return static_cast(Fail(GuestEexist)); - if (!std::filesystem::is_directory(destination.parent_path(), error)) return static_cast(Fail(HasNonDirectoryPrefix(destination) ? GuestEnotdir : GuestEnoent)); #ifdef _WIN32 std::filesystem::path target; if (const int followError = FollowLinkPath(source, target)) return static_cast(Fail(followError)); @@ -506,6 +617,18 @@ static int LinkNative(const std::filesystem::path& source, const std::filesystem return 0; } +#ifndef _WIN32 +static int LinkSymbolicLinkItself(const std::filesystem::path& source, const std::filesystem::path& destination) { + if (const int error = LinkDestinationError(destination)) return static_cast(Fail(error)); + if (::linkat(AT_FDCWD, source.c_str(), AT_FDCWD, destination.c_str(), 0) != 0) { + if (errno == EXDEV) return static_cast(Fail(GuestExdev)); + throw std::system_error(errno, std::generic_category(), "linkat"); + } + RecordWrittenPath_nid_no_patch(destination); + return 0; +} +#endif + int APS5_VABI link_nid_postfix(const char* existing, const char* created) { std::string existingCopy, createdCopy; if (const int error = CopyPath(existing, existingCopy)) return static_cast(Fail(error)); @@ -533,10 +656,17 @@ int APS5_VABI linkat_nid_postfix(int fd1, const char* path1, int fd2, const char if (const int error = CopyPath(path1, copy1)) return static_cast(Fail(error)); std::filesystem::path source, destination; if (const int error = ResolveAtPath(fd1, path1, source)) return static_cast(Fail(error)); - if (flag == 0 && IsSymbolicLink(source)) NotImplemented_nid_no_patch("linkat: a hard link to a symbolic link itself"); - if (const int error = LinkSourceError(source)) return static_cast(Fail(error)); + const bool linkItself = flag == 0 && IsSymbolicLink(source); +#ifdef _WIN32 + if (linkItself) NotImplemented_nid_no_patch("linkat: a hard link to a symbolic link itself on Windows"); +#endif + if (!linkItself) + if (const int error = LinkSourceError(source)) return static_cast(Fail(error)); if (const int error = CopyPath(path2, copy2)) return static_cast(Fail(error)); if (const int error = ResolveAtPath(fd2, path2, destination)) return static_cast(Fail(error)); +#ifndef _WIN32 + if (linkItself) return LinkSymbolicLinkItself(source, destination); +#endif return LinkNative(source, destination); } @@ -597,6 +727,24 @@ int APS5_VABI kldunload_nid_postfix(int fileid) { return static_cast(Fail(GuestEperm)); } +int APS5_VABI kldsym_nid_postfix(int fileid, int cmd, void* data) { + struct SymbolLookup { + std::int32_t version; + const char* name; + std::uint64_t value; + std::uint64_t size; + }; + static_assert(sizeof(SymbolLookup) == 32); + constexpr int LookupCommand = 1; + SymbolLookup lookup{}; + if (!GuestCopyIn_nid_no_patch(&lookup, data, sizeof(lookup))) return static_cast(Fail(GuestEfault)); + if (lookup.version != static_cast(sizeof(lookup)) || cmd != LookupCommand) return static_cast(Fail(GuestEinval)); + std::string name; + if (const int error = CopyGuestPath_nid_no_patch(lookup.name, name)) return static_cast(Fail(error)); + if (fileid != 0) return static_cast(Fail(GuestEnoent)); + throw std::runtime_error("kldsym: looking up the symbol " + name + " in every kernel module is not supported"); +} + int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { std::string targetCopy, createdCopy; if (const int error = CopyPath(target, targetCopy)) return static_cast(Fail(error)); @@ -832,6 +980,12 @@ int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int fl return 0; } +int ChangeFileFlags_nid_no_patch(const char* function, const char* path, std::uint64_t flags, bool follow); + +int APS5_VABI lchflags_nid_postfix(const char* path, std::uint64_t flags) { + return ChangeFileFlags_nid_no_patch("lchflags", path, flags, false); +} + int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { if (flag & ~GuestAtRemovedir) return static_cast(Fail(GuestEinval)); std::string copy; diff --git a/core/libs/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp index 6659f0e8f4..d6f5a98094 100644 --- a/core/libs/prx/libkernel_sys/include/Api.hpp +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -74,6 +74,7 @@ int APS5_VABI openat_nid_postfix(int fd, const char* path, int flags, ...); int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode); int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag); int APS5_VABI lchmod_nid_postfix(const char* path, std::uint16_t mode); +int APS5_VABI lchflags_nid_postfix(const char* path, std::uint64_t flags); int APS5_VABI fchown_nid_postfix(int fd, std::uint32_t uid, std::uint32_t gid); int APS5_VABI sched_rr_get_interval_nid_postfix(int pid, KernelTimespec* interval); int APS5_VABI chown_nid_postfix(const char* path, std::uint32_t uid, std::uint32_t gid); @@ -108,13 +109,13 @@ int APS5_VABI get_vm_map_timestamp_nid_postfix(void); int APS5_VABI getfsstat_nid_postfix(void); int APS5_VABI ipmimgr_call_nid_postfix(void); int APS5_VABI is_in_sandbox_nid_postfix(void); -int APS5_VABI kenv_nid_postfix(void); +int APS5_VABI kenv_nid_postfix(int what, const char* name, char* value, int len); int APS5_VABI kldfind_nid_postfix(const char* file); int APS5_VABI kldfirstmod_nid_postfix(int fileid); int APS5_VABI kldload_nid_postfix(void); int APS5_VABI kldnext_nid_postfix(int fileid); int APS5_VABI kldstat_nid_postfix(void); -int APS5_VABI kldsym_nid_postfix(void); +int APS5_VABI kldsym_nid_postfix(int fileid, int cmd, void* data); int APS5_VABI kldunload_nid_postfix(int fileid); int APS5_VABI kmq_notify_nid_postfix(void); int APS5_VABI kmq_open_nid_postfix(void); @@ -133,7 +134,6 @@ int APS5_VABI ksem_trywait_nid_postfix(void); int APS5_VABI ksem_unlink_nid_postfix(void); int APS5_VABI ksem_wait_nid_postfix(void); int APS5_VABI ktrace_nid_postfix(void); -int APS5_VABI lchflags_nid_postfix(void); int APS5_VABI mdbg_call_nid_postfix(void); int APS5_VABI mdbg_service_nid_postfix(void); int APS5_VABI mount_nid_postfix(void); @@ -253,7 +253,7 @@ int APS5_VABI sceKernelGetCpuUsageAll(void); int APS5_VABI sceKernelGetCpuUsageProc(void); int APS5_VABI sceKernelGetCpuUsageProc2(void); int APS5_VABI sceKernelGetCpuUsageThread(void); -int APS5_VABI sceKernelGetCpumodeGame(void); +int APS5_VABI sceKernelGetCpumodeGame(int* mode); int APS5_VABI sceKernelGetCurrentFanDuty(void); int APS5_VABI sceKernelGetDataTransferMode(void); int APS5_VABI sceKernelGetDebugMenuMiniModeForRcmgr(void); @@ -437,7 +437,7 @@ int APS5_VABI sceKernelSetBudget(void); int APS5_VABI sceKernelSetCallRecord(void); int APS5_VABI sceKernelSetCompressionAttribute(void); int APS5_VABI sceKernelSetCoredumpAppInfo(void); -int APS5_VABI sceKernelSetCpumodeGame(void); +int APS5_VABI sceKernelSetCpumodeGame(int mode); int APS5_VABI sceKernelSetDataTransferMode(void); int APS5_VABI sceKernelSetEmergencyErrorLog(void); int APS5_VABI sceKernelSetFsstParam(void); @@ -536,12 +536,12 @@ int APS5_VABI scePthreadSingle(void); int APS5_VABI scePthreadSuspend(void); int APS5_VABI scePthreadSuspendAll(void); int APS5_VABI scePthreadTimedjoin(void); -int APS5_VABI sched_setscheduler_nid_postfix(void); +int APS5_VABI sched_setscheduler_nid_postfix(int pid, int policy, const KernelSchedParam* param); int APS5_VABI sem_setname_nid_postfix(void); int APS5_VABI set_phys_fmem_limit_nid_postfix(void); int APS5_VABI setlogin_nid_postfix(const char* name); int APS5_VABI sigqueue_nid_postfix(void); -int APS5_VABI sigreturn_nid_postfix(void); +int APS5_VABI sigreturn_nid_postfix(const UserUcontext* context); int APS5_VABI statfs_nid_postfix(void); int APS5_VABI swapon_nid_postfix(void); int APS5_VABI symlinkat_nid_postfix(void); diff --git a/core/libs/tests/AioTest.hpp b/core/libs/tests/AioTest.hpp new file mode 100644 index 0000000000..4ec41e60ff --- /dev/null +++ b/core/libs/tests/AioTest.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifndef ANYPS5_AIO_TESTING +#error AIO test controls require ANYPS5_AIO_TESTING +#endif + +extern "C" { + +void AioAdvanceIdentityForTest_nid_no_patch(std::int64_t next); +std::int32_t AioProcessingWaitersForTest_nid_no_patch(); + +} diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 27894e4131..62f136df73 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -333,6 +333,15 @@ int main(int argc, char** argv) { empty = ApplicationHeapReallocate_nid_no_patch(nullptr, 0); require(empty != nullptr); ApplicationHeapFree_nid_no_patch(empty); + pointer = static_cast(reallocarray_nid_postfix(nullptr, 4, 10)); + require(pointer != nullptr && ApplicationHeapUsableSize_nid_no_patch(pointer) == 40); + std::memset(pointer, 0x5a, 40); + pointer = static_cast(reallocarray_nid_postfix(pointer, 5, 20)); + require(pointer != nullptr && ApplicationHeapUsableSize_nid_no_patch(pointer) == 100); + for (unsigned i = 0; i < 40; ++i) require(pointer[i] == 0x5a); + *__error_nid_postfix() = 0; + require(reallocarray_nid_postfix(pointer, 2, SIZE_MAX) == nullptr && *__error_nid_postfix() == 12); + ApplicationHeapFree_nid_no_patch(pointer); for (std::size_t alignment : {16, 64, 4096}) { auto* aligned = ApplicationHeapAlign_nid_no_patch(alignment, 37); require(reinterpret_cast(aligned) % alignment == 0); diff --git a/core/libs/tests/FileStreamStatus.cpp b/core/libs/tests/FileStreamStatus.cpp new file mode 100644 index 0000000000..edf8b3efd8 --- /dev/null +++ b/core/libs/tests/FileStreamStatus.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/FileStream.hpp" +#include +#include +#include +#include +#include + +int main() { + auto* native = std::tmpfile(); + if (!native) return 1; + FileStream stream(native); + stream.MarkDirectory(); + constexpr std::int16_t unrelated = 0x1104; + stream.GuestState().flags = unrelated; + unsigned failures = 0; + for (const bool endOfFile : {false, true}) { + for (const bool error : {false, true}) { + stream.PublishStatus(endOfFile, error); + const auto flags = stream.GuestState().flags; + failures += flags != (unrelated | (endOfFile ? 0x20 : 0) | (error ? 0x40 : 0)); + errno = 123; + stream.KeepDirectoryReadError(false); + failures += errno != 123; + stream.KeepDirectoryReadError(true); + failures += errno != (error ? 21 : 123); + } + } + std::barrier start(2); + std::atomic invalid = 0; + std::thread publish([&] { + start.arrive_and_wait(); + for (unsigned index = 0; index < 20000; ++index) + stream.PublishStatus((index & 1) != 0, (index & 2) != 0); + }); + std::thread read([&] { + start.arrive_and_wait(); + for (unsigned index = 0; index < 20000; ++index) { + errno = 123; + stream.KeepDirectoryReadError(true); + if (errno != 123 && errno != 21) invalid.fetch_add(1, std::memory_order_relaxed); + const auto flags = __atomic_load_n(&stream.GuestState().flags, __ATOMIC_RELAXED); + if ((flags & ~0x60) != unrelated) invalid.fetch_add(1, std::memory_order_relaxed); + } + }); + publish.join(); + read.join(); + failures += invalid.load(std::memory_order_relaxed); + failures += std::fclose(native) != 0; + std::printf("file stream status: 4 serial cases, 20000 publications, 20000 reads, %u failures\n", failures); + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index 79dfd69a61..83fc668a6e 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -29,6 +29,12 @@ static void Require(bool value, int line) { } } #define Check(value) Require((value), __LINE__) + +static void DeleteAio(std::int32_t id) { + std::int32_t removed = -1; + Check(sceKernelAioDeleteRequest(id, &removed) == 0 && removed == 0); +} + #ifdef _WIN32 static int g_parameterReports = 0; static void CountParameterReport(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) { @@ -413,6 +419,7 @@ int main(int argc, char** argv) { result.return_value != 16 || std::memcmp(bytes.data(), sharedContents.data() + offset, 16) != 0) { misread = true; } + DeleteAio(requestId); } }); } diff --git a/core/libs/tests/GuestAioRegistry.cpp b/core/libs/tests/GuestAioRegistry.cpp new file mode 100644 index 0000000000..1b165ec7ca --- /dev/null +++ b/core/libs/tests/GuestAioRegistry.cpp @@ -0,0 +1,251 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tests/AioTest.hpp" + +static void Require(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Aio check failed at line %d\n", line); + std::abort(); + } +} +#define Check(value) Require((value), __LINE__) + +static void DeleteAio(std::int32_t id) { + std::int32_t removed = -1; + Check(sceKernelAioDeleteRequest(id, &removed) == 0 && removed == 0); +} + +static std::int32_t SubmitEmptyAio() { + KernelAioResult result{-1, 0}; + KernelAioRwRequest request{0, 0, nullptr, &result, -1}; + std::int32_t id = 0; + Check(sceKernelAioSubmitReadCommands(&request, 1, 1, &id) == 0); + Check(result.state == 3 && result.return_value == SCE_KERNEL_ERROR_EBADF); + return id; +} + +static void CheckAioIdentity() { + const auto retained = SubmitEmptyAio(); + std::int32_t state = 0; + Check(sceKernelAioPollRequest(retained, &state) == 0 && state == 3); + auto previous = retained; + for (int iteration = 0; iteration < 1100; ++iteration) { + const auto id = SubmitEmptyAio(); + Check(id > previous); + previous = id; + DeleteAio(id); + Check(sceKernelAioPollRequest(retained, &state) == 0 && state == 0x10003); + } + DeleteAio(retained); + for (int iteration = 0; iteration < 1100; ++iteration) { + const auto id = SubmitEmptyAio(); + Check(id > previous); + previous = id; + DeleteAio(id); + } + Check(sceKernelAioPollRequest(retained, &state) == 0 && state == SCE_KERNEL_ERROR_ESRCH); +} + +static void CheckAioAdmission() { + const auto file = std::filesystem::path("anyps5-aio-admission-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + { std::ofstream stream(file, std::ios::binary); stream << 'Z'; Check(static_cast(stream)); } + const int fd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDWR, 0); + Check(fd >= 0); + std::vector retained; + for (int index = 0; index < 510; ++index) retained.push_back(SubmitEmptyAio()); + std::array results{{{-7, 7}, {-7, 7}}}; + char byte = 'X'; + KernelAioRwRequest requests[2]{{0, 1, &byte, &results[0], fd}, {0, 1, &byte, &results[1], fd}}; + std::int32_t state = 0; + Check(sceKernelAioPollRequest(retained[0], &state) == 0 && state == 3); + for (const auto submit : {sceKernelAioSubmitReadCommandsMultiple, sceKernelAioSubmitWriteCommandsMultiple}) { + std::int32_t ids[2]{-7, -7}; + bool refused = false; + try { submit(requests, 2, 1, ids); } catch (const std::runtime_error&) { refused = true; } + Check(refused && ids[0] == -7 && ids[1] == -7); + Check(results[0].state == 7 && results[0].return_value == -7 && results[1].state == 7 && results[1].return_value == -7); + } + retained.push_back(SubmitEmptyAio()); + for (const auto submit : {sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands}) { + std::int32_t id = -7; + bool refused = false; + try { submit(requests, 1, 1, &id); } catch (const std::runtime_error&) { refused = true; } + Check(refused && id == -7 && results[0].state == 7 && results[0].return_value == -7); + } + Check(sceKernelAioPollRequest(retained[0], &state) == 0 && state == 0x10003); + { std::ifstream stream(file, std::ios::binary); Check(stream.get() == 'Z' && stream.get() == std::char_traits::eof()); } + for (const auto id : retained) DeleteAio(id); + std::int32_t ids[2]{}; + Check(sceKernelAioSubmitReadCommandsMultiple(requests, 2, 1, ids) == 0); + Check(ids[0] != ids[1] && results[0].state == 3 && results[1].state == 3); + for (const auto id : ids) DeleteAio(id); + Check(sceKernelClose(fd) == 0 && std::filesystem::remove(file)); +} + +static int g_aioSourceCalls = 0; + +static void FailAioSource(std::uintptr_t, std::size_t) { + if (++g_aioSourceCalls % 2 == 0) throw std::runtime_error("AIO source fault injection"); +} + +static void CheckAioRollback() { + const auto file = std::filesystem::path("anyps5-aio-rollback-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + { std::ofstream stream(file, std::ios::binary); stream << "zz"; Check(static_cast(stream)); } + const int fd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDWR, 0); + Check(fd >= 0); + std::array results{{{-7, 7}, {-7, 7}}}; + char byte = 'A'; + KernelAioRwRequest requests[2]{{0, 1, &byte, &results[0], fd}, {1, 1, &byte, &results[1], fd}}; + for (const auto submit : {sceKernelAioSubmitWriteCommands, sceKernelAioSubmitWriteCommandsMultiple}) { + GuestArena::GuestArenaSetHostReadHook_nid_no_patch(FailAioSource); + for (int iteration = 0; iteration < 600; ++iteration) { + results = {{{-7, 7}, {-7, 7}}}; + std::int32_t ids[2]{-7, -7}; + bool refused = false; + const auto calls = g_aioSourceCalls; + try { submit(requests, 2, 1, ids); } catch (const std::runtime_error& error) { refused = std::strcmp(error.what(), "AIO source fault injection") == 0; } + Check(refused && ids[0] == -7 && ids[1] == -7); + Check(g_aioSourceCalls == calls + 2 && results[0].state == 3 && results[0].return_value == 1 && results[1].state == 7); + } + GuestArena::GuestArenaSetHostReadHook_nid_no_patch(nullptr); + } + Check(sceKernelClose(fd) == 0); + { std::ifstream stream(file, std::ios::binary); Check(stream.get() == 'A' && stream.get() == 'z' && stream.get() == std::char_traits::eof()); } + Check(std::filesystem::remove(file)); + CheckAioAdmission(); +} + +static void CheckAioIdentityExhaustion() { + const auto maximum = std::numeric_limits::max(); + AioAdvanceIdentityForTest_nid_no_patch(maximum); + std::array results{{{-7, 7}, {-7, 7}}}; + KernelAioRwRequest requests[2]{{0, 0, nullptr, &results[0], -1}, {0, 0, nullptr, &results[1], -1}}; + std::int32_t ids[2]{-7, -7}; + bool refused = false; + try { sceKernelAioSubmitReadCommandsMultiple(requests, 2, 1, ids); } catch (const std::runtime_error&) { refused = true; } + Check(refused && ids[0] == -7 && ids[1] == -7 && results[0].state == 7 && results[1].state == 7); + const auto final = SubmitEmptyAio(); + Check(final == maximum); + DeleteAio(final); + refused = false; + try { sceKernelAioSubmitReadCommands(requests, 1, 1, ids); } catch (const std::runtime_error&) { refused = true; } + Check(refused && ids[0] == -7 && results[0].state == 7); + std::int32_t state = 0; + Check(sceKernelAioPollRequest(final, &state) == 0 && state == SCE_KERNEL_ERROR_ESRCH); +} + +static std::mutex g_aioGateMutex; +static std::condition_variable g_aioGateChanged; +static bool g_aioGateEntered = false; +static bool g_aioGateReleased = false; +static bool g_aioGateFails = false; + +static void HoldAioSource(std::uintptr_t, std::size_t) { + std::unique_lock lock(g_aioGateMutex); + g_aioGateEntered = true; + g_aioGateChanged.notify_all(); + Check(g_aioGateChanged.wait_for(lock, std::chrono::seconds(5), [] { return g_aioGateReleased; })); + if (g_aioGateFails) throw std::runtime_error("AIO gated transfer failure"); +} + +static void CheckAioProcessingOwnership(bool fail) { + g_aioGateFails = fail; + const auto maximum = std::numeric_limits::max(); + AioAdvanceIdentityForTest_nid_no_patch(maximum - 2); + const auto completed = SubmitEmptyAio(); + const auto pending = completed + 1; + KernelAioResult result{-7, 7}; + char byte = 'x'; + KernelAioRwRequest request{0, 1, &byte, &result, -1}; + std::int32_t submittedId = -7; + bool submitFailed = false; + GuestArena::GuestArenaSetHostReadHook_nid_no_patch(HoldAioSource); + std::thread submitter([&] { + try { Check(sceKernelAioSubmitWriteCommands(&request, 1, 1, &submittedId) == 0); } + catch (const std::runtime_error& error) { submitFailed = std::strcmp(error.what(), "AIO gated transfer failure") == 0; } + }); + { + std::unique_lock lock(g_aioGateMutex); + Check(g_aioGateChanged.wait_for(lock, std::chrono::seconds(5), [] { return g_aioGateEntered; })); + } + std::int32_t state = -7; + Check(sceKernelAioPollRequest(pending, &state) == 0 && state == 2); + for (const auto operation : {sceKernelAioCancelRequest, sceKernelAioDeleteRequest}) { + state = -7; + bool refused = false; + try { operation(pending, &state); } catch (const std::runtime_error&) { refused = true; } + Check(refused && state == -7); + } + for (const auto operation : {sceKernelAioCancelRequests, sceKernelAioDeleteRequests}) { + std::int32_t ids[2]{completed, pending}; + std::int32_t states[2]{-7, -7}; + bool refused = false; + try { operation(ids, 2, states); } catch (const std::runtime_error&) { refused = true; } + Check(refused && states[0] == -7 && states[1] == -7); + } + Check(sceKernelAioPollRequest(completed, &state) == 0 && state == 3); + Check(sceKernelAioPollRequest(pending, &state) == 0 && state == 2); + std::uint32_t timeout = 1000; + Check(sceKernelAioWaitRequest(pending, &state, &timeout) == SCE_KERNEL_ERROR_ETIMEDOUT && state == 2); + std::atomic waiterDone{false}; + std::int32_t waited = -7; + std::thread waiter([&] { + Check(sceKernelAioWaitRequest(pending, &waited, nullptr) == 0); + waiterDone.store(true); + }); + const auto waiterDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (AioProcessingWaitersForTest_nid_no_patch() == 0 && std::chrono::steady_clock::now() < waiterDeadline) std::this_thread::yield(); + Check(AioProcessingWaitersForTest_nid_no_patch() == 1); + const auto concurrent = SubmitEmptyAio(); + Check(!waiterDone.load() && result.state == 7 && submittedId == -7); + { + std::lock_guard lock(g_aioGateMutex); + g_aioGateReleased = true; + g_aioGateChanged.notify_all(); + } + submitter.join(); + waiter.join(); + GuestArena::GuestArenaSetHostReadHook_nid_no_patch(nullptr); + Check(waiterDone.load() && submitFailed == fail); + Check(AioProcessingWaitersForTest_nid_no_patch() == 0); + if (fail) { + Check(waited == SCE_KERNEL_ERROR_ESRCH && submittedId == -7 && result.state == 7); + } else { + Check(waited == 3 && submittedId == pending); + Check(result.state == 3 && result.return_value == SCE_KERNEL_ERROR_EBADF); + DeleteAio(pending); + } + for (const auto id : {completed, concurrent}) DeleteAio(id); +} + +int main(int argc, char** argv) { + Check(argc == 2); + if (argc > 1 && std::strcmp(argv[1], "identity") == 0) { CheckAioIdentity(); return 0; } + if (argc > 1 && std::strcmp(argv[1], "admission") == 0) { CheckAioAdmission(); return 0; } + if (argc > 1 && std::strcmp(argv[1], "rollback") == 0) { CheckAioRollback(); return 0; } + if (argc > 1 && std::strcmp(argv[1], "identity-exhaustion") == 0) { CheckAioIdentityExhaustion(); return 0; } + if (argc > 1 && std::strcmp(argv[1], "processing") == 0) { CheckAioProcessingOwnership(false); return 0; } + if (argc > 1 && std::strcmp(argv[1], "processing-rollback") == 0) { CheckAioProcessingOwnership(true); return 0; } + Check(false); +} diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 7b8c328e40..db284c0321 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -1672,6 +1673,15 @@ void TestResampleOpus(std::uint32_t context) { Require(decimated.afterFirstJob.ratio == 2.0f && decimated.afterFirstJob.samples == 2); Require(decimated.atEnd.result == 0 && decimated.atEnd.samples >= 0 && decimated.atEnd.samples <= 2); + for (const float ratio : {0x1p32f, 0x1p63f, std::nextafter(0x1p64f, 0.0f), 0x1p64f, std::numeric_limits::max()}) { + const std::uint32_t extreme = CreateOpus(context); + const auto skipped = Stream(context, extreme, OPUS_STEREO, sizeof(OPUS_STEREO), 2, 512, ratio); + Require(skipped.pcm.size() == 2); + Require(skipped.pcm[0] == reference.pcm[0] && skipped.pcm[1] == reference.pcm[1]); + Require(skipped.atEnd.result == 0 && skipped.atEnd.samples == 0); + Require(sceAjmInstanceDestroy(context, extreme) == 0); + } + const std::uint32_t slower = CreateOpus(context); const auto interpolated = Stream(context, slower, OPUS_STEREO, sizeof(OPUS_STEREO), 2, 512, 0.5f); RequireInterpolated(reference.pcm, interpolated.pcm, 2); diff --git a/core/libs/tests/GuestAmmPool.cpp b/core/libs/tests/GuestAmmPool.cpp new file mode 100644 index 0000000000..78d96378ae --- /dev/null +++ b/core/libs/tests/GuestAmmPool.cpp @@ -0,0 +1,274 @@ +#include "prx/libkernel/Apr/include/AmmPool.hpp" +#include +#include +#include +#include +#include +#include +#include + +static std::atomic failingAllocation{-1}; + +void* operator new(std::size_t size) { + if (failingAllocation.load(std::memory_order_relaxed) >= 0 && failingAllocation.fetch_sub(1, std::memory_order_relaxed) == 0) throw std::bad_alloc(); + if (void* memory = std::malloc(size == 0 ? 1 : size)) return memory; + throw std::bad_alloc(); +} + +void operator delete(void* memory) noexcept { std::free(memory); } +void operator delete(void* memory, std::size_t) noexcept { std::free(memory); } + +static void RequireAt(bool value, int line) { + if (value) return; + std::fprintf(stderr, "GuestAmmPool.cpp:%d: requirement failed\n", line); + std::abort(); +} + +#define Require(value) RequireAt((value), __LINE__) + +namespace { + +constexpr std::uint64_t page = AmmPool::PageBytes; +constexpr std::uint64_t address = 0x7000000000; + +bool Equal(const AmmPool::Records& a, const AmmPool::Records& b) { + const auto samePage = [](const AmmPool::Page& x, const AmmPool::Page& y) { return x.physical == y.physical && x.type == y.type && x.protection == y.protection && x.pooled == y.pooled; }; + if (a.pool != b.pool || a.pooledUses != b.pooledUses || a.pages.size() != b.pages.size()) return false; + for (auto x = a.pages.begin(), y = b.pages.begin(); x != a.pages.end(); ++x, ++y) { + if (x->first != y->first || !samePage(x->second, y->second)) return false; + } + return true; +} + +std::uint64_t FreeBytes(const AmmPool::Records& records) { + std::uint64_t free = 0; + for (const auto& [offset, size] : records.pool) free += size; + return free; +} + +void RequireConsistent(const AmmPool::Records& records) { + std::map uses; + for (const auto& [at, entry] : records.pages) { + if (entry.pooled) ++uses[entry.physical]; + } + Require(uses == records.pooledUses); + std::uint64_t previousEnd = 0; + bool first = true; + for (const auto& [offset, size] : records.pool) { + Require(size != 0 && offset % page == 0 && size % page == 0); + Require(first || offset > previousEnd); + previousEnd = offset + size; + first = false; + } + for (const auto& [physical, count] : uses) { + for (const auto& [offset, size] : records.pool) Require(physical >= offset + size || physical + page <= offset); + } +} + +AmmPool::Records Fragmented() { + AmmPool::Records records; + records.pool.emplace(0, page); + records.pool.emplace(2 * page, page); + records.pool.emplace(4 * page, page); + records.pages.emplace(address - 2 * page, AmmPool::Page{page, 0, 3, true}); + records.pooledUses.emplace(page, 1); + records.pages.emplace(address - page, AmmPool::Page{3 * page, 0, 3, true}); + records.pooledUses.emplace(3 * page, 1); + return records; +} + +void Map(AmmPool::Records& records, std::uint64_t bytes, const std::function& mapRun) { + AmmPool::MapScattered(records, address, bytes, 1, 3, mapRun); +} + +void TestScatteredMap() { + auto records = Fragmented(); + std::map runs; + Map(records, 3 * page, [&](std::uint64_t at, std::uint64_t physical, std::uint64_t size) { runs.emplace(at, physical); Require(size == page); }); + Require(records.pool.empty()); + Require(runs.size() == 3 && runs.at(address) == 0 && runs.at(address + page) == 2 * page && runs.at(address + 2 * page) == 4 * page); + for (std::uint64_t index = 0; index < 3; ++index) { + const auto found = records.pages.find(address + index * page); + Require(found != records.pages.end() && found->second.physical == 2 * index * page && found->second.pooled && found->second.type == 1 && found->second.protection == 3); + Require(records.pooledUses.at(2 * index * page) == 1); + } + RequireConsistent(records); +} + +void TestSplitRun() { + AmmPool::Records records; + records.pool.emplace(0, page); + records.pool.emplace(2 * page, 3 * page); + std::vector> runs; + Map(records, 3 * page, [&](std::uint64_t at, std::uint64_t physical, std::uint64_t size) { runs.emplace_back(physical, size); }); + Require(runs.size() == 2 && runs[0] == std::make_pair(std::uint64_t{0}, page) && runs[1] == std::make_pair(2 * page, 2 * page)); + Require(records.pool.size() == 1 && records.pool.at(4 * page) == page); + Require(records.pages.size() == 3 && records.pooledUses.size() == 3); + RequireConsistent(records); +} + +void TestRefusals() { + auto records = Fragmented(); + const auto before = records; + bool refused = false; + try { + Map(records, 4 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) { Require(false); }); + } catch (const std::runtime_error& error) { + refused = std::string(error.what()).starts_with("AMM: no free run of "); + } + Require(refused && Equal(records, before)); +} + +void TestHostMapFailsAfterEachRun() { + for (int failAt = 0; failAt < 3; ++failAt) { + auto records = Fragmented(); + const auto before = records; + int mapped = 0; + bool failed = false; + try { + Map(records, 3 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) { + if (mapped++ == failAt) throw std::runtime_error("AMM: map failed"); + }); + } catch (const std::runtime_error&) { + failed = true; + } + Require(failed && Equal(records, before)); + RequireConsistent(records); + Map(records, 3 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) {}); + Require(FreeBytes(records) == 0); + RequireConsistent(records); + } +} + +void TestEveryAllocationFails() { + long failures = 0; + for (long allocation = 0;; ++allocation) { + Require(allocation < 1000); + auto records = Fragmented(); + const auto before = records; + bool failed = false; + failingAllocation.store(allocation); + try { + Map(records, 3 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) {}); + } catch (const std::bad_alloc&) { + failed = true; + } + failingAllocation.store(-1); + if (!failed) { + Require(records.pool.empty() && records.pages.size() == 5 && records.pooledUses.size() == 5); + RequireConsistent(records); + break; + } + ++failures; + Require(Equal(records, before)); + RequireConsistent(records); + Map(records, 3 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) {}); + Require(FreeBytes(records) == 0); + RequireConsistent(records); + } + Require(failures >= 8); +} + +void TestEveryAllocationFailsWithASplit() { + long failures = 0; + for (long allocation = 0;; ++allocation) { + Require(allocation < 1000); + AmmPool::Records records; + records.pool.emplace(0, page); + records.pool.emplace(2 * page, 3 * page); + const auto before = records; + bool failed = false; + failingAllocation.store(allocation); + try { + Map(records, 3 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) {}); + } catch (const std::bad_alloc&) { + failed = true; + } + failingAllocation.store(-1); + if (!failed) { + Require(records.pool.size() == 1 && records.pool.at(4 * page) == page && records.pages.size() == 3); + RequireConsistent(records); + break; + } + ++failures; + Require(Equal(records, before)); + RequireConsistent(records); + Map(records, 4 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) {}); + Require(FreeBytes(records) == 0); + } + Require(failures >= 8); +} + +void TestPrtAllocationShape() { + for (int failAt = 0; failAt < 3; ++failAt) { + AmmPool::Records records; + records.pool.emplace(0, 2 * page); + records.pool.emplace(3 * page, page); + records.pool.emplace(5 * page, 2 * page); + const auto before = records; + int mapped = 0; + bool failed = false; + try { + Map(records, 5 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t) { + if (mapped++ == failAt) throw std::runtime_error("AMM: map failed"); + }); + } catch (const std::runtime_error&) { + failed = true; + } + Require(failed && Equal(records, before)); + RequireConsistent(records); + std::vector sizes; + Map(records, 5 * page, [&](std::uint64_t, std::uint64_t, std::uint64_t size) { sizes.push_back(size); }); + Require(sizes.size() == 3 && sizes[0] == 2 * page && sizes[1] == page && sizes[2] == 2 * page); + Require(FreeBytes(records) == 0 && records.pages.size() == 5); + RequireConsistent(records); + } +} + +void TestAddPageFailures() { + long failures = 0; + for (long allocation = 0;; ++allocation) { + Require(allocation < 100); + AmmPool::Records records; + records.pages.emplace(address, AmmPool::Page{0, 0, 3, true}); + records.pooledUses.emplace(0, 1); + const auto before = records; + bool failed = false; + failingAllocation.store(allocation); + try { + AmmPool::AddPage(records, address + page, AmmPool::Page{page, 0, 3, true}); + } catch (const std::bad_alloc&) { + failed = true; + } + failingAllocation.store(-1); + if (!failed) { + Require(records.pages.size() == 2 && records.pooledUses.at(page) == 1); + RequireConsistent(records); + break; + } + ++failures; + Require(Equal(records, before)); + RequireConsistent(records); + } + Require(failures >= 1); +} + +} + +int main() { + try { + TestScatteredMap(); + TestSplitRun(); + TestRefusals(); + TestHostMapFailsAfterEachRun(); + TestEveryAllocationFails(); + TestEveryAllocationFailsWithASplit(); + TestPrtAllocationShape(); + TestAddPageFailures(); + std::puts("AMM scattered pool pages published exactly once"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index 2795ed3172..3b06c206fa 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -235,6 +235,7 @@ void TestKernelEventQueue() { Require(sceKernelAddAmprEvent(eq, 7, &userData) == 0); Recorder recorder; Require(sceAmprCommandBufferWriteKernelEventQueue_04_00(&recorder.buffer, static_cast(eq), 7, 0x1234, 0) == 0); + RequireAppended(recorder, 0, 0, Apr::Opcode::WriteKernelEventQueue, sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00(static_cast(eq), 7, 0x1234)); Require(sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0) == 0); KernelEvent event{}; int count = 0; @@ -715,6 +716,29 @@ void TestResolveFailures() { std::remove(paths[0]); } +void TestResolveForEachVariants() { + const char* paths[]{"ampr_foreach_existing.bin", "ampr_foreach_missing.bin"}; + const char* names[]{"foreach_existing.bin", "foreach_missing.bin"}; + { std::ofstream file(paths[0], std::ios::binary); file << "data"; } + std::uint32_t expected = 0; + std::uint32_t failed = 99; + Require(sceKernelAprResolveFilepathsToIds(paths, 1, &expected, &failed) == 0); + std::uint32_t ids[]{123, 123}; + int results[]{123, 123}; + Require(sceKernelAprResolveFilepathsToIdsForEach(paths, 2, ids, results) == 1); + Require(ids[0] == expected && ids[1] == 0xffffffffu && results[0] == 0 && results[1] == static_cast(0x80020002)); + ids[0] = ids[1] = 123; + results[0] = results[1] = 123; + Require(sceKernelAprResolveFilepathsWithPrefixToIdsForEach("ampr_", names, 2, ids, results) == 1); + Require(ids[0] == expected && ids[1] == 0xffffffffu && results[0] == 0 && results[1] == static_cast(0x80020002)); + std::uint64_t sizes[]{123, 123}; + ids[0] = ids[1] = 123; + results[0] = results[1] = 123; + Require(sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizesForEach("ampr_", names, 2, ids, sizes, results) == 1); + Require(ids[0] == expected && ids[1] == 0xffffffffu && sizes[0] == 4 && sizes[1] == 0 && results[0] == 0 && results[1] == static_cast(0x80020002)); + std::remove(paths[0]); +} + void TestMalformedCommandSizes() { const auto requireFailure = [](const void* record, std::size_t recordBytes, std::uint32_t bufferBytes, const char* expected) { std::array memory{}; @@ -1125,6 +1149,101 @@ void TestAmmRemapKeepsPoolReferences() { Require(At(fresh) == 0xC1); } +void TestAmmMapsScatteredPoolPages() { + std::uint64_t start = 0; + std::uint64_t end = 0; + std::uint64_t multimapStart = 0; + std::uint64_t multimapEnd = 0; + Require(sceAmprAmmGetVirtualAddressRanges(&start, &end, &multimapStart, &multimapEnd) == 0); + const auto exhausts = [](Recorder& recorder) { + try { + SubmitAmm(recorder); + } catch (const std::runtime_error& error) { + return std::string(error.what()).starts_with("AMM: no free run of "); + } + return false; + }; + const auto drained = start + 1024 * page; + for (std::uint64_t index = 0;; ++index) { + Require(index < 4096); + Recorder drain; + Require(sceAmprAmmCommandBufferMap(&drain.buffer, drained + index * page, page, 0, cpuReadWrite) == 0); + if (exhausts(drain)) break; + } + + std::int64_t pool = -1; + Require(sceAmprAmmGiveDirectMemory(0, static_cast(sceKernelGetDirectMemorySize()), 5 * page, page, 1, &pool) == 0); + const auto singles = start + 6144 * page; + Recorder maps; + for (std::uint64_t index = 0; index < 5; ++index) Require(sceAmprAmmCommandBufferMap(&maps.buffer, singles + index * page, page, 0, cpuReadWrite) == 0); + SubmitAmm(maps); + for (std::uint64_t index = 0; index < 5; ++index) At(singles + index * page) = 0x100 + index; + + Recorder release; + for (const std::uint64_t index : {0, 2, 4}) Require(sceAmprAmmCommandBufferUnmap(&release.buffer, singles + index * page, page) == 0); + SubmitAmm(release); + + const auto scattered = start + 6400 * page; + Recorder gather; + Require(sceAmprAmmCommandBufferMap(&gather.buffer, scattered, 3 * page, 0, cpuReadWrite) == 0); + SubmitAmm(gather); + for (std::uint64_t index = 0; index < 3; ++index) At(scattered + index * page) = 0x200 + index; + for (std::uint64_t index = 0; index < 3; ++index) Require(At(scattered + index * page) == 0x200 + index); + Require(At(singles + page) == 0x101 && At(singles + 3 * page) == 0x103); + + Recorder empty; + Require(sceAmprAmmCommandBufferMap(&empty.buffer, singles, page, 0, cpuReadWrite) == 0); + Require(exhausts(empty)); +} + +void TestAmmPrtAllocationFailureKeepsZeros() { + std::uint64_t start = 0; + std::uint64_t end = 0; + std::uint64_t multimapStart = 0; + std::uint64_t multimapEnd = 0; + Require(sceAmprAmmGetVirtualAddressRanges(&start, &end, &multimapStart, &multimapEnd) == 0); + const auto exhausts = [](Recorder& recorder) { + try { + SubmitAmm(recorder); + } catch (const std::runtime_error& error) { + return std::string(error.what()).starts_with("AMM: no free run of "); + } + return false; + }; + const auto drained = start + 24576 * page; + for (std::uint64_t index = 0;; ++index) { + Require(index < 4096); + Recorder drain; + Require(sceAmprAmmCommandBufferMap(&drain.buffer, drained + index * page, page, 0, cpuReadWrite) == 0); + if (exhausts(drain)) break; + } + std::int64_t pool = -1; + Require(sceAmprAmmGiveDirectMemory(0, static_cast(sceKernelGetDirectMemorySize()), page, page, 1, &pool) == 0); + + const std::uint64_t prt = start + 28672 * page; + Recorder reserve; + Require(sceAmprAmmCommandBufferMapAsPrt(&reserve.buffer, prt, 4 * page) == 0); + SubmitAmm(reserve); + Require(At(prt) == 0 && At(prt + 3 * page) == 0); + + Recorder tooMany; + Require(sceAmprAmmCommandBufferAllocatePaForPrt(&tooMany.buffer, prt + page, 2 * page, 0, cpuReadWrite) == 0); + Require(exhausts(tooMany)); + Require(Protection(prt + page) == 0x10 && At(prt) == 0 && At(prt + page) == 0 && At(prt + 2 * page) == 0 && At(prt + 3 * page) == 0); + + Recorder one; + Require(sceAmprAmmCommandBufferAllocatePaForPrt(&one.buffer, prt + page, page, 0, cpuReadWrite) == 0); + SubmitAmm(one); + Require(Protection(prt + page) == 0x03); + At(prt + page) = 0x61; + Require(At(prt + page) == 0x61 && At(prt) == 0 && At(prt + 2 * page) == 0); + + Recorder none; + Require(sceAmprAmmCommandBufferAllocatePaForPrt(&none.buffer, prt + 2 * page, page, 0, cpuReadWrite) == 0); + Require(exhausts(none)); + Require(At(prt + 2 * page) == 0 && At(prt + page) == 0x61); +} + void TestAmmMapFailureKeepsPoolOwnership() { std::uint64_t start = 0; std::uint64_t end = 0; @@ -1351,6 +1470,26 @@ void TestAmmPrt() { Require(sceAmprAmmMeasureAmmCommandSizeAllocatePaForPrt(prt + 8, page, 0, cpuReadWrite) == ammRejected); } +void TestWriteAddressCommand() { + Recorder recorder; + alignas(8) std::uint64_t target = 0; + const auto offset = recorder.Offset(); + const auto commands = recorder.Commands(); + Require(sceAmprCommandBufferWriteAddress_04_00(&recorder.buffer, &target, 0x1122334455667788ull, 0) == 0); + RequireAppended(recorder, offset, commands, Apr::Opcode::WriteAddress, sceAmprMeasureCommandSizeWriteAddress_04_00()); + Apr::WriteAddressCommand command{}; + std::memcpy(&command, recorder.memory.data() + offset, sizeof(command)); + Require(command.address == reinterpret_cast(&target) && command.value == 0x1122334455667788ull && command.flags == 0); + Require(sceAmprMeasureCommandSizeWriteAddress_04_00() == sizeof(Apr::WriteAddressCommand)); + Require(sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00(0, 0, 0) == sizeof(Apr::WriteKernelEventQueueCommand)); + Require(sceAmprCommandBufferGetType(&recorder.buffer) == static_cast(Apr::BufferType::Apr)); + Require(sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0) == 0); + Require(target == 0x1122334455667788ull); + Apr::CommandBufferObject generic{}; + Require(sceAmprCommandBufferConstructor(&generic) == 0); + Require(sceAmprCommandBufferGetType(&generic) == static_cast(Apr::BufferType::Generic)); +} + void TestKernelWriteCommands() { std::uint64_t start = 0; std::uint64_t end = 0; @@ -1444,6 +1583,7 @@ int main() { TestFullBuffer(); TestOffsetPastBuffer(); TestSubmission(); + TestWriteAddressCommand(); TestKernelEventQueue(); TestWaits(); TestWaitSatisfiedAfterSubmission(); @@ -1460,6 +1600,7 @@ int main() { TestReadPastEndOfFileZeroFills(); TestReadAtUnseekableOffsetThrows(); TestResolveFailures(); + TestResolveForEachVariants(); TestMalformedCommandSizes(); TestResolveSymbolicLink(); TestGatherScatter(); @@ -1467,9 +1608,11 @@ int main() { TestAmmUsageStats(); TestAmmRemapKeepsPoolReferences(); TestAmmMapFailureKeepsPoolOwnership(); + TestAmmMapsScatteredPoolPages(); TestAmm(); TestAmmRemapAndProtect(); TestAmmPrt(); + TestAmmPrtAllocationFailureKeepsZeros(); TestKernelWriteCommands(); return 0; } diff --git a/core/libs/tests/GuestAprRead.cpp b/core/libs/tests/GuestAprRead.cpp index 004346dbcd..da7ee7df7c 100644 --- a/core/libs/tests/GuestAprRead.cpp +++ b/core/libs/tests/GuestAprRead.cpp @@ -173,6 +173,45 @@ void TestStreams(const Fixture& fixture) { Require(held, "cannot hold 600 guest fopen streams"); } +void TestQueries() { + constexpr int GuestEinval = 22; + constexpr int GuestEnoent = 2; + const char* names[] = {"generated.bin", "missing.bin"}; + std::array ids{0, 0}; + std::array sizes{1, 1}; + std::uint32_t failed = 7; + Require(sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizes("apr_read_", names, 1, ids.data(), sizes.data(), &failed) == 0, "APR did not resolve a prefixed path"); + Require(failed == 7 && sizes[0] == FileBytes, "APR did not report the prefixed file's size"); + const auto id = ids[0]; + ids = {0, 0}; + sizes = {1, 1}; + Require(sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizes("apr_read_", names, 2, ids.data(), sizes.data(), &failed) == -1 && *__error_nid_postfix() == GuestEnoent, "APR resolved a missing prefixed path"); + Require(failed == 1 && ids[0] == id && sizes[0] == FileBytes && ids[1] == 0xFFFFFFFFu && sizes[1] == 0, "APR did not mark only the missing prefixed path"); + std::uint64_t size = 0; + Require(sceKernelAprGetFileSize(id, nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "APR accepted a null file size"); + Require(sceKernelAprGetFileSize(id, &size) == 0 && size == FileBytes, "APR reported a wrong file size"); + FileStat stat{}; + Require(sceKernelAprGetFileStat(id, nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "APR accepted a null file stat"); + Require(sceKernelAprGetFileStat(id, &stat) == 0 && stat.st_size == static_cast(FileBytes), "APR reported a wrong file stat size"); + std::vector data(4096, 0xCC); + const Apr::ReadFileCommand command{{Apr::Opcode::ReadFile, sizeof(Apr::ReadFileCommand)}, id, 0, reinterpret_cast(data.data()), data.size(), 100}; + 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()}; + std::uint32_t result = 0xFFFFFFFFu; + std::uint32_t first = 0; + Require(sceKernelAprSubmitCommandBufferAndGetResult(&buffer, 0, nullptr, &first) == -1 && *__error_nid_postfix() == GuestEinval && first == 0, "APR accepted a null result"); + Require(sceKernelAprSubmitCommandBufferAndGetResult(nullptr, 0, &result, &first) == -1 && *__error_nid_postfix() == GuestEinval, "APR accepted a null command buffer"); + Require(sceKernelAprSubmitCommandBufferAndGetResult(&buffer, 0, &result, nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "APR accepted a null submission id"); + Require(result == 0xFFFFFFFFu && first == 0, "a rejected APR submission wrote its result or id"); + Require(sceKernelAprSubmitCommandBufferAndGetResult(&buffer, 0, &result, &first) == 0 && result == 0 && first != 0, "APR did not return a result and an id"); + Require(sceKernelAprWaitCommandBuffer(first) == 0, "APR did not wait for the submission"); + for (std::size_t index = 0; index < data.size(); ++index) Require(data[index] == FileByte(100 + index), "APR read through the submission with a result returned incorrect bytes"); + std::uint32_t second = 0; + Require(sceKernelAprSubmitCommandBufferAndGetResult(&buffer, 1, &result, &second) == 0 && second == first + 1, "APR submission ids did not count up"); + Require(sceKernelAprWaitCommandBuffer(second) == 0, "APR did not wait for the second submission"); +} + #ifdef _WIN32 std::uintptr_t faultBegin = 0; std::size_t faultBytes = 0; @@ -263,6 +302,8 @@ int main(int argc, char** argv) { TestRead(fixture, storage.Data()); } else if (mode == "eof") { TestEof(fixture); + } else if (mode == "queries") { + TestQueries(); } else if (mode == "errors") { TestErrors(fixture); } else if (mode == "streams") { diff --git a/core/libs/tests/GuestAudio3dParameters.cpp b/core/libs/tests/GuestAudio3dParameters.cpp index 232c2e1065..41583e675d 100644 --- a/core/libs/tests/GuestAudio3dParameters.cpp +++ b/core/libs/tests/GuestAudio3dParameters.cpp @@ -98,5 +98,8 @@ int main() { CheckInvalidDestination(); CheckShortBuffer(); CheckExtendedBuffer(); + Require(sceAudio3dTerminate() == static_cast(0x80EA0007), "terminate before initialize reports not ready"); + Require(sceAudio3dInitialize(1) == static_cast(0x80EA0004), "positive reserved parameter rejected in sceAudio3dInitialize"); + Require(sceAudio3dInitialize(42) == static_cast(0x80EA0004), "positive reserved parameter rejected in sceAudio3dInitialize"); Require(sceAudio3dInitialize(-1) == static_cast(0x80EA0004), "non-zero reserved parameter rejected in sceAudio3dInitialize"); } diff --git a/core/libs/tests/GuestAudiodec.cpp b/core/libs/tests/GuestAudiodec.cpp index ed04f8ee5d..6461dd9bf4 100644 --- a/core/libs/tests/GuestAudiodec.cpp +++ b/core/libs/tests/GuestAudiodec.cpp @@ -369,8 +369,16 @@ void TestAt9() { } int main() { + Require(sceAudiodecInitLibrary(0) == errorInvalidType); Require(sceAudiodecInitLibrary(4) == errorInvalidType); Require(sceAudiodecTermLibrary(0) == errorInvalidType); + Require(sceAudiodecTermLibrary(4) == errorInvalidType); + Require(sceAudiodecDeleteDecoder(0) == errorInvalidHandle); + Require(sceAudiodecDeleteDecoder(-1) == errorInvalidHandle); + Require(sceAudiodecClearContext(0) == errorInvalidHandle); + Require(sceAudiodecClearContext(-1) == errorInvalidHandle); + Require(sceAudiodecDecode(0, nullptr) == errorInvalidHandle); + Require(sceAudiodecDecode(-1, nullptr) == errorInvalidHandle); AudiodecParamMp3 mp3{sizeof(mp3), 1}; AudiodecMp3Info mp3Info{}; @@ -379,6 +387,7 @@ int main() { Require(sceAudiodecCreateDecoder(&ctrl, 2) == errorArg); for (std::uint32_t type = 1; type <= 3; ++type) Require(sceAudiodecInitLibrary(type) == 0); Require(sceAudiodecCreateDecoder(nullptr, 2) == errorInvalidCtrlPointer); + Require(sceAudiodecCreateDecoder(&ctrl, 0) == errorInvalidType); Require(sceAudiodecCreateDecoder(&ctrl, 5) == errorInvalidType); mp3.ui_size = 12; Require(sceAudiodecCreateDecoder(&ctrl, 2) == errorInvalidParamSize); diff --git a/core/libs/tests/GuestBluetoothHid.cpp b/core/libs/tests/GuestBluetoothHid.cpp index 56965517eb..718f2442c3 100644 --- a/core/libs/tests/GuestBluetoothHid.cpp +++ b/core/libs/tests/GuestBluetoothHid.cpp @@ -30,6 +30,7 @@ int main() { constexpr auto invalidArgument = static_cast(0x81540001u); constexpr auto noCallback = static_cast(0x81540006u); Require(sceBluetoothHidRegisterCallback(nullptr, nullptr, nullptr) == notInitialized, "initialization check order"); + Require(sceBluetoothHidRegisterCallback(&Callback, nullptr, nullptr) == notInitialized, "non-null callback before initialization"); Require(sceBluetoothHidRegisterDevice(1, 2) == notInitialized, "registration before initialization"); Require(sceBluetoothHidUnregisterDevice(1, 2) == notInitialized, "unregistration before initialization"); Require(sceBluetoothHidUnregisterCallback() == notInitialized, "callback removal before initialization"); @@ -95,6 +96,8 @@ int main() { Require(threw, "set feature report did not throw"); Require(sceBluetoothHidUnregisterDevice(0x44f, 0x1000) == 0, "device unregistration failed"); Require(sceBluetoothHidUnregisterCallback() == 0, "callback unregistration failed"); + Require(sceBluetoothHidUnregisterCallback() == noCallback, "duplicate callback unregistration"); + Require(sceBluetoothHidRegisterDevice(0x44f, 0x1000) == noCallback, "device registration after callback unregistration"); BluetoothHidThreadParam thread{}; BluetoothHidParam filled; std::memset(&filled, 0xa5, sizeof(filled)); @@ -111,6 +114,7 @@ int main() { std::memset(&filled, 0x5a, sizeof(filled)); Require(sceBluetoothHidParamInitialize(&filled, nullptr) == 0 && filled.threadParam == nullptr && filled.reserved == 0 && filled.padding == 0x5a5a5a5au, "a null thread parameter was not stored as given"); Require(sceBluetoothHidParamInitialize(nullptr, &thread) == invalidArgument, "a null parameter block was not refused"); + Require(sceBluetoothHidParamInitialize(nullptr, nullptr) == invalidArgument, "null parameter and null thread parameter were not refused"); std::puts("BluetoothHid tests passed"); return 0; } diff --git a/core/libs/tests/GuestCamera.cpp b/core/libs/tests/GuestCamera.cpp index 06c5b3b7ee..18afa7bc26 100644 --- a/core/libs/tests/GuestCamera.cpp +++ b/core/libs/tests/GuestCamera.cpp @@ -39,6 +39,15 @@ void CheckClosed() { const CameraStartParameter start = Start(1, 1); CameraFrameData frame{}; frame.sizeThis = sizeof(CameraFrameData); + for (const int handle : {0, -1}) { + Require(sceCameraClose(handle) == ErrorParam, "closing non-positive handle must report PARAM"); + Require(sceCameraCloseByHandle(handle) == ErrorParam, "closing by non-positive handle must report PARAM"); + Require(sceCameraStart(handle, &start) == ErrorParam, "starting non-positive handle must report PARAM"); + Require(sceCameraStartByHandle(handle, &start) == ErrorParam, "starting by non-positive handle must report PARAM"); + Require(sceCameraStop(handle) == ErrorParam, "stopping non-positive handle must report PARAM"); + Require(sceCameraStopByHandle(handle) == ErrorParam, "stopping by non-positive handle must report PARAM"); + Require(sceCameraIsValidFrameData(handle, &frame) == ErrorParam, "validating non-positive handle must report PARAM"); + } Require(sceCameraClose(1) == ErrorNotOpen, "closing before any open must report NOT_OPEN"); Require(sceCameraCloseByHandle(1) == ErrorNotOpen, "closing by handle before any open must report NOT_OPEN"); Require(sceCameraStart(1, &start) == ErrorNotOpen, "starting before any open must report NOT_OPEN"); @@ -99,6 +108,11 @@ void CheckFrames() { void CheckClose() { Require(sceCameraClose(0) == ErrorParam, "closing handle 0 must be rejected"); + Require(sceCameraClose(-1) == ErrorParam, "closing negative handle must be rejected"); + Require(sceCameraCloseByHandle(0) == ErrorParam, "closing by handle 0 must be rejected"); + Require(sceCameraCloseByHandle(-1) == ErrorParam, "closing by negative handle must be rejected"); + Require(sceCameraStopByHandle(0) == ErrorParam, "stopping by handle 0 must be rejected"); + Require(sceCameraStopByHandle(-1) == ErrorParam, "stopping by negative handle must be rejected"); const CameraStartParameter start = Start(1, 1); CameraFrameData frame{}; frame.sizeThis = sizeof(frame); diff --git a/core/libs/tests/GuestCameraSettings.cpp b/core/libs/tests/GuestCameraSettings.cpp index 8628cdbe19..45d17164ed 100644 --- a/core/libs/tests/GuestCameraSettings.cpp +++ b/core/libs/tests/GuestCameraSettings.cpp @@ -56,6 +56,8 @@ void CheckGetter(GetU32 get, std::uint32_t expected, const char* message) { Require(get(99, Channel0, &value, nullptr) == ErrorNotOpen && value == 0x5a5a5a5a, "a fabricated handle must not read settings"); Require(get(1, 0, &value, nullptr) == ErrorParam, "a getter must reject channel 0"); Require(get(1, ChannelBoth, &value, nullptr) == ErrorParam, "a getter must reject both channels"); + Require(get(1, 4, &value, nullptr) == ErrorParam, "a getter must reject channel 4"); + Require(get(1, 0xFFFFFFFFu, &value, nullptr) == ErrorParam, "a getter must reject invalid channel"); Require(get(1, Channel0, nullptr, nullptr) == ErrorParam, "a getter must reject a null result"); Require(get(1, Channel0, &value, &option) == ErrorParam, "a getter must reject an option"); Require(value == 0x5a5a5a5a, "a refused getter must not write its result"); @@ -113,7 +115,9 @@ void CheckSetter(SetU32 set, std::uint32_t largestValue) { std::uint32_t option = 0; Require(set(1, ChannelBoth, largestValue, nullptr) == ErrorNotConnected, "a setter must report NOT_CONNECTED"); Require(set(0, Channel0, 0, nullptr) == ErrorParam, "a setter must reject handle 0"); + Require(set(1, 0, 0, nullptr) == ErrorParam, "a setter must reject channel 0"); Require(set(1, 4, 0, nullptr) == ErrorParam, "a setter must reject channel 4"); + Require(set(1, 0xFFFFFFFFu, 0, nullptr) == ErrorParam, "a setter must reject invalid channel"); Require(set(1, Channel0, 0, &option) == ErrorParam, "a setter must reject an option"); if (largestValue != UINT32_MAX) Require(set(1, Channel0, largestValue + 1, nullptr) == ErrorParam, "a setter must reject a value past its range"); } diff --git a/core/libs/tests/GuestCesJapanese.cpp b/core/libs/tests/GuestCesJapanese.cpp index 42b8e2c16e..5009755c54 100644 --- a/core/libs/tests/GuestCesJapanese.cpp +++ b/core/libs/tests/GuestCesJapanese.cpp @@ -328,9 +328,12 @@ int main() { std::memcpy(&utf32Be, "\x00\x00\x65\xE5", 4); Require(sceCesUtf32beToSJis(&utf32Be, sjis, mbcs, sizeof(mbcs), &produced) == 0 && std::memcmp(mbcs, "\x93\xFA", 2) == 0); Require(sceCesUtf32beToSJis(nullptr, sjis, mbcs, sizeof(mbcs), &produced) == invalidSrcBuffer); + Require(sceCesUtf32leToSJis(nullptr, sjis, mbcs, sizeof(mbcs), &produced) == invalidSrcBuffer); std::uint32_t utf32Le = 0; std::memcpy(&utf32Le, "\xE5\x65\x00\x00", 4); Require(sceCesUtf32leToEucJp(&utf32Le, euc, mbcs, sizeof(mbcs), &produced) == 0 && std::memcmp(mbcs, "\xC6\xFC", 2) == 0); + Require(sceCesUtf32leToEucJp(nullptr, euc, mbcs, sizeof(mbcs), &produced) == invalidSrcBuffer); + Require(sceCesUtf32beToEucJp(nullptr, euc, mbcs, sizeof(mbcs), &produced) == invalidSrcBuffer); constexpr int microsoftForms[] = {0x00A2, 0x00A3, 0x00A5, 0x00AC, 0x2014, 0x2016, 0x203E, 0x2212, 0x301C, 0xFEFF}; for (int code = 1; code < 0x10000; ++code) { @@ -354,6 +357,7 @@ int main() { Require(sceCesSJisCodeToJisX0208(0x0041, &ku, &ten) == invalidEncode); Require(sceCesSJisCodeToJisX0208(0x827F, &ku, &ten) == invalidEncode); Require(sceCesSJisCodeToJisX0208(0x82A0, nullptr, &ten) == invalidDstBuffer); + Require(sceCesSJisCodeToJisX0208(0x82A0, &ku, nullptr) == invalidDstBuffer); Require(sceCesJisX0208ToSJisCode(4, 2, &sjisCode) == 0 && sjisCode == 0x82A0); Require(sceCesJisX0208ToSJisCode(0, 2, &sjisCode) == invalidEncode); Require(sceCesJisX0208ToSJisCode(4, 95, &sjisCode) == invalidEncode); diff --git a/core/libs/tests/GuestCesMbc.cpp b/core/libs/tests/GuestCesMbc.cpp index 9d1e17c780..0b4dad4d08 100644 --- a/core/libs/tests/GuestCesMbc.cpp +++ b/core/libs/tests/GuestCesMbc.cpp @@ -412,10 +412,14 @@ int main() { std::uint32_t wide = 0; std::memcpy(&wide, "\x00\x00\x30\x42", 4); Require(sceCesUtf32beToMbc(&wide, sjis, mbc, sizeof(mbc), &produced) == 0 && produced == 2 && std::memcmp(mbc, "\x82\xA0", 2) == 0); + Require(sceCesUtf32beToMbc(nullptr, sjis, mbc, sizeof(mbc), &produced) == invalidSrcBuffer); + Require(sceCesUtf32leToMbc(nullptr, sjis, mbc, sizeof(mbc), &produced) == invalidSrcBuffer); Require(sceCesUtf32leToMbc(&wide, sjis, mbc, sizeof(mbc), &produced) == invalidEncode); std::uint16_t narrow = 0; std::memcpy(&narrow, "\xD8\x00", 2); Require(sceCesUtf16leToMbc(&narrow, 1, &consumed, cp1252, mbc, sizeof(mbc), &produced) == 0 && consumed == 1 && produced == 1 && mbc[0] == 0xD8); + Require(sceCesUtf16leToMbc(nullptr, 1, &consumed, cp1252, mbc, sizeof(mbc), &produced) == invalidSrcBuffer); + Require(sceCesUtf16beToMbc(nullptr, 1, &consumed, cp1252, mbc, sizeof(mbc), &produced) == invalidSrcBuffer); Require(sceCesUtf16beToMbc(&narrow, 1, &consumed, cp1252, mbc, sizeof(mbc), &produced) == srcBufferEnd); Require(sceCesUtf8ToMbc(Bytes("A\xE4\xB8\xAD"), 4, &consumed, gbk, mbc, sizeof(mbc), &produced) == 0 && consumed == 1 && produced == 1 && mbc[0] == 'A'); diff --git a/core/libs/tests/GuestCesUnicode.cpp b/core/libs/tests/GuestCesUnicode.cpp index fa8fee7f88..b1282d9bf9 100644 --- a/core/libs/tests/GuestCesUnicode.cpp +++ b/core/libs/tests/GuestCesUnicode.cpp @@ -146,10 +146,14 @@ void TestErrors() { Require(sceCesUtf8ToUtf32(nullptr, 4, &consumed, &scalar) == INVALID_SRC_BUFFER); Require(sceCesUtf8ToUtf32(emoji, 4, &consumed, nullptr) == INVALID_DST_BUFFER); Require(sceCesUtf8ToUcs2(emoji, 4, &consumed, nullptr) == INVALID_DST_BUFFER); + Require(sceCesUtf8ToUtf16(nullptr, 4, &consumed, units, 2, &produced) == INVALID_SRC_BUFFER); + Require(sceCesUtf8ToUtf16be(nullptr, 4, &consumed, units, 2, &produced) == INVALID_SRC_BUFFER); + Require(sceCesUtf8ToUtf16le(nullptr, 4, &consumed, units, 2, &produced) == INVALID_SRC_BUFFER); Require(sceCesUtf8ToUtf16(emoji, 4, &consumed, nullptr, 2, &produced) == INVALID_DST_BUFFER); Require(sceCesUtf16ToUtf8(nullptr, 1, &consumed, bytes, 4, &produced) == INVALID_SRC_BUFFER); Require(sceCesUtf16ToUtf8(high, 0, &consumed, bytes, 4, &produced) == SRC_BUFFER_END); Require(sceCesUtf32beToUtf8(nullptr, bytes, 4, &produced) == INVALID_SRC_BUFFER); + Require(sceCesUtf32leToUtf8(nullptr, bytes, 4, &produced) == INVALID_SRC_BUFFER); Require(sceCesUtf32ToUtf8(0, nullptr, 4, &produced) == INVALID_DST_BUFFER); Require(sceCesUtf8ToUtf32(emoji, 5, nullptr, &scalar) == 0 && scalar == 0x1F600); Require(sceCesUtf8ToUtf16(emoji, 5, nullptr, units, 2, nullptr) == 0 && units[0] == 0xD83D && units[1] == 0xDE00); diff --git a/core/libs/tests/GuestCesWide.cpp b/core/libs/tests/GuestCesWide.cpp index e644f825e5..44c46ce266 100644 --- a/core/libs/tests/GuestCesWide.cpp +++ b/core/libs/tests/GuestCesWide.cpp @@ -67,6 +67,10 @@ int main() { std::memcpy(&yLe, "\x78\x01\x00\x00", 4); Require(sceCesUtf32leToSbc(&yLe, cp1252, &sbc) == 0 && sbc == 0x9F); Require(sceCesUtf32leToSbc(nullptr, cp1252, &sbc) == invalidSrcBuffer); + Require(sceCesUtf32beToSbc(nullptr, cp1252, &sbc) == invalidSrcBuffer); + Require(sceCesUtf16beToSbc(nullptr, 1, &length, cp1252, &sbc) == invalidSrcBuffer); + Require(sceCesUtf16leToSbc(nullptr, 1, &length, cp1252, &sbc) == invalidSrcBuffer); + Require(sceCesUtf32ToSbc(0x0178, nullptr, &sbc) == invalidParameter); for (int value = 0; value < 256; ++value) { if (value == 0x98) { Require(sceCesSbcToUtf32(cp1251, static_cast(value), &utf32) == unassignedCode); diff --git a/core/libs/tests/GuestCharacter.cpp b/core/libs/tests/GuestCharacter.cpp index 7f900acea7..fa7a93f4ff 100644 --- a/core/libs/tests/GuestCharacter.cpp +++ b/core/libs/tests/GuestCharacter.cpp @@ -92,4 +92,12 @@ int main() { Require(!isascii_nid_postfix(256)); Require(toascii_nid_postfix(258) == 2); Require(toascii_nid_postfix(-1) == 0x7F); + for (int c = EOF; c <= 255; ++c) { + Require(bool(isnumber_nid_postfix(c)) == bool(std::isdigit(c))); + Require(bool(ishexnumber_nid_postfix(c)) == bool(std::isxdigit(c))); + if (c >= '0' && c <= '9') Require(digittoint_nid_postfix(c) == c - '0'); + else if (c >= 'a' && c <= 'f') Require(digittoint_nid_postfix(c) == c - 'a' + 10); + else if (c >= 'A' && c <= 'F') Require(digittoint_nid_postfix(c) == c - 'A' + 10); + else Require(digittoint_nid_postfix(c) == 0); + } } diff --git a/core/libs/tests/GuestCondRecursiveMutex.cpp b/core/libs/tests/GuestCondRecursiveMutex.cpp index e21300d818..e4a05e60b6 100644 --- a/core/libs/tests/GuestCondRecursiveMutex.cpp +++ b/core/libs/tests/GuestCondRecursiveMutex.cpp @@ -55,7 +55,13 @@ static int PosixTimedwait(Context& context) { static void WaitWithRecursiveMutex(int (*wait)(Context&)) { Context context; PthreadMutexattr attr = nullptr; + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSettype(nullptr, MUTEX_TYPE_RECURSIVE) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSettype(&attr, MUTEX_TYPE_RECURSIVE) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrInit(&attr) == SCE_OK); + Require(scePthreadMutexattrSettype(&attr, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSettype(&attr, 5) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrSettype(&attr, MUTEX_TYPE_RECURSIVE) == SCE_OK); Require(scePthreadMutexInit(&context.mutex, &attr, nullptr) == SCE_OK); Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); diff --git a/core/libs/tests/GuestContentSearch.cpp b/core/libs/tests/GuestContentSearch.cpp index bac0286fe0..1adc3da4a6 100644 --- a/core/libs/tests/GuestContentSearch.cpp +++ b/core/libs/tests/GuestContentSearch.cpp @@ -54,6 +54,8 @@ int main() { Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, &updateId) == ErrorNotInitialized); Require(sceContentSearchGetTotalContentSize(&columnSet, 1, &size, &updateId) == ErrorNotInitialized); Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 2, &count, infos, &updateId) == ErrorNotInitialized); + Require(sceContentSearchGetMetadataFieldInfo(0, nullptr, nullptr, nullptr) == ErrorNotInitialized); + Require(sceContentSearchGetMetadataValue(0, nullptr, nullptr) == ErrorNotInitialized); Require(sceContentSearchOpenMetadata("/data/photo.jpg", &metadataId) == ErrorNotInitialized); Require(sceContentSearchOpenMetadataByContentId(1, &metadataId) == ErrorNotInitialized); Require(sceContentSearchCloseMetadata(1) == ErrorNotInitialized); @@ -95,6 +97,18 @@ int main() { Require(sceContentSearchGetNumOfContent(&columnSet, 1, nullptr, &updateId) == ErrorInvalidArgument); Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, nullptr) == ErrorInvalidArgument); Require(sceContentSearchGetTotalContentSize(nullptr, 1, &size, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetTotalContentSize(nullptr, 0, nullptr, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetTotalContentSize(nullptr, 0, &size, nullptr) == ErrorInvalidArgument); + std::int32_t metadataType = 0, fieldSize = 0; + Require(sceContentSearchGetMetadataFieldInfo(1, nullptr, nullptr, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetMetadataFieldInfo(1, "field", nullptr, &fieldSize) == ErrorInvalidArgument); + Require(sceContentSearchGetMetadataFieldInfo(1, "field", &metadataType, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetMetadataValue(1, nullptr, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetMetadataValue(1, "field", nullptr) == ErrorInvalidArgument); + Require(sceContentSearchOpenMetadata(nullptr, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchOpenMetadata("/data/photo.jpg", nullptr) == ErrorInvalidArgument); + Require(sceContentSearchOpenMetadata(nullptr, &metadataId) == ErrorInvalidArgument); + Require(sceContentSearchOpenMetadataByContentId(1, nullptr) == ErrorInvalidArgument); Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 2, &count, nullptr, &updateId) == ErrorInvalidArgument); ContentSearchContentColumn unknownColumn{8, 0, &photo, 2}; ContentSearchContentColumnSet unknownColumnSet{&unknownColumn, 1, 2}; diff --git a/core/libs/tests/GuestConvertKeycodeTitle.cpp b/core/libs/tests/GuestConvertKeycodeTitle.cpp index 21ccc29fa5..0ee595de20 100644 --- a/core/libs/tests/GuestConvertKeycodeTitle.cpp +++ b/core/libs/tests/GuestConvertKeycodeTitle.cpp @@ -22,7 +22,10 @@ int main() { char16_t character = 0; if (sceConvertKeycodeGetVirtualKeycode(0, 0, nullptr) != static_cast(0x80BC0031u)) return 1; if (sceConvertKeycodeGetVirtualKeycode(0, 0, &virtualKey) != static_cast(0x80BC001Du)) return 1; + if (sceConvertKeycodeGetVirtualKeycode(0, 40, &virtualKey) != static_cast(0x80BC001Du)) return 1; if (sceConvertKeycodeGetCharacter(0, 0, 0, nullptr) != static_cast(0x80BC0031u)) return 1; if (sceConvertKeycodeGetCharacter(0, 0, 0, &character) != static_cast(0x80BC001Du)) return 1; + if (sceConvertKeycodeGetCharacter(0, 0, 40, &character) != static_cast(0x80BC001Du)) return 1; + if (sceConvertKeycodeGetImeKeyboardType(0x10000000, nullptr) != static_cast(0x80BC0031u)) return 1; return sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4 ? 0 : 1; } diff --git a/core/libs/tests/GuestCountOnes.cpp b/core/libs/tests/GuestCountOnes.cpp new file mode 100644 index 0000000000..55265b826f --- /dev/null +++ b/core/libs/tests/GuestCountOnes.cpp @@ -0,0 +1,68 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (condition) return; + std::fprintf(stderr, "count ones mismatch\n"); + std::abort(); +} + +static std::uint32_t Reference(std::uint64_t value) { + std::uint32_t result = 0; + while (value != 0) { + result += value & 1; + value >>= 1; + } + return result; +} + +template +static void Check(TCounter count) { + constexpr unsigned width = std::numeric_limits::digits; + const auto maximum = std::numeric_limits::max(); + Require(count(0) == 0); + Require(count(maximum) == width); + if constexpr (width <= 16) { + for (std::uint32_t value = 0; value <= maximum; ++value) { + Require(count(static_cast(value)) == Reference(value)); + } + } + for (unsigned bit = 0; bit < width; ++bit) { + const auto value = static_cast(TValue{1} << bit); + Require(count(value) == 1); + Require(count(static_cast(~value)) == width - 1); + for (unsigned other = 0; other < width; ++other) { + const auto pair = static_cast(value | (TValue{1} << other)); + Require(count(pair) == (bit == other ? 1u : 2u)); + } + } + if constexpr (width > 16) { + std::uint64_t state = 0x769c81d32f04a5b7ULL; + for (unsigned index = 0; index < 100000; ++index) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + const auto value = static_cast(state); + Require(count(value) == Reference(value)); + Require(count(static_cast(~value)) == width - Reference(value)); + } + } +} + +int main(int argc, char** argv) { + Require(argc == 2); + const int saved = *__error_nid_postfix(); + *__error_nid_postfix() = 87; + if (std::strcmp(argv[1], "uc") == 0) Check(stdc_count_ones_uc_nid_postfix); + else if (std::strcmp(argv[1], "us") == 0) Check(stdc_count_ones_us_nid_postfix); + else if (std::strcmp(argv[1], "ui") == 0) Check(stdc_count_ones_ui_nid_postfix); + else if (std::strcmp(argv[1], "ul") == 0) Check(stdc_count_ones_ul_nid_postfix); + else if (std::strcmp(argv[1], "ull") == 0) Check(stdc_count_ones_ull_nid_postfix); + else Require(false); + Require(*__error_nid_postfix() == 87); + *__error_nid_postfix() = saved; +} diff --git a/core/libs/tests/GuestCountZeros.cpp b/core/libs/tests/GuestCountZeros.cpp new file mode 100644 index 0000000000..8dd64f97aa --- /dev/null +++ b/core/libs/tests/GuestCountZeros.cpp @@ -0,0 +1,70 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (condition) return; + std::fprintf(stderr, "count zeros mismatch\n"); + std::abort(); +} + +template +static std::uint32_t Reference(TValue value) { + std::uint32_t result = 0; + for (unsigned bit = 0; bit < std::numeric_limits::digits; ++bit) { + result += (value & 1) == 0; + value = static_cast(value >> 1); + } + return result; +} + +template +static void Check(TCounter count) { + constexpr unsigned width = std::numeric_limits::digits; + const auto maximum = std::numeric_limits::max(); + Require(count(0) == width); + Require(count(maximum) == 0); + if constexpr (width <= 16) { + for (std::uint32_t value = 0; value <= maximum; ++value) { + Require(count(static_cast(value)) == Reference(static_cast(value))); + } + } + for (unsigned bit = 0; bit < width; ++bit) { + const auto value = static_cast(TValue{1} << bit); + Require(count(value) == width - 1); + Require(count(static_cast(~value)) == 1); + for (unsigned other = 0; other < width; ++other) { + const auto pair = static_cast(value | (TValue{1} << other)); + Require(count(pair) == width - (bit == other ? 1u : 2u)); + } + } + if constexpr (width > 16) { + std::uint64_t state = 0x6b82439d015af7ceULL; + for (unsigned index = 0; index < 100000; ++index) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + const auto value = static_cast(state); + Require(count(value) == Reference(value)); + const auto complement = static_cast(~value); + Require(count(complement) == Reference(complement)); + } + } +} + +int main(int argc, char** argv) { + Require(argc == 2); + const int saved = *__error_nid_postfix(); + *__error_nid_postfix() = 87; + if (std::strcmp(argv[1], "uc") == 0) Check(stdc_count_zeros_uc_nid_postfix); + else if (std::strcmp(argv[1], "us") == 0) Check(stdc_count_zeros_us_nid_postfix); + else if (std::strcmp(argv[1], "ui") == 0) Check(stdc_count_zeros_ui_nid_postfix); + else if (std::strcmp(argv[1], "ul") == 0) Check(stdc_count_zeros_ul_nid_postfix); + else if (std::strcmp(argv[1], "ull") == 0) Check(stdc_count_zeros_ull_nid_postfix); + else Require(false); + Require(*__error_nid_postfix() == 87); + *__error_nid_postfix() = saved; +} diff --git a/core/libs/tests/GuestCpuTimeClocks.cpp b/core/libs/tests/GuestCpuTimeClocks.cpp index 18ae60bb87..964264e060 100644 --- a/core/libs/tests/GuestCpuTimeClocks.cpp +++ b/core/libs/tests/GuestCpuTimeClocks.cpp @@ -115,7 +115,10 @@ int main() { Require(sceKernelClockGettime(GUEST_CLOCK_PROCESS_CPUTIME_ID, &time) == SCE_OK); Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); Require(sceKernelClockGettime(GUEST_CLOCK_THREAD_CPUTIME_ID, nullptr) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelClockGettime(GUEST_CLOCK_PROCESS_CPUTIME_ID, nullptr) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelClockGetres(GUEST_CLOCK_THREAD_CPUTIME_ID, nullptr) == SCE_KERNEL_ERROR_EFAULT); Require(sceKernelClockGetres(GUEST_CLOCK_PROCESS_CPUTIME_ID, nullptr) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelClockGettime(static_cast(-1), &time) == SCE_KERNEL_ERROR_EINVAL); RequireResolution(GUEST_CLOCK_THREAD_CPUTIME_ID); RequireResolution(GUEST_CLOCK_PROCESS_CPUTIME_ID); diff --git a/core/libs/tests/GuestCurrentCpu.cpp b/core/libs/tests/GuestCurrentCpu.cpp index 323eead874..7a07c2311c 100644 --- a/core/libs/tests/GuestCurrentCpu.cpp +++ b/core/libs/tests/GuestCurrentCpu.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "SceTypes.hpp" @@ -35,6 +36,13 @@ int main() { Require(sceKernelGetCpuFrequency() > 0 && static_cast(sceKernelGetCpuFrequency()) == sceKernelGetTscFrequency()); Require(sceKernelGetTscFrequency() >= 100000000u && sceKernelGetTscFrequency() <= 10000000000u); + KernelCpumask currentMask = 0; + Require(scePthreadSetaffinity(nullptr, available) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadSetaffinity(scePthreadSelf(), 0) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadGetaffinity(nullptr, ¤tMask) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadGetaffinity(scePthreadSelf(), nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadGetaffinity(scePthreadSelf(), ¤tMask) == SCE_OK && currentMask != 0); + const std::array affinities{available, 0x1FFB, 0x3, 0x3F, 0x1000, 0x1, 0xFFFF, 0x10000}; for (const KernelCpumask affinity : affinities) { const KernelCpumask expected = (affinity & available) != 0 ? affinity & available : available; diff --git a/core/libs/tests/GuestDatagramReceive.cpp b/core/libs/tests/GuestDatagramReceive.cpp index d11fe04de6..4ee3e106a3 100644 --- a/core/libs/tests/GuestDatagramReceive.cpp +++ b/core/libs/tests/GuestDatagramReceive.cpp @@ -144,7 +144,14 @@ int main(int argc, char** argv) { Require(argc == 2, "expected receive API argument"); const bool net = std::strncmp(argv[1], "sce", 3) == 0; const bool from = std::strstr(argv[1], "from") != nullptr; - if (net) Require(sceNetInit_nid_postfix() == 0, "initialize libSceNet"); + if (net) { + Require(sceNetInit_nid_postfix() == 0, "initialize libSceNet"); + Require(sceNetSocketClose(-1) < 0, "reject invalid descriptor in sceNetSocketClose"); + Require(*sceNetErrnoLoc() == 9, "bad file descriptor error"); + } else { + Require(close_nid_postfix(-1) < 0, "reject invalid descriptor in close"); + Require(*__error_nid_postfix() == 9, "bad file descriptor error"); + } Check(net, from, 2); Check(net, from, 28); } diff --git a/core/libs/tests/GuestDinkumwareInternals.cpp b/core/libs/tests/GuestDinkumwareInternals.cpp index c1be3d457e..fc64c245ea 100644 --- a/core/libs/tests/GuestDinkumwareInternals.cpp +++ b/core/libs/tests/GuestDinkumwareInternals.cpp @@ -253,6 +253,10 @@ void TestDoubleHelpers() { CheckExp(0.0, 1.0, 2000, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp scaled overflow"); CheckExp(1842.0, 1.0, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp inside the positive cutoff overflows"); CheckExp(-1842.0, 1.0, 0, 0.0, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_Exp inside the negative cutoff underflows"); + CheckExp(-0x1.a3ae147ae147bp+3, 1.0, 3, 0x1.0e6b5a0a65117p-16, Finite, 0, FE_INEXACT, "_Exp rounds a negative exponent as titles do"); + CheckExp(-0x1.e1fae147ae148p+6, 2.0, 3, 0x1.1e838b63af9c1p-170, Finite, 0, FE_INEXACT, "_Exp rounds a scaled small result as titles do"); + CheckExp(0x1.b073b645a1cacp+7, -1.0, -1, -0x1.edef67d6786cep+310, Finite, 0, FE_INEXACT, "_Exp rounds a large negative product as titles do"); + CheckExp(0x1.5c523eb57b55fp-53, 2.0, 0, 0x1.0000000000001p+1, Finite, 0, FE_INEXACT, "_Exp evaluates arguments above half an epsilon"); errno = 0; std::feclearexcept(FE_ALL_EXCEPT); @@ -340,6 +344,10 @@ void TestDoubleHelpers() { CheckFExp(0.0f, 1.0f, 200, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp scaled overflow"); CheckFExp(229.0f, 1.0f, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp inside the positive cutoff overflows"); CheckFExp(-229.0f, 1.0f, 0, 0.0f, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_FExp inside the negative cutoff underflows"); + CheckFExp(-0x1.4fbe76p+0f, 1.0f, 3, 0x1.13e1bp+1f, Finite, 0, FE_INEXACT, "_FExp rounds a negative exponent as titles do"); + CheckFExp(-0x1.fd4b92p+5f, 1.0f, -3, 0x1.1d1806p-95f, Finite, 0, FE_INEXACT, "_FExp rounds a scaled small result as titles do"); + CheckFExp(0x1.5e147ap+4f, 2.0f, 3, 0x1.7b078cp+35f, Finite, 0, FE_INEXACT, "_FExp rounds a large product as titles do"); + CheckFExp(0x1.0644b8p-24f, 0.25f, 2, 0x1.000002p+0f, Finite, 0, FE_INEXACT, "_FExp evaluates arguments above half an epsilon"); errno = 0; std::feclearexcept(FE_ALL_EXCEPT); diff --git a/core/libs/tests/GuestDirectoryRewind.cpp b/core/libs/tests/GuestDirectoryRewind.cpp index 424c5bda23..2343ebfe37 100644 --- a/core/libs/tests/GuestDirectoryRewind.cpp +++ b/core/libs/tests/GuestDirectoryRewind.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -144,5 +145,7 @@ int main() { Require(sceKernelLseek(reused, 0, 1) == 5); Require(sceKernelLseek(reused, 0, 2) == 0); Require(sceKernelClose(reused) == 0); + Require(sceKernelClose(reused) == SCE_KERNEL_ERROR_EBADF); + Require(sceKernelClose(-1) == SCE_KERNEL_ERROR_EBADF); std::filesystem::remove_all(root); } diff --git a/core/libs/tests/GuestFileTimestamps.cpp b/core/libs/tests/GuestFileTimestamps.cpp index 8a081181a5..0cdc8fa88b 100644 --- a/core/libs/tests/GuestFileTimestamps.cpp +++ b/core/libs/tests/GuestFileTimestamps.cpp @@ -255,6 +255,7 @@ int main() { FileStat linkStatus{}; Require(lstat_nid_postfix(linkPath.c_str(), &linkStatus) == 0 && Same(linkStatus.st_mtim, first[1]), "symlink mtime updated"); #endif + Require(unlink_nid_postfix(linkPath.c_str()) == 0, "remove the symlink"); } int pipe[2]; Require(pipe_nid_postfix(pipe) == 0, "create pipe"); @@ -263,6 +264,9 @@ int main() { #ifdef _WIN32 Require(futimes_nid_postfix(pipe[0], first) == -1 && *__error_nid_postfix() == 22, "pipe timestamp update fails"); #endif + const KernelTimeval invalidUsec[2] = {{first[0].tv_sec, -1}, {first[1].tv_sec, 0}}; + Require(futimes_nid_postfix(pipe[0], invalidUsec) == -1 && *__error_nid_postfix() == 22, "negative usec rejected in futimes"); + Require(futimes_nid_postfix(-1, first) == -1 && *__error_nid_postfix() == 9, "bad file descriptor in futimes"); Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0, "close pipe"); const auto throughFile = (root / "file.txt" / "child").string(); for (const auto update : {utimes_nid_postfix, lutimes_nid_postfix}) { @@ -300,6 +304,9 @@ int main() { Require(RemoveDirectoryW(std::filesystem::path(directoryLink).c_str()) && DeleteFileW(std::filesystem::path(fileLink).c_str()) && DeleteFileW(std::filesystem::path(danglingLink).c_str()), "remove the link fixtures"); } +#endif +#ifdef _WIN32 + DeleteFileW(std::filesystem::path(linkPath).c_str()); #endif std::filesystem::remove_all(root); } diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index e303ad72f9..c854d40b34 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -1061,6 +1061,11 @@ int main() { std::filesystem::create_symlink("missing.txt", dangling); const auto statLoop = root / "stat-loop"; std::filesystem::create_symlink("stat-loop", statLoop); + for (const auto* linkPath : {&dangling, &statLoop}) { + Require(sceKernelRmdir(linkPath->string().c_str()) == static_cast(0x80020014u)); + Require(rmdir_nid_postfix(linkPath->string().c_str()) == -1 && *__error_nid_postfix() == 20); + Require(lstat_nid_postfix(linkPath->string().c_str(), &linkStatus) == 0 && (linkStatus.st_mode & 0170000) == 0120000); + } errno = 0; Require(!File::TryFillFileStat(statLoop, &status) && errno == ELOOP); Require(sceKernelStat(statLoop.generic_string().c_str(), &status) == SCE_KERNEL_ERROR_ELOOP); @@ -1123,6 +1128,19 @@ int main() { Require(sceKernelRmdir(belowStatLoop.c_str()) == static_cast(0x8002003eu)); Require(rmdir_nid_postfix(belowStatLoop.c_str()) == -1 && *__error_nid_postfix() == 62); Require(sceKernelRmdir(statLoop.string().c_str()) == static_cast(0x80020014u)); + Require(rmdir_nid_postfix(statLoop.string().c_str()) == -1 && *__error_nid_postfix() == 20); + const auto danglingDirectoryLink = root / "dangling-directory-link"; + Require(CreateSymbolicLinkW(danglingDirectoryLink.c_str(), L"missing-directory", + SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0); + for (const auto* linkPath : {&danglingLink, &danglingDirectoryLink, &directoryLink}) { + Require(sceKernelRmdir(linkPath->string().c_str()) == static_cast(0x80020014u)); + Require(rmdir_nid_postfix(linkPath->string().c_str()) == -1 && *__error_nid_postfix() == 20); + const DWORD linkAttributes = GetFileAttributesW(linkPath->c_str()); + Require(linkAttributes != INVALID_FILE_ATTRIBUTES && (linkAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0); + } + const DWORD targetAttributes = GetFileAttributesW(targetDirectory.c_str()); + Require(targetAttributes != INVALID_FILE_ATTRIBUTES && (targetAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + Require(RemoveDirectoryW(danglingDirectoryLink.c_str()) != 0); Require(sceKernelTruncate_nid_postfix(statLoop.string().c_str(), 0) == static_cast(0x8002003eu)); Require(truncate_nid_postfix(statLoop.string().c_str(), 0) == -1 && *__error_nid_postfix() == 62); FileStat followed{}; diff --git a/core/libs/tests/GuestFloatingPrefix.cpp b/core/libs/tests/GuestFloatingPrefix.cpp new file mode 100644 index 0000000000..cf81250bad --- /dev/null +++ b/core/libs/tests/GuestFloatingPrefix.cpp @@ -0,0 +1,206 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::exit(1); +} + +template +using Prefix = int (APS5_VABI*)(const TChar**, TChar**); + +struct Case { + const char* text; + int code; + std::size_t cursor; + bool writesEnd; +}; + +constexpr std::array Cases{ + Case{"", 1, 0, false}, Case{"12.5e4", 1, 0, false}, Case{".", 1, 0, false}, + Case{"-", 9, 1, false}, Case{"+", 1, 1, false}, Case{" -12", 9, 3, false}, + Case{"\t\r\n\v\f +0x1p2", 2, 9, false}, Case{"0x1", 2, 2, false}, + Case{"-0X.8p2", 10, 3, false}, Case{"0x", 1, 0, false}, Case{"0x.", 1, 0, false}, + Case{"0xg", 1, 0, false}, Case{"0x.1", 2, 2, false}, Case{"0x..1", 1, 0, false}, + Case{"0x 1", 1, 0, false}, Case{" -0xg", 9, 3, false}, + Case{"n", 0, 0, true}, Case{"na", 0, 0, true}, Case{"nax", 0, 0, true}, + Case{" -Na", 0, 0, true}, Case{"nan", 4, 3, true}, Case{"-NaN", 4, 4, true}, + Case{"nan()", 4, 5, true}, Case{"nan(a_Z09)", 4, 10, true}, + Case{"nan(payload", 4, 3, true}, Case{"nan(a-b)", 4, 3, true}, + Case{"nan(a(b))", 4, 3, true}, Case{"nan()tail", 4, 5, true}, + Case{"i", 0, 0, true}, Case{"in", 0, 0, true}, Case{"inx", 0, 0, true}, + Case{" -iN", 0, 0, true}, Case{"inf", 3, 3, true}, Case{"-INF", 11, 4, true}, + Case{"infinity", 3, 8, true}, Case{"+InFiNiTy!", 3, 9, true}, + Case{"infi", 3, 3, true}, Case{"infin", 3, 3, true}, Case{"infinit", 3, 3, true}, + Case{"infinite", 3, 3, true}, Case{" -infinity", 11, 11, true}, + Case{"not-a-number", 0, 0, true}, Case{"garbage", 1, 0, false}, +}; + +template +static std::vector Text(const char* source) { + std::vector result; + do result.push_back(static_cast(*source)); while (*source++); + return result; +} + +template +static void CheckOne(Prefix function, const TChar* text, const Case& test) { + constexpr std::uint64_t guard = 0xd3c4b5a69788796a; + TChar sentinel{}; + struct Outputs { + std::uint64_t before; + const TChar* cursor; + std::uint64_t middle; + TChar* end; + std::uint64_t after; + }; + static_assert(offsetof(Outputs, cursor) == 8 && offsetof(Outputs, middle) == 16); + static_assert(offsetof(Outputs, end) == 24 && offsetof(Outputs, after) == 32); + Outputs outputs{guard, text, guard, &sentinel, guard}; + const int code = function(&outputs.cursor, &outputs.end); + if (code != test.code || outputs.cursor != text + test.cursor || + outputs.end != (test.writesEnd ? text + test.cursor : &sentinel)) { + std::fprintf(stderr, "prefix '%s': code %d expected %d, cursor %td expected %zu\n", + test.text, code, test.code, outputs.cursor - text, test.cursor); + std::exit(1); + } + Require(outputs.before == guard && outputs.middle == guard && outputs.after == guard, "output bounds"); + const TChar* cursor = text; + Require(function(&cursor, nullptr) == test.code && cursor == text + test.cursor, "optional end pointer"); +} + +template +static void Check(Prefix function, unsigned payloadLimit = 256) { + for (const auto& test : Cases) { + auto text = Text(test.text); + const auto saved = text; + CheckOne(function, text.data(), test); + Require(text == saved, "input modified"); + } + for (const char* word : {"nan", "inf", "infinity"}) { + const std::size_t length = std::strlen(word); + for (unsigned mask = 0; mask < (1u << length); ++mask) { + std::string text = word; + for (std::size_t index = 0; index < length; ++index) + if (mask & (1u << index)) text[index] -= 'a' - 'A'; + for (const char* sign : {"", "+", "-"}) { + const std::string input = " \t" + std::string(sign) + text; + const int code = word[0] == 'n' ? 4 : 3 + (sign[0] == '-' ? 8 : 0); + const Case test{input.c_str(), code, input.size(), true}; + const auto data = Text(input.c_str()); + CheckOne(function, data.data(), test); + } + } + } + for (unsigned value = 0; value < payloadLimit; ++value) { + const std::array payload{TChar{'n'}, TChar{'a'}, TChar{'n'}, TChar{'('}, static_cast(value), TChar{')'}, TChar{}}; + const bool accepted = (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') || + (value >= '0' && value <= '9') || value == '_'; + const std::size_t stop = accepted ? 6 : value == ')' ? 5 : 3; + const std::string label = "payload byte " + std::to_string(value) + ", unit width " + std::to_string(sizeof(TChar)); + CheckOne(function, payload.data(), Case{label.c_str(), 4, stop, true}); + } + if constexpr (sizeof(TChar) == 2) { + const std::array high{static_cast(0x100), TChar{'i'}, TChar{'n'}, TChar{}}; + CheckOne(function, high.data(), Case{"wide code unit", 1, 0, false}); + } +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + const std::size_t page = info.dwPageSize; + auto* allocation = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(allocation != nullptr, "guard allocation"); + DWORD previous = 0; + Require(VirtualProtect(allocation + page, page, PAGE_NOACCESS, &previous) != 0, "guard protection"); +#else + const std::size_t page = static_cast(sysconf(_SC_PAGESIZE)); + auto* allocation = static_cast(mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED, "guard allocation"); + Require(mprotect(allocation + page, page, PROT_NONE) == 0, "guard protection"); +#endif + for (const auto& test : Cases) { + const auto source = Text(test.text); + const std::size_t bytes = source.size() * sizeof(TChar); + auto* text = reinterpret_cast(allocation + page - bytes); + std::memcpy(text, source.data(), bytes); + CheckOne(function, text, test); + } +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0, "guard release"); +#else + Require(munmap(allocation, page * 2) == 0, "guard release"); +#endif +} + +template +static void CheckConcurrent(Prefix function) { + std::array workers; + for (auto& worker : workers) { + worker = std::thread([function] { + for (unsigned repeat = 0; repeat < 8; ++repeat) { + for (const auto& test : Cases) { + const auto text = Text(test.text); + CheckOne(function, text.data(), test); + } + } + }); + } + for (auto& worker : workers) worker.join(); +} + +int main(int argc, char** argv) { + Require(argc == 4, "library directory, patched/unpatched, narrow/wide"); + const bool wide = std::string(argv[3]) == "wide"; + Require(wide || std::string(argv[3]) == "narrow", "width selection"); + const std::string name = wide ? "_WStopfx" : "_Stopfx"; + const std::string symbol = std::string(argv[2]) == "patched" ? Nid::ComputeNid(name, "") : name + "_nid_postfix"; + const auto path = std::filesystem::absolute(argv[1]) / "libc.prx"; +#ifdef _WIN32 + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + const auto startup = GetModuleHandleW(L"libc.prx"); + const auto library = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + Require(library != nullptr, "load libc"); + std::array loaded{}; + const auto length = GetModuleFileNameW(library, loaded.data(), static_cast(loaded.size())); + Require(length != 0 && length < loaded.size() && std::filesystem::equivalent(path, std::filesystem::path(loaded.data())), "wrong libc loaded"); + if (startup) Require(startup == library, "startup libc differs"); + const auto function = reinterpret_cast(GetProcAddress(library, symbol.c_str())); +#else + const auto library = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!library) std::fprintf(stderr, "%s\n", dlerror()); + Require(library != nullptr, "load libc"); + const auto function = dlsym(library, symbol.c_str()); +#endif + if (!function) std::fprintf(stderr, "Missing %s (%s)\n", name.c_str(), symbol.c_str()); + Require(function != nullptr, "cannot exercise missing prefix parser"); + if (wide) { + const auto prefix = reinterpret_cast>(function); + Check(prefix); + CheckConcurrent(prefix); + } else { + const auto prefix = reinterpret_cast>(function); + Check(prefix); + CheckConcurrent(prefix); + } + std::printf("%s: prefix codes, pointer updates, input/output bounds and guarded reads passed\n", name.c_str()); + return 0; +} diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index 093c88fe45..902c864087 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -18,6 +18,9 @@ #include #include #include +#ifdef __GLIBC__ +#include +#endif static void Check(bool value, int line) { if (!value) { @@ -45,6 +48,41 @@ static void APS5_VABI Release(void*, void* pointer) { std::free(pointer); } +struct HeldBlock { + void* pointer; + std::uint32_t size; + bool released; +}; + +static std::vector heldBlocks; +static std::size_t heldBlockLimit = SIZE_MAX; + +static void* APS5_VABI HoldAllocate(void*, std::uint32_t size) { + if (heldBlocks.size() >= heldBlockLimit) return nullptr; + void* pointer = std::malloc(size); + heldBlocks.push_back({pointer, size, false}); + return pointer; +} + +static void APS5_VABI HoldRelease(void*, void* pointer) { + for (HeldBlock& block : heldBlocks) { + if (block.pointer == pointer && !block.released) { + block.released = true; + return; + } + } + Require(false); +} + +static const HeldBlock* HeldBlockOf(const void* pointer) { + const auto address = reinterpret_cast(pointer); + for (const HeldBlock& block : heldBlocks) { + const auto begin = reinterpret_cast(block.pointer); + if (address >= begin && address - begin < block.size) return █ + } + return nullptr; +} + static std::uint32_t APS5_VABI CoarsePixelResolution() { return 16; } @@ -87,6 +125,18 @@ static std::int32_t APS5_VABI ParseText(FontTextSource* source, void** order, Fo return 1; } +static std::int32_t APS5_VABI ParseLine(FontTextSource* source, void** order, FontTextParseResult* result) { + auto* parsed = static_cast(source->textObject); + *order = const_cast(parsed->text + parsed->at); + if (parsed->text[parsed->at] == '\n') { + result->Terminate.terminateCode = '\n'; + return 0; + } + result->FontCode.font = parsed->font; + result->FontCode.code = static_cast(parsed->text[parsed->at++]); + return 1; +} + struct ParsedCodes { const std::uint32_t* codes; std::size_t count; @@ -106,6 +156,127 @@ static std::int32_t APS5_VABI ParseCodes(FontTextSource* source, void** order, F return 1; } +static void CheckWritingDoesNotOwnCharacters(const FontMemory* memory, FontHandle font) { + const std::string text(32, 'A'); + ParsedText parsed{text.c_str(), 0, font}; + FontTextSource source{}; + Require(sceFontTextSourceInit(&source, parsed.text, static_cast(text.size()), ParseText, &parsed) == SCE_FONT_OK); + FontTextCharacter* characters = nullptr; + const auto create = [&] { + parsed.at = 0; + FontString string = nullptr; + Require(sceFontCreateString(memory, &source, nullptr, &string) == SCE_FONT_OK); + std::uint32_t count = 0; + characters = sceFontStringRefersTextCharacters(string, &count); + Require(characters != nullptr && count == text.size()); + return string; + }; + const auto steps = [](FontWriting* writing) { + std::size_t count = 0; + while (sceFontWritingRefersRenderStep(writing) != nullptr) ++count; + return count; + }; +#ifdef __GLIBC__ + const auto before = mallinfo2().uordblks; +#endif + const int allocationsBefore = allocations; + for (int i = 0; i < 512; ++i) { + FontString string = create(); + FontWriting writing{}; + Require(sceFontWritingInit(&writing, string, characters) == SCE_FONT_OK && steps(&writing) == text.size()); + Require(sceFontDestroyString(&string) == SCE_FONT_OK); + } + Require(allocations == allocationsBefore); +#ifdef __GLIBC__ + Require(static_cast(mallinfo2().uordblks) - static_cast(before) < 256 * 1024); +#endif + FontString string = create(); + FontWriting first{}; + Require(sceFontWritingInit(&first, string, characters) == SCE_FONT_OK); + FontWriting copy; + std::memcpy(©, &first, sizeof(copy)); + Require(steps(&first) == text.size() && steps(©) == text.size()); + Require(sceFontDestroyString(&string) == SCE_FONT_OK); + string = create(); + Require(sceFontWritingInit(&first, string, characters) == SCE_FONT_OK && sceFontWritingInit(©, string, characters) == SCE_FONT_OK); + Require(steps(&first) == text.size() && steps(©) == text.size()); + Require(sceFontDestroyString(&string) == SCE_FONT_OK); + FontString other = create(); + const FontTextCharacter* otherCharacters = characters; + string = create(); + FontWriting failed{}; + Require(sceFontWritingInit(&failed, string, characters) == SCE_FONT_OK); + Require(sceFontWritingInit(&failed, string, otherCharacters) == SCE_FONT_ERROR_INVALID_PARAMETER && sceFontWritingRefersRenderStep(&failed) == nullptr); + Require(sceFontDestroyString(&string) == SCE_FONT_OK && sceFontDestroyString(&other) == SCE_FONT_OK); +} + +static void CheckWritingAfterStringDestroyed(FontHandle font) { + const FontMemoryInterface iface{HoldAllocate, HoldRelease, nullptr, nullptr, nullptr, nullptr}; + FontMemory memory{}; + Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); + const std::string text(32, 'A'); + ParsedText parsed{text.c_str(), 0, font}; + FontTextSource source{}; + Require(sceFontTextSourceInit(&source, parsed.text, static_cast(text.size()), ParseText, &parsed) == SCE_FONT_OK); + const auto create = [&](FontTextCharacter** characters) { + parsed.at = 0; + FontString string = nullptr; + Require(sceFontCreateString(&memory, &source, nullptr, &string) == SCE_FONT_OK && HeldBlockOf(string) != nullptr); + std::uint32_t count = 0; + *characters = sceFontStringRefersTextCharacters(string, &count); + Require(*characters != nullptr && count == text.size()); + Require(HeldBlockOf(*characters) != nullptr && HeldBlockOf(*characters + count - 1) == HeldBlockOf(*characters)); + return string; + }; + const auto steps = [](FontWriting* writing) { + std::size_t count = 0; + while (sceFontWritingRefersRenderStep(writing) != nullptr) ++count; + return count; + }; + const FontWritingLetterStep unset{}; + const FontWritingLetterStep* letter = nullptr; + FontTextCharacter* characters = nullptr; + parsed.at = 0; + FontString string = nullptr; + const std::size_t blocksBefore = heldBlocks.size(); + heldBlockLimit = blocksBefore + 1; + Require(sceFontCreateString(&memory, &source, nullptr, &string) == SCE_FONT_ERROR_ALLOCATION_FAILED && string == nullptr); + heldBlockLimit = SIZE_MAX; + Require(heldBlocks.size() == blocksBefore + 1 && heldBlocks.back().released); + FontString failedString = static_cast(heldBlocks.back().pointer); + Require(sceFontDestroyString(&failedString) == SCE_FONT_ERROR_INVALID_STRING && failedString == nullptr); + string = create(&characters); + FontString staleHandle = string; + const void* destroyed = string; + FontWriting writing{}; + Require(sceFontWritingInit(&writing, string, characters) == SCE_FONT_OK && sceFontWritingRefersRenderStep(&writing) != nullptr); + Require(sceFontWritingRefersRenderStepCharacter(&writing, &letter) == characters && letter != nullptr); + Require(sceFontWritingRefersRenderStep(&writing) != nullptr); + FontWriting copy; + std::memcpy(©, &writing, sizeof(copy)); + Require(sceFontDestroyString(&string) == SCE_FONT_OK && string == nullptr); + Require(HeldBlockOf(destroyed)->released && HeldBlockOf(characters)->released); + for (FontWriting* stale : {&writing, ©}) { + letter = &unset; + Require(sceFontWritingRefersRenderStepCharacter(stale, &letter) == nullptr && letter == nullptr); + Require(sceFontWritingRefersRenderStep(stale) == nullptr); + } + Require(sceFontWritingInit(©, staleHandle, characters) == SCE_FONT_ERROR_INVALID_STRING && sceFontWritingRefersRenderStep(©) == nullptr); + Require(sceFontDestroyString(&staleHandle) == SCE_FONT_ERROR_INVALID_STRING && staleHandle == nullptr); + FontTextCharacter* fresh = nullptr; + string = create(&fresh); + Require(sceFontWritingInit(&writing, string, fresh + 1) == SCE_FONT_OK && sceFontWritingInit(©, string, fresh) == SCE_FONT_OK); + Require(steps(&writing) == text.size() - 1 && sceFontWritingRefersRenderStepCharacter(&writing, &letter) == fresh + text.size() - 1); + Require(steps(©) == text.size()); + Require(sceFontDestroyString(&string) == SCE_FONT_OK); + for (const HeldBlock& block : heldBlocks) { + Require(block.released); + std::free(block.pointer); + } + heldBlocks.clear(); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); +} + static void CheckTextCodes(const FontMemory* memory, FontHandle font) { ParsedText parsed{"AVA", 0, font}; FontTextSource source{}; @@ -116,6 +287,16 @@ static void CheckTextCodes(const FontMemory* memory, FontHandle font) { FontTextCharacter* characters = sceFontStringRefersTextCharacters(string, &count); Require(characters != nullptr && count == 3); Require(sceFontStringGetWritingForm(string) == 0x12); + Require(sceFontStringGetTerminateCode(string) == 0 && sceFontStringGetTerminateOrder(string) == parsed.text + 3); + ParsedText line{"AV\nA", 0, font}; + FontTextSource lineSource{}; + Require(sceFontTextSourceInit(&lineSource, line.text, 4, ParseLine, &line) == SCE_FONT_OK); + FontString lineString = nullptr; + Require(sceFontCreateString(memory, &lineSource, nullptr, &lineString) == SCE_FONT_OK && lineString != nullptr); + std::uint32_t lineCount = 0; + Require(sceFontStringRefersTextCharacters(lineString, &lineCount) != nullptr && lineCount == 2); + Require(sceFontStringGetTerminateCode(lineString) == '\n' && sceFontStringGetTerminateOrder(lineString) == line.text + 2); + Require(sceFontDestroyString(&lineString) == SCE_FONT_OK); FontString rtlString = nullptr; Require(sceFontTextSourceSetWritingForm(&source, 0x11) == SCE_FONT_OK && sceFontCreateString(memory, &source, nullptr, &rtlString) == SCE_FONT_OK); Require(sceFontStringGetWritingForm(rtlString) == 0x11); @@ -232,6 +413,31 @@ static bool KerningIs(const FontKerning& kerning, float offsetX) { return kerning.offsetX == offsetX && kerning.offsetY == 0.0f && kerning.positionX == 0.0f && kerning.positionY == 0.0f; } +static void CheckRenderEffects(FontHandle font, FontRenderer renderer) { + const auto near = [](float value, float expected) { return std::fabs(value - expected) < 1e-6f; }; + float slant = 1.0f; + float weightX = 0.0f; + float weightY = 0.0f; + std::uint32_t mode = 7; + Require(sceFontSetupRenderEffectSlant(font, 0.25f) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + Require(sceFontSetupRenderEffectWeight(font, 1.02f, 0.98f, 0) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + Require(sceFontGetRenderEffectSlant(font, &slant) == SCE_FONT_ERROR_NOT_BOUND_RENDERER && slant == 0.0f); + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontSetupRenderEffectSlant(font, 0.25f) == SCE_FONT_OK); + Require(sceFontGetRenderEffectSlant(font, &slant) == SCE_FONT_OK && slant == 0.25f); + Require(sceFontSetupRenderEffectSlant(font, 2.0f) == SCE_FONT_OK); + Require(sceFontGetRenderEffectSlant(font, &slant) == SCE_FONT_OK && slant == 1.0f); + Require(sceFontSetupRenderEffectWeight(font, 1.02f, 0.98f, 1) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSetupRenderEffectWeight(font, 1.02f, 0.98f, 0) == SCE_FONT_OK); + Require(sceFontGetRenderEffectWeight(font, &weightX, &weightY, &mode) == SCE_FONT_OK && near(weightX, 1.02f) && near(weightY, 0.98f) && mode == 0); + Require(sceFontSetupRenderEffectWeight(font, 1.5f, 0.5f, 0) == SCE_FONT_OK); + Require(sceFontGetRenderEffectWeight(font, &weightX, &weightY, nullptr) == SCE_FONT_OK && near(weightX, 1.04f) && near(weightY, 0.96f)); + Require(sceFontSetupRenderEffectSlant(nullptr, 0.0f) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetupRenderEffectWeight(nullptr, 1.0f, 1.0f, 0) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetupRenderEffectSlant(font, 0.0f) == SCE_FONT_OK && sceFontSetupRenderEffectWeight(font, 1.0f, 1.0f, 0) == SCE_FONT_OK); + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); +} + static void CheckGlyphScale(FontHandle font, const FontMemory* memory, float expectedW, float expectedH) { float width = 0.0f; float height = 0.0f; @@ -650,6 +856,7 @@ int main() { Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); CheckPointAndDpiScale(font, library, &memory, renderer); + CheckRenderEffects(font, renderer); ParsedText parsed{"AV", 0, font}; FontTextSource source{}; @@ -697,6 +904,8 @@ int main() { Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == characters + 1 && out.letter == retainedLetter && out.guard == guard); Require(sceFontWritingRefersRenderStepCharacter(&writing, &out.letter) == nullptr && out.letter == nullptr); Require(sceFontDestroyString(&string) == SCE_FONT_OK); + CheckWritingDoesNotOwnCharacters(&memory, font); + CheckWritingAfterStringDestroyed(font); CheckTextCodes(&memory, font); Require(sceFontCloseFont(font) == SCE_FONT_OK); diff --git a/core/libs/tests/GuestFontMetrics.cpp b/core/libs/tests/GuestFontMetrics.cpp index 24d5f2b1ca..cd7a1c1fe6 100644 --- a/core/libs/tests/GuestFontMetrics.cpp +++ b/core/libs/tests/GuestFontMetrics.cpp @@ -128,6 +128,11 @@ int main() { Require(cleared.form == 0 && cleared.effectWeightX == 1.0f); Require(sceFontGetFontMetrics(plain, 1, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); Require(sceFontGetFontMetrics(nullptr, 1, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetEffectWeight(nullptr, 1.0f, 1.0f, 0) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetEffectWeight(plain, 1.0f, 1.0f, 1) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSetEffectSlant(nullptr, 0.0f) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontSetScalePixel(nullptr, 16.0f, 16.0f) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontCloseFont(nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); for (FontHandle font : {plain, typo, versionOne, os2Only}) Require(sceFontCloseFont(font) == SCE_FONT_OK); Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); diff --git a/core/libs/tests/GuestFontOutlineProfile.cpp b/core/libs/tests/GuestFontOutlineProfile.cpp index 1c57782d45..c91631a2c8 100644 --- a/core/libs/tests/GuestFontOutlineProfile.cpp +++ b/core/libs/tests/GuestFontOutlineProfile.cpp @@ -114,6 +114,12 @@ int main() { Require(sceFontGetFontGlyphsOutlineProfile(plain, &profile) == SCE_FONT_OK && Zero(profile)); for (FontHandle font : {profiled, plain, shortMaxp}) Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + FontLibrary nullLib = nullptr; + Require(sceFontDestroyLibrary(&nullLib) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(sceFontMemoryTerm(nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + FontMemory badMem{}; + Require(sceFontMemoryTerm(&badMem) == SCE_FONT_ERROR_INVALID_MEMORY); Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); std::puts("Font outline profile tests passed"); diff --git a/core/libs/tests/GuestFontScissor.cpp b/core/libs/tests/GuestFontScissor.cpp new file mode 100644 index 0000000000..60b5c09c3e --- /dev/null +++ b/core/libs/tests/GuestFontScissor.cpp @@ -0,0 +1,225 @@ +#include "prx/libSceFontFt/include/Api.hpp" +#include "prx/libSceFont/include/Api.hpp" +#include "prx/libSceFont/include/FontTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font scissor check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static int allocations = 0; +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + ++allocations; + return std::malloc(size); +} +static void APS5_VABI Release(void*, void* pointer) { + if (pointer) --allocations; + std::free(pointer); +} + +static void Put16(std::vector& out, std::uint32_t value) { + out.push_back(static_cast(value >> 8)); + out.push_back(static_cast(value)); +} + +static void Put32(std::vector& out, std::uint32_t value) { + Put16(out, value >> 16); + Put16(out, value & 0xFFFFu); +} + +static void PutAll16(std::vector& out, std::initializer_list values) { + for (const int value : values) Put16(out, static_cast(static_cast(value))); +} + +static std::vector SquareGlyphFont() { + constexpr std::uint32_t codepoint = 'A'; + constexpr int width = 900; + std::vector head; + Put32(head, 0x00010000u); + Put32(head, 0x00010000u); + Put32(head, 0); + Put32(head, 0x5F0F3CF5u); + PutAll16(head, {0x000B, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, width, 800, 0, 8, 2, 0, 0}); + std::vector hhea; + Put32(hhea, 0x00010000u); + PutAll16(hhea, {800, -200, 0, width, 0, 0, width, 1, 0, 0, 0, 0, 0, 0, 0, 2}); + std::vector maxp; + Put32(maxp, 0x00010000u); + PutAll16(maxp, {2, 4, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}); + std::vector hmtx; + PutAll16(hmtx, {width, 0, width, 100}); + std::vector glyf; + PutAll16(glyf, {1, 100, 0, width - 100, 700, 3, 0}); + glyf.insert(glyf.end(), {1, 1, 1, 1}); + PutAll16(glyf, {100, 0, width - 200, 0, 0, 700, 0, -700}); + std::vector loca; + PutAll16(loca, {0, 0, static_cast(glyf.size() / 2)}); + std::vector cmap; + PutAll16(cmap, {0, 1, 3, 1}); + Put32(cmap, 12); + PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, static_cast(codepoint), 0xFFFF, 0, static_cast(codepoint), 0xFFFF, 1 - static_cast(codepoint), 1, 0, 0}); + const std::vector*>> tables = { + {"cmap", &cmap}, {"glyf", &glyf}, {"head", &head}, {"hhea", &hhea}, {"hmtx", &hmtx}, {"loca", &loca}, {"maxp", &maxp}}; + std::vector font; + Put32(font, 0x00010000u); + PutAll16(font, {static_cast(tables.size()), 64, 2, static_cast(tables.size()) * 16 - 64}); + std::uint32_t offset = 12 + static_cast(tables.size()) * 16; + for (const auto& [tag, data] : tables) { + font.insert(font.end(), tag, tag + 4); + Put32(font, 0); + Put32(font, offset); + Put32(font, static_cast(data->size())); + offset += (static_cast(data->size()) + 3u) & ~3u; + } + for (const auto& [tag, data] : tables) { + font.insert(font.end(), data->begin(), data->end()); + font.resize((font.size() + 3u) & ~std::size_t{3}); + } + return font; +} + +static void CheckScissor(int width, int height, int x0, int y0, int w, int h, int expectedX0, int expectedY0, int expectedX1, int expectedY1) { + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, nullptr, 0, 1, width, height); + sceFontRenderSurfaceSetScissor(&surface, x0, y0, w, h); + Require(surface.sc_x0 == expectedX0 && surface.sc_y0 == expectedY0); + Require(surface.sc_x1 == expectedX1 && surface.sc_y1 == expectedY1); +} + +static void CheckScissorArithmetic() { + CheckScissor(100, 80, 10, 20, 30, 40, 10, 20, 40, 60); + CheckScissor(100, 80, -10, -5, 30, 20, 0, 0, 20, 15); + CheckScissor(100, 80, -30, -20, 30, 20, 0, 0, 0, 0); + CheckScissor(100, 80, 90, 70, 30, 20, 90, 70, 100, 80); + CheckScissor(100, 80, 100, 80, 10, 10, 100, 80, 100, 80); + CheckScissor(100, 80, 101, 81, 10, 10, 100, 80, 100, 80); + CheckScissor(100, 80, 0, 0, 0, 0, 0, 0, 0, 0); + CheckScissor(100, 80, 50, 40, 0, 0, 50, 40, 50, 40); + CheckScissor(100, 80, 50, 40, 1000, 1000, 50, 40, 100, 80); + CheckScissor(100, 80, -1000, -1000, 1000, 1000, 0, 0, 0, 0); + CheckScissor(100, 80, -5, -5, 10, 10, 0, 0, 5, 5); + CheckScissor(1, 1, 0, 0, 2, 2, 0, 0, 1, 1); + CheckScissor(0, 80, 10, 20, 30, 40, 0, 20, 0, 60); + CheckScissor(100, 0, 10, 20, 30, 40, 10, 0, 40, 0); + + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, nullptr, 0, 1, 0, 80); + surface.sc_x0 = 7; + surface.sc_x1 = 9; + sceFontRenderSurfaceSetScissor(&surface, 10, 20, 30, 40); + Require(surface.sc_x0 == 7 && surface.sc_x1 == 9); + Require(surface.sc_y0 == 20 && surface.sc_y1 == 60); + + sceFontRenderSurfaceInit(&surface, nullptr, 0, 1, 100, 0); + surface.sc_y0 = 3; + surface.sc_y1 = 5; + sceFontRenderSurfaceSetScissor(&surface, 10, 20, 30, 40); + Require(surface.sc_x0 == 10 && surface.sc_x1 == 40); + Require(surface.sc_y0 == 3 && surface.sc_y1 == 5); +} + +struct RenderedGlyph { + std::vector pixels = std::vector(128 * 128); + FontGlyphMetrics metrics{}; + FontRenderOutput output{}; +}; + +static RenderedGlyph RenderGlyph(FontHandle font, int x, int y, bool scissor, int x0 = 0, int y0 = 0, int w = 0, int h = 0) { + RenderedGlyph rendered; + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, rendered.pixels.data(), 128, 1, 128, 128); + if (scissor) sceFontRenderSurfaceSetScissor(&surface, x0, y0, w, h); + Require(sceFontRenderCharGlyphImageHorizontal(font, 'A', &surface, static_cast(x), static_cast(y), &rendered.metrics, &rendered.output) == SCE_FONT_OK); + return rendered; +} + +static void CheckRenderedClipping(FontHandle font) { + const RenderedGlyph reference = RenderGlyph(font, 10, 100, false); + const auto& rect = reference.output.UpdateRect; + Require(rect.w > 4 && rect.h > 4); + Require(rect.x + rect.w <= 128 && rect.y + rect.h <= 128); + int lit = 0; + for (std::size_t index = 0; index < reference.pixels.size(); ++index) { + const int x = static_cast(index % 128); + const int y = static_cast(index / 128); + const bool inside = x >= static_cast(rect.x) && x < static_cast(rect.x + rect.w) && y >= static_cast(rect.y) && y < static_cast(rect.y + rect.h); + if (!inside) Require(reference.pixels[index] == 0); + if (reference.pixels[index] != 0) ++lit; + } + Require(lit > 0); + + const int insetX = static_cast(rect.x) + 1; + const int insetY = static_cast(rect.y) + 1; + const int insetW = static_cast(rect.w) - 2; + const int insetH = static_cast(rect.h) - 2; + const RenderedGlyph clipped = RenderGlyph(font, 10, 100, true, insetX, insetY, insetW, insetH); + Require(clipped.output.UpdateRect.x == static_cast(insetX) && clipped.output.UpdateRect.y == static_cast(insetY)); + Require(clipped.output.UpdateRect.w == static_cast(insetW) && clipped.output.UpdateRect.h == static_cast(insetH)); + for (std::size_t index = 0; index < clipped.pixels.size(); ++index) { + const int x = static_cast(index % 128); + const int y = static_cast(index / 128); + const bool inside = x >= insetX && x < insetX + insetW && y >= insetY && y < insetY + insetH; + Require(clipped.pixels[index] == (inside ? reference.pixels[index] : 0)); + } + + const int largerX = std::max(0, static_cast(rect.x) - 5); + const int largerY = std::max(0, static_cast(rect.y) - 5); + const int largerW = std::min(128 - largerX, static_cast(rect.w) + 10); + const int largerH = std::min(128 - largerY, static_cast(rect.h) + 10); + const RenderedGlyph larger = RenderGlyph(font, 10, 100, true, largerX, largerY, largerW, largerH); + Require(std::memcmp(&larger.output.UpdateRect, &rect, sizeof(rect)) == 0); + Require(larger.pixels == reference.pixels); + + const int negativeX = static_cast(rect.x) - 10; + const int negativeY = static_cast(rect.y) - 10; + const RenderedGlyph negative = RenderGlyph(font, 10, 100, true, negativeX, negativeY, static_cast(rect.w) + 20, static_cast(rect.h) + 20); + Require(std::memcmp(&negative.output.UpdateRect, &rect, sizeof(rect)) == 0); + Require(negative.pixels == reference.pixels); + + const RenderedGlyph empty = RenderGlyph(font, 10, 100, true, static_cast(rect.x), static_cast(rect.y), 0, 0); + Require(empty.output.UpdateRect.w == 0 && empty.output.UpdateRect.h == 0); + Require(std::all_of(empty.pixels.begin(), empty.pixels.end(), [](std::uint8_t value) { return value == 0; })); + Require(empty.metrics.width > 0.0f && empty.metrics.Horizontal.advance > 0.0f); + + const RenderedGlyph outside = RenderGlyph(font, 10, 100, true, 0, 0, 1, 1); + Require(outside.output.UpdateRect.w == 0 && outside.output.UpdateRect.h == 0); + Require(std::all_of(outside.pixels.begin(), outside.pixels.end(), [](std::uint8_t value) { return value == 0; })); + Require(outside.metrics.width > 0.0f && outside.metrics.Horizontal.advance > 0.0f); +} + +int main() { + CheckScissorArithmetic(); + + const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr}; + FontMemory memory{}; + Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); + FontLibrary library = nullptr; + Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK); + Require(sceFontSupportExternalFonts(library, 1, 0x52) == SCE_FONT_OK); + const std::vector fontData = SquareGlyphFont(); + FontHandle font = nullptr; + Require(sceFontOpenFontMemory(library, fontData.data(), static_cast(fontData.size()), nullptr, &font) == SCE_FONT_OK); + Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + FontRenderer renderer = nullptr; + Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK); + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontSetupRenderScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + CheckRenderedClipping(font); + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); + Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK); + Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); + Require(allocations == 0); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); +} diff --git a/core/libs/tests/GuestFontStyleInformation.cpp b/core/libs/tests/GuestFontStyleInformation.cpp index 21443be90b..a9fbc7470a 100644 --- a/core/libs/tests/GuestFontStyleInformation.cpp +++ b/core/libs/tests/GuestFontStyleInformation.cpp @@ -150,6 +150,17 @@ int main() { Require(sceFontGetFontStyleInformation(named, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); Require(sceFontGetFontStyleInformation(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + float w = 0.0f; + float h = 0.0f; + float slant = 0.0f; + std::uint32_t hdpi = 0; + std::uint32_t vdpi = 0; + Require(sceFontGetScalePixel(nullptr, &w, &h) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetScalePoint(nullptr, &w, &h) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetEffectSlant(nullptr, &slant) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetResolutionDpi(nullptr, &hdpi, &vdpi) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(sceFontGetResolutionDpi(named, nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + for (FontHandle font : {named, os2, odd}) Require(sceFontCloseFont(font) == SCE_FONT_OK); Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); diff --git a/core/libs/tests/GuestFreeTypeAdvances.cpp b/core/libs/tests/GuestFreeTypeAdvances.cpp new file mode 100644 index 0000000000..8aeb14f403 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeAdvances.cpp @@ -0,0 +1,296 @@ +#include "FreeTypeFontFixture.hpp" +#include "prx/libSceFreeType/libSceFreeType.hpp" + +#include +#include FT_FREETYPE_H +#include FT_ADVANCES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType advance check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +constexpr std::int64_t Sentinel = 0x1122334455667788; + +struct Fixture { + std::vector bytes; + FT_Library library = nullptr; + FT_Face native = nullptr; + GuestLibrary* guestLibrary = nullptr; + GuestFaceRec* guest = nullptr; + + explicit Fixture(std::vector font) : bytes(std::move(font)) { + Require(FT_Init_FreeType(&library) == 0); + Require(FT_New_Memory_Face(library, bytes.data(), static_cast(bytes.size()), 0, &native) == 0); + Require(FT_Init_FreeType_nid_postfix(&guestLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(guestLibrary, bytes.data(), static_cast(bytes.size()), 0, &guest) == 0); + SetSize(37, 53); + } + + ~Fixture() { + Require(FT_Done_FreeType_nid_postfix(guestLibrary) == 0); + Require(FT_Done_FreeType(library) == 0); + } + + void SetSize(unsigned width, unsigned height) { + Require(FT_Set_Pixel_Sizes(native, width, height) == 0); + Require(FT_Set_Pixel_Sizes_nid_postfix(guest, width, height) == 0); + } + + void Load(unsigned glyph) { + Require(FT_Load_Glyph(native, glyph, FT_LOAD_DEFAULT) == 0); + Require(FT_Load_Glyph_nid_postfix(guest, glyph, FT_LOAD_DEFAULT) == 0); + } + + void CheckSlot() const { + const auto& actual = *guest->glyph; + const auto& expected = *native->glyph; + Require(actual.glyph_index == expected.glyph_index && actual.format == static_cast(expected.format)); + Require(actual.metrics.width == expected.metrics.width && actual.metrics.height == expected.metrics.height); + Require(actual.metrics.horiBearingX == expected.metrics.horiBearingX && actual.metrics.horiBearingY == expected.metrics.horiBearingY); + Require(actual.metrics.horiAdvance == expected.metrics.horiAdvance && actual.metrics.vertAdvance == expected.metrics.vertAdvance); + Require(actual.advance.x == expected.advance.x && actual.advance.y == expected.advance.y); + Require(actual.linearHoriAdvance == expected.linearHoriAdvance && actual.linearVertAdvance == expected.linearVertAdvance); + Require(actual.outline.n_points == expected.outline.n_points && actual.outline.n_contours == expected.outline.n_contours); + Require(actual.outline.flags == expected.outline.flags); + for (unsigned index = 0; index < actual.outline.n_points; ++index) { + Require(actual.outline.points[index].x == expected.outline.points[index].x); + Require(actual.outline.points[index].y == expected.outline.points[index].y); + } + } +}; + +static void CheckSingle(Fixture& fixture, unsigned glyph, std::int32_t flags) { + fixture.Load(1); + FT_Fixed expected = static_cast(Sentinel); + const int error = FT_Get_Advance(fixture.native, glyph, flags, &expected); + std::array actual{Sentinel, Sentinel, Sentinel}; + Require(FT_Get_Advance_nid_postfix(fixture.guest, glyph, flags, &actual[1]) == error); + Require(actual.front() == Sentinel && actual.back() == Sentinel); + if (error == 0 || sizeof(FT_Fixed) == sizeof(std::int64_t)) Require(actual[1] == expected); + else Require(actual[1] == Sentinel); + fixture.CheckSlot(); +} + +static void CheckBatch(Fixture& fixture, unsigned start, unsigned count, std::int32_t flags) { + fixture.Load(1); + std::array expected; + expected.fill(static_cast(Sentinel)); + const int error = FT_Get_Advances(fixture.native, start, count, flags, expected.data()); + std::array actual; + actual.fill(Sentinel); + Require(FT_Get_Advances_nid_postfix(fixture.guest, start, count, flags, &actual[1]) == error); + Require(actual.front() == Sentinel && actual.back() == Sentinel); + for (unsigned index = 0; index < expected.size(); ++index) { + if ((error == 0 && index < count) || sizeof(FT_Fixed) == sizeof(std::int64_t)) Require(actual[index + 1] == expected[index]); + else Require(actual[index + 1] == Sentinel); + } + fixture.CheckSlot(); +} + +static void CheckFastPreservesGuestEdits(Fixture& fixture) { + fixture.Load(1); + auto* slot = fixture.guest->glyph; + Require(slot->outline.n_points > 0); + slot->outline.points[0].x += 137; + slot->outline.flags ^= 2; + const auto point = slot->outline.points[0].x; + const auto outlineFlags = slot->outline.flags; + const auto slotBefore = *slot; + std::array actual{Sentinel, Sentinel}; + Require(FT_Get_Advances_nid_postfix(fixture.guest, 0, 2, FT_LOAD_NO_SCALE | FT_ADVANCE_FLAG_FAST_ONLY, actual.data()) == 0); + Require(std::memcmp(&slotBefore, slot, sizeof(slotBefore)) == 0); + Require(slot->outline.points[0].x == point && slot->outline.flags == outlineFlags); + Require(FT_Get_Advance_nid_postfix(fixture.guest, 0, FT_LOAD_DEFAULT | FT_ADVANCE_FLAG_FAST_ONLY, actual.data()) == FT_Err_Unimplemented_Feature); + Require(slot->outline.points[0].x == point && slot->outline.flags == outlineFlags); + Require(FT_Get_Advance_nid_postfix(fixture.guest, 0, FT_LOAD_DEFAULT, actual.data()) == 0); + FT_Fixed expected = 0; + Require(FT_Get_Advance(fixture.native, 0, FT_LOAD_DEFAULT, &expected) == 0); + fixture.CheckSlot(); +} + +static void CheckAliasedOutput(Fixture& fixture) { + fixture.Load(1); + Require(FT_Get_Advance(fixture.native, 0, FT_LOAD_DEFAULT, &fixture.native->glyph->advance.x) == 0); + Require(FT_Get_Advance_nid_postfix(fixture.guest, 0, FT_LOAD_DEFAULT, &fixture.guest->glyph->advance.x) == 0); + fixture.CheckSlot(); +} + +static std::uint32_t Read32(const std::vector& bytes, std::size_t offset) { + return (std::uint32_t{bytes[offset]} << 24) | (std::uint32_t{bytes[offset + 1]} << 16) | + (std::uint32_t{bytes[offset + 2]} << 8) | bytes[offset + 3]; +} + +static std::vector BrokenGlyphFont() { + auto bytes = SquareGlyphFont('A', 913); + for (std::size_t offset = 12; offset + 16 <= bytes.size(); offset += 16) { + if (std::memcmp(bytes.data() + offset, "glyf", 4) != 0) continue; + const auto glyph = Read32(bytes, offset + 8); + bytes[glyph] = 0x7F; + bytes[glyph + 1] = 0xFF; + return bytes; + } + std::abort(); +} + +static void CffIndex(std::vector& font, const std::vector>& entries) { + Put16(font, static_cast(entries.size())); + if (entries.empty()) return; + font.push_back(1); + std::size_t offset = 1; + font.push_back(static_cast(offset)); + for (const auto& entry : entries) { + offset += entry.size(); + Require(offset <= 255); + font.push_back(static_cast(offset)); + } + for (const auto& entry : entries) font.insert(font.end(), entry.begin(), entry.end()); +} + +static std::vector CffFont() { + std::vector font{1, 0, 4, 1}; + CffIndex(font, {{'A', 'd', 'v', 'a', 'n', 'c', 'e'}}); + const auto topDict = font.size(); + CffIndex(font, {{0, 17}}); + CffIndex(font, {}); + CffIndex(font, {}); + Require(font.size() < 107); + font[topDict + 5] = static_cast(font.size() + 139); + CffIndex(font, {{249, 80, 14}, {254, 37, 239, 139, 21, 139, 249, 80, 248, 136, 139, 139, 253, 80, 5, 14}}); + return font; +} + +static std::vector OtfCffFont() { + const auto original = SquareGlyphFont('A', 913); + using Table = std::pair, std::vector>; + std::vector tables{{{'C', 'F', 'F', ' '}, CffFont()}}; + for (std::size_t record = 12; record < 12 + 7 * 16; record += 16) { + if (std::memcmp(original.data() + record, "glyf", 4) == 0 || std::memcmp(original.data() + record, "loca", 4) == 0) continue; + std::array tag; + std::memcpy(tag.data(), original.data() + record, tag.size()); + const auto start = Read32(original, record + 8); + const auto length = Read32(original, record + 12); + tables.push_back({tag, {original.begin() + start, original.begin() + start + length}}); + } + std::vector font; + Put32(font, 0x4F54544F); + PutAll16(font, {6, 64, 2, 32}); + std::size_t offset = 12 + tables.size() * 16; + for (const auto& [tag, data] : tables) { + font.insert(font.end(), tag.begin(), tag.end()); + Put32(font, 0); + Put32(font, static_cast(offset)); + Put32(font, static_cast(data.size())); + offset += (data.size() + 3) & ~std::size_t{3}; + } + for (const auto& [tag, data] : tables) { + font.insert(font.end(), data.begin(), data.end()); + font.resize((font.size() + 3) & ~std::size_t{3}); + } + return font; +} + +static void CheckCff() { + Fixture fixture(CffFont()); + for (const std::int32_t flags : std::array{FT_LOAD_NO_SCALE, FT_LOAD_NO_HINTING, + FT_ADVANCE_FLAG_FAST_ONLY | FT_LOAD_NO_SCALE, FT_LOAD_DEFAULT}) { + CheckSingle(fixture, 1, flags); + CheckBatch(fixture, 0, 2, flags); + } + fixture.Load(1); + Require(fixture.guest->glyph->outline.n_points > 0); + fixture.guest->glyph->outline.points[0].x += 137; + FT_Fixed expected = 0; + std::int64_t actual = Sentinel; + Require(FT_Get_Advance(fixture.native, 1, FT_LOAD_NO_SCALE, &expected) == 0 && expected < 0); + Require(FT_Get_Advance_nid_postfix(fixture.guest, 1, FT_LOAD_NO_SCALE, &actual) == 0 && actual == expected); + fixture.CheckSlot(); + + Fixture otf(OtfCffFont()); + for (const std::int32_t flags : std::array{FT_LOAD_NO_SCALE, FT_LOAD_NO_SCALE | FT_LOAD_VERTICAL_LAYOUT, FT_LOAD_DEFAULT}) { + CheckSingle(otf, 1, flags); + CheckBatch(otf, 0, 2, flags); + } + otf.Load(1); + Require(otf.guest->glyph->outline.n_points > 0); + otf.guest->glyph->outline.points[0].x += 137; + const auto point = otf.guest->glyph->outline.points[0].x; + Require(FT_Get_Advance_nid_postfix(otf.guest, 1, FT_LOAD_NO_SCALE, &actual) == 0); + Require(otf.guest->glyph->outline.points[0].x == point); + const auto flags = FT_LOAD_NO_SCALE | FT_LOAD_VERTICAL_LAYOUT; + Require(FT_Get_Advance(otf.native, 1, flags, &expected) == 0); + Require(FT_Get_Advance_nid_postfix(otf.guest, 1, flags, &actual) == 0 && actual == expected); + otf.CheckSlot(); +} + +static void CheckPartialFailure() { + Fixture fixture(BrokenGlyphFont()); + std::array expected{static_cast(Sentinel), static_cast(Sentinel)}; + const int error = FT_Get_Advances(fixture.native, 0, 2, FT_LOAD_DEFAULT, expected.data()); + Require(error != 0 && expected[0] != static_cast(Sentinel) && expected[1] == static_cast(Sentinel)); + std::array actual{Sentinel, Sentinel, Sentinel, Sentinel}; + if constexpr (sizeof(FT_Fixed) == sizeof(std::int64_t)) { + Require(FT_Get_Advances_nid_postfix(fixture.guest, 0, 2, FT_LOAD_DEFAULT, &actual[1]) == error); + Require(actual[1] == expected[0] && actual[2] == Sentinel); + } else { + bool threw = false; + try { + FT_Get_Advances_nid_postfix(fixture.guest, 0, 2, FT_LOAD_DEFAULT, &actual[1]); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw && actual[1] == Sentinel && actual[2] == Sentinel); + } + Require(actual.front() == Sentinel && actual.back() == Sentinel); + fixture.CheckSlot(); +} + +int main() { + Fixture fixture(SquareGlyphFont('A', 913)); + for (const unsigned size : {7u, 37u, 113u}) { + fixture.SetSize(size, size + 11); + for (const std::int32_t flags : std::array{FT_LOAD_DEFAULT, FT_LOAD_NO_SCALE, FT_LOAD_NO_HINTING, FT_LOAD_VERTICAL_LAYOUT, + FT_LOAD_NO_SCALE | FT_LOAD_VERTICAL_LAYOUT, FT_LOAD_TARGET_LIGHT, FT_ADVANCE_FLAG_FAST_ONLY, + FT_ADVANCE_FLAG_FAST_ONLY | FT_LOAD_NO_SCALE, FT_ADVANCE_FLAG_FAST_ONLY | FT_LOAD_NO_HINTING}) { + CheckSingle(fixture, 0, flags); + CheckSingle(fixture, 1, flags); + CheckBatch(fixture, 0, 2, flags); + CheckBatch(fixture, 1, 1, flags); + } + } + for (const unsigned glyph : {2u, std::numeric_limits::max()}) CheckSingle(fixture, glyph, FT_LOAD_DEFAULT); + CheckBatch(fixture, 0, 0, FT_LOAD_DEFAULT); + CheckBatch(fixture, 1, 0, FT_LOAD_DEFAULT); + CheckBatch(fixture, 2, 0, FT_LOAD_DEFAULT); + CheckBatch(fixture, 1, 2, FT_LOAD_DEFAULT); + CheckBatch(fixture, 1, std::numeric_limits::max(), FT_LOAD_DEFAULT); + CheckBatch(fixture, std::numeric_limits::max(), 2, FT_LOAD_DEFAULT); + + std::int64_t output = Sentinel; + Require(FT_Get_Advance_nid_postfix(nullptr, 0, 0, &output) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Advance_nid_postfix(nullptr, 0, 0, nullptr) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Advance_nid_postfix(fixture.guest, 2, 0, nullptr) == FT_Err_Invalid_Argument); + Require(FT_Get_Advances_nid_postfix(nullptr, 0, 0, 0, &output) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Advances_nid_postfix(nullptr, 0, 0, 0, nullptr) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Advances_nid_postfix(fixture.guest, 2, 0, 0, nullptr) == FT_Err_Invalid_Argument); + Require(output == Sentinel); + CheckFastPreservesGuestEdits(fixture); + CheckAliasedOutput(fixture); + CheckPartialFailure(); + CheckCff(); +} diff --git a/core/libs/tests/GuestFreeTypeStroker.cpp b/core/libs/tests/GuestFreeTypeStroker.cpp new file mode 100644 index 0000000000..55e5567685 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeStroker.cpp @@ -0,0 +1,238 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_STROKER_H + +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType stroker check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct Pair { + void* guest = nullptr; + FT_Stroker native = nullptr; + + Pair(GuestLibrary& library, FT_Library nativeLibrary) { + Require(FT_Stroker_New_nid_postfix(&library, &guest) == 0); + Require(FT_Stroker_New(nativeLibrary, &native) == 0); + FT_Stroker_Set(static_cast(guest), 64, FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Stroker_Set(native, 64, FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); + } + + ~Pair() { + FT_Stroker_Done_nid_postfix(guest); + FT_Stroker_Done(native); + } + + void CountsMatch() const { + std::uint32_t points = 11; + std::uint32_t contours = 13; + FT_UInt nativePoints = 11; + FT_UInt nativeContours = 13; + Require(FT_Stroker_GetCounts_nid_postfix(guest, &points, &contours) == FT_Stroker_GetCounts(native, &nativePoints, &nativeContours)); + Require(points == nativePoints && contours == nativeContours); + } + + void Square(std::uint8_t open, bool finish = true) const { + const GuestVector corners[] = {{0, 0}, {640, 0}, {640, 640}, {0, 640}}; + FT_Vector nativeStart{0, 0}; + Require(FT_Stroker_BeginSubPath_nid_postfix(guest, &corners[0], open) == FT_Stroker_BeginSubPath(native, &nativeStart, open)); + for (int index = 1; index < 4; ++index) { + FT_Vector nativePoint{static_cast(corners[index].x), static_cast(corners[index].y)}; + Require(FT_Stroker_LineTo_nid_postfix(guest, &corners[index]) == FT_Stroker_LineTo(native, &nativePoint)); + } + if (!finish) return; + Require(FT_Stroker_EndSubPath_nid_postfix(guest) == FT_Stroker_EndSubPath(native)); + } +}; + +static void TestNew(GuestLibrary& library, FT_Library native) { + void* guest = reinterpret_cast(1); + FT_Stroker expected = nullptr; + Require(FT_Stroker_New_nid_postfix(nullptr, &guest) == FT_Stroker_New(nullptr, &expected)); + Require(guest == nullptr && expected == nullptr); + Require(FT_Stroker_New_nid_postfix(&library, nullptr) == FT_Stroker_New(native, nullptr)); + Require(FT_Stroker_New_nid_postfix(&library, &guest) == FT_Stroker_New(native, &expected)); + Require(guest != nullptr && expected != nullptr); + FT_Stroker_Done(static_cast(guest)); + FT_Stroker_Done(expected); +} + +static void TestDone(GuestLibrary& library, FT_Library native) { + for (int round = 0; round < 3; ++round) { + void* guest = nullptr; + FT_Stroker expected = nullptr; + Require(FT_Stroker_New_nid_postfix(&library, &guest) == 0 && FT_Stroker_New(native, &expected) == 0); + FT_Stroker_Done_nid_postfix(guest); + FT_Stroker_Done(expected); + } + FT_Stroker_Done_nid_postfix(nullptr); + FT_Stroker_Done(nullptr); +} + +static void TestGetCounts(GuestLibrary& library, FT_Library native) { + Pair pair(library, native); + pair.CountsMatch(); + std::uint32_t points = 7; + std::uint32_t contours = 9; + FT_UInt nativePoints = 7; + FT_UInt nativeContours = 9; + Require(FT_Stroker_GetCounts_nid_postfix(nullptr, &points, &contours) == FT_Stroker_GetCounts(nullptr, &nativePoints, &nativeContours)); + Require(points == nativePoints && contours == nativeContours); + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, nullptr, nullptr) == FT_Stroker_GetCounts(pair.native, nullptr, nullptr)); +} + +static void TestBeginSubPath(GuestLibrary& library, FT_Library native) { + for (const std::uint8_t open : {0, 1}) { + Pair pair(library, native); + const GuestVector start{64, 128}; + FT_Vector nativeStart{64, 128}; + Require(FT_Stroker_BeginSubPath_nid_postfix(pair.guest, &start, open) == FT_Stroker_BeginSubPath(pair.native, &nativeStart, open)); + pair.CountsMatch(); + } + Pair pair(library, native); + const GuestVector start{0, 0}; + FT_Vector nativeStart{0, 0}; + Require(FT_Stroker_BeginSubPath_nid_postfix(nullptr, &start, 0) == FT_Stroker_BeginSubPath(nullptr, &nativeStart, 0)); + Require(FT_Stroker_BeginSubPath_nid_postfix(pair.guest, nullptr, 0) == FT_Stroker_BeginSubPath(pair.native, nullptr, 0)); +} + +static void TestLineTo(GuestLibrary& library, FT_Library native) { + Pair pair(library, native); + const GuestVector start{0, 0}; + FT_Vector nativeStart{0, 0}; + Require(FT_Stroker_BeginSubPath_nid_postfix(pair.guest, &start, 1) == FT_Stroker_BeginSubPath(pair.native, &nativeStart, 1)); + for (const GuestVector point : {GuestVector{640, 0}, GuestVector{640, 640}, GuestVector{0, 640}}) { + FT_Vector nativePoint{static_cast(point.x), static_cast(point.y)}; + Require(FT_Stroker_LineTo_nid_postfix(pair.guest, &point) == FT_Stroker_LineTo(pair.native, &nativePoint)); + pair.CountsMatch(); + } + const GuestVector point{1, 1}; + FT_Vector nativePoint{1, 1}; + Require(FT_Stroker_LineTo_nid_postfix(nullptr, &point) == FT_Stroker_LineTo(nullptr, &nativePoint)); + Require(FT_Stroker_LineTo_nid_postfix(pair.guest, nullptr) == FT_Stroker_LineTo(pair.native, nullptr)); +} + +static void TestEndSubPath(GuestLibrary& library, FT_Library native) { + for (const std::uint8_t open : {0, 1}) { + Pair pair(library, native); + pair.Square(open, false); + Require(FT_Stroker_EndSubPath_nid_postfix(pair.guest) == FT_Stroker_EndSubPath(pair.native)); + pair.CountsMatch(); + } + Require(FT_Stroker_EndSubPath_nid_postfix(nullptr) == FT_Stroker_EndSubPath(nullptr)); +} + +static void TestSet(GuestLibrary& library, FT_Library native) { + for (const std::int64_t radius : {0, 16, 64, 200}) { + for (const std::int32_t cap : {0, 1, 2}) { + for (const std::int32_t join : {0, 1, 2, 3}) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, radius, cap, join, 0x20000); + FT_Stroker_Set(pair.native, static_cast(radius), static_cast(cap), static_cast(join), 0x20000); + pair.Square(0); + pair.Square(1); + pair.CountsMatch(); + } + } + } +} + +static void TestRewind(GuestLibrary& library, FT_Library native) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Stroker_Set(pair.native, 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + pair.Square(0); + pair.CountsMatch(); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + FT_Stroker_Rewind_nid_postfix(pair.guest); + FT_Stroker_Rewind(pair.native); + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points == 0 && contours == 0); + pair.CountsMatch(); + FT_Stroker_Rewind_nid_postfix(nullptr); +} + +static void TestGetBorderCounts(GuestLibrary& library, FT_Library native) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, 64, FT_STROKER_LINECAP_SQUARE, FT_STROKER_LINEJOIN_BEVEL, 0); + FT_Stroker_Set(pair.native, 64, FT_STROKER_LINECAP_SQUARE, FT_STROKER_LINEJOIN_BEVEL, 0); + pair.Square(0); + for (const std::int32_t border : {0, 1, 2, -1}) { + std::uint32_t points = 5; + std::uint32_t contours = 6; + FT_UInt nativePoints = 5; + FT_UInt nativeContours = 6; + Require(FT_Stroker_GetBorderCounts_nid_postfix(pair.guest, border, &points, &contours) == + FT_Stroker_GetBorderCounts(pair.native, static_cast(border), &nativePoints, &nativeContours)); + Require(points == nativePoints && contours == nativeContours); + } + Require(FT_Stroker_GetBorderCounts_nid_postfix(nullptr, 0, nullptr, nullptr) == FT_Stroker_GetBorderCounts(nullptr, FT_STROKER_BORDER_LEFT, nullptr, nullptr)); +} + +static void TestConicTo(GuestLibrary& library, FT_Library native) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, 48, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Stroker_Set(pair.native, 48, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + const GuestVector start{0, 0}; + FT_Vector nativeStart{0, 0}; + Require(FT_Stroker_BeginSubPath_nid_postfix(pair.guest, &start, 1) == FT_Stroker_BeginSubPath(pair.native, &nativeStart, 1)); + const GuestVector control{320, 640}; + const GuestVector to{640, 0}; + FT_Vector nativeControl{320, 640}; + FT_Vector nativeTo{640, 0}; + Require(FT_Stroker_ConicTo_nid_postfix(pair.guest, &control, &to) == FT_Stroker_ConicTo(pair.native, &nativeControl, &nativeTo)); + Require(FT_Stroker_EndSubPath_nid_postfix(pair.guest) == FT_Stroker_EndSubPath(pair.native)); + pair.CountsMatch(); + Require(FT_Stroker_ConicTo_nid_postfix(pair.guest, nullptr, &to) == FT_Stroker_ConicTo(pair.native, nullptr, &nativeTo)); + Require(FT_Stroker_ConicTo_nid_postfix(nullptr, &control, &to) == FT_Stroker_ConicTo(nullptr, &nativeControl, &nativeTo)); +} + +static void TestCubicTo(GuestLibrary& library, FT_Library native) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, 48, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Stroker_Set(pair.native, 48, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + const GuestVector start{0, 0}; + FT_Vector nativeStart{0, 0}; + Require(FT_Stroker_BeginSubPath_nid_postfix(pair.guest, &start, 1) == FT_Stroker_BeginSubPath(pair.native, &nativeStart, 1)); + const GuestVector first{0, 640}; + const GuestVector second{640, 640}; + const GuestVector to{640, 0}; + FT_Vector nativeFirst{0, 640}; + FT_Vector nativeSecond{640, 640}; + FT_Vector nativeTo{640, 0}; + Require(FT_Stroker_CubicTo_nid_postfix(pair.guest, &first, &second, &to) == FT_Stroker_CubicTo(pair.native, &nativeFirst, &nativeSecond, &nativeTo)); + Require(FT_Stroker_EndSubPath_nid_postfix(pair.guest) == FT_Stroker_EndSubPath(pair.native)); + pair.CountsMatch(); + Require(FT_Stroker_CubicTo_nid_postfix(pair.guest, &first, nullptr, &to) == FT_Stroker_CubicTo(pair.native, &nativeFirst, nullptr, &nativeTo)); + Require(FT_Stroker_CubicTo_nid_postfix(nullptr, &first, &second, &to) == FT_Stroker_CubicTo(nullptr, &nativeFirst, &nativeSecond, &nativeTo)); +} + +int main() { + GuestLibrary library{}; + FT_Library native = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&native) == 0); + TestNew(library, native); + TestDone(library, native); + TestGetCounts(library, native); + TestBeginSubPath(library, native); + TestLineTo(library, native); + TestEndSubPath(library, native); + TestSet(library, native); + TestRewind(library, native); + TestGetBorderCounts(library, native); + TestConicTo(library, native); + TestCubicTo(library, native); + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(native) == 0); +} diff --git a/core/libs/tests/GuestG2PDialog.cpp b/core/libs/tests/GuestG2PDialog.cpp index b0c5cc8adc..5b0181ab44 100644 --- a/core/libs/tests/GuestG2PDialog.cpp +++ b/core/libs/tests/GuestG2PDialog.cpp @@ -70,6 +70,10 @@ int main() { Require(sceG2PDialogGetStatus() == kStatusFinished); Require(sceG2PDialogUpdateStatus() == kStatusFinished); + Require(sceG2PDialogOpen(nullptr) == kErrArgNull); + Require(sceG2PDialogOpen(¶m) == 0); + Require(sceG2PDialogGetStatus() == kStatusFinished); + Require(sceG2PDialogGetResult(nullptr) == kErrArgNull); Require(sceG2PDialogGetResult(reinterpret_cast(result)) == 0); Require(result[0] == -1 && result[1] == -1 && result[3] == -1); diff --git a/core/libs/tests/GuestGameUpdate.cpp b/core/libs/tests/GuestGameUpdate.cpp index 34c5c5308b..eaeda50654 100644 --- a/core/libs/tests/GuestGameUpdate.cpp +++ b/core/libs/tests/GuestGameUpdate.cpp @@ -113,6 +113,7 @@ int main() { Require(sceGameUpdateGetAddcontLatestVersion(0, &label, &info) == notInitialized); Require(sceGameUpdateGetAddcontLatestVersion(0, &label, nullptr) == invalidArgument); Require(sceGameUpdateGetAddcontLatestVersion(0, nullptr, &info) == invalidArgument); + Require(sceGameUpdateGetAddcontLatestVersion(0, nullptr, nullptr) == invalidArgument); Require(sceGameUpdateInitialize() == 0); Require(sceGameUpdateInitialize() == alreadyInitialized); Require(sceGameUpdateGetAddcontLatestVersion(0, &label, nullptr) == invalidArgument); @@ -133,4 +134,15 @@ int main() { Require(sceGameUpdateTerminate() == notInitialized); info.size = sizeof(info); Require(sceGameUpdateGetAddcontLatestVersion(0, &label, &info) == notInitialized); + Require(sceGameUpdateGetAddcontLatestVersion(0, &label, nullptr) == invalidArgument); + Require(sceGameUpdateGetAddcontLatestVersion(0, nullptr, &info) == invalidArgument); + Require(sceGameUpdateGetAddcontLatestVersion(0, nullptr, nullptr) == invalidArgument); + Require(sceGameUpdateCreateRequest() == notInitialized); + GameUpdateCheckParam postParam{}; + postParam.size = sizeof(postParam); + GameUpdateCheckResult postResult{}; + postResult.size = sizeof(postResult); + Require(sceGameUpdateCheck(0x20000000, &postParam, &postResult) == notInitialized); + Require(sceGameUpdateAbortRequest(0x20000000) == notInitialized); + Require(sceGameUpdateDeleteRequest(0x20000000) == notInitialized); } diff --git a/core/libs/tests/GuestGnmNeoRetail.cpp b/core/libs/tests/GuestGnmNeoRetail.cpp index 3101ebb9b0..e52722a37f 100644 --- a/core/libs/tests/GuestGnmNeoRetail.cpp +++ b/core/libs/tests/GuestGnmNeoRetail.cpp @@ -34,7 +34,6 @@ int main() { sceGnmGetResourceUserData, sceGnmQueryResourceRegistrationUserMemoryRequirements, sceGnmRegisterGdsResource, - sceGnmRegisterOwner, sceGnmRegisterOwnerForSystem, sceGnmRegisterResource, sceGnmSetResourceRegistrationUserMemory, @@ -54,6 +53,7 @@ int main() { sceGnmSetSpiEnableSqCounters, sceGnmSetSpiEnableSqCountersForUnitInstance, }); + if (sceGnmRegisterOwner(nullptr, nullptr) != Failure) std::abort(); RequireAll(NoInterface, { sceGnmDriverInternalRetrieveGnmInterface, sceGnmDriverInternalRetrieveGnmInterfaceForGpuDebugger, diff --git a/core/libs/tests/GuestHmdSetupDialog.cpp b/core/libs/tests/GuestHmdSetupDialog.cpp index 347f8c4ad1..af83eb7cc9 100644 --- a/core/libs/tests/GuestHmdSetupDialog.cpp +++ b/core/libs/tests/GuestHmdSetupDialog.cpp @@ -64,4 +64,12 @@ int main() { Require(sceHmdSetupDialogGetResult(result) == 0); Require(result[0] == kResultUserCanceled); Require(sceHmdSetupDialogTerminate() == 0); + Require(sceHmdSetupDialogTerminate() == kErrNotInitialized); + Require(sceHmdSetupDialogOpen(nullptr) == kErrNotInitialized); + Require(sceHmdSetupDialogOpen(¶m) == kErrNotInitialized); + Require(sceHmdSetupDialogClose() == kErrNotInitialized); + Require(sceHmdSetupDialogGetResult(nullptr) == kErrNotInitialized); + Require(sceHmdSetupDialogGetResult(result) == kErrNotInitialized); + Require(sceHmdSetupDialogGetStatus() == kStatusNone); + Require(sceHmdSetupDialogUpdateStatus() == kStatusNone); } diff --git a/core/libs/tests/GuestHttp2Transport.cpp b/core/libs/tests/GuestHttp2Transport.cpp index b785722d08..03594e78b6 100644 --- a/core/libs/tests/GuestHttp2Transport.cpp +++ b/core/libs/tests/GuestHttp2Transport.cpp @@ -8,6 +8,7 @@ #endif #include "SceTypes.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceHttp2/include/Http2.hpp" #include "prx/libSceNet/include/Net.hpp" #include "prx/libSceSsl/include/Ssl.hpp" @@ -28,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -526,7 +529,15 @@ int main(int argc, char** argv) { SceSsl::Data root{reinterpret_cast(const_cast(RootPem)), sizeof(RootPem)}; SceSsl::Data* authorities[] = {&root}; Require(sceSslLoadCert(ssl, 1, authorities, nullptr, nullptr) == 0, "trust loopback CA"); - if (argc == 2 && std::string_view(argv[1]) == "--early") { + if (argc == 2 && std::string_view(argv[1]) == "--system-roots") { + const auto list = std::filesystem::current_path() / sceKernelGetFsSandboxRandomWord() / "common" / "cert" / "CA_LIST.cer"; + std::filesystem::create_directories(list.parent_path()); + std::ofstream(list, std::ios::binary | std::ios::trunc) << RootPem; + const int unloaded = sceSslInit_nid_postfix(8 * 1024 * 1024); + Require(unloaded > 0, "SSL context without certificates"); + Success(unloaded, Mode::Normal); + Require(sceSslTerm_nid_postfix(unloaded) == 0, "SSL context without certificates term"); + } else if (argc == 2 && std::string_view(argv[1]) == "--early") { for (const auto mode : {Mode::EarlyFinal, Mode::EarlyReset, Mode::EarlyEmpty, Mode::EarlyLarge}) EarlyResponse(ssl, mode); Failure(ssl, Mode::EarlyResetIncomplete); #ifdef _WIN32 diff --git a/core/libs/tests/GuestIcuCString.cpp b/core/libs/tests/GuestIcuCString.cpp new file mode 100644 index 0000000000..a2e38d77dc --- /dev/null +++ b/core/libs/tests/GuestIcuCString.cpp @@ -0,0 +1,177 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "ICU C string: %s\n", message); + std::abort(); +} + +template +bool Rejects(TCall call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +template +void CheckFormat(TValue value, int radix) { + std::array expected{}; + const auto converted = radix == 10 + ? std::to_chars(expected.data(), expected.data() + expected.size(), value, radix) + : std::to_chars(expected.data(), expected.data() + expected.size(), static_cast>(value), radix); + Require(converted.ec == std::errc{}, "standard formatting oracle succeeds"); + for (char* cursor = expected.data(); cursor != converted.ptr; ++cursor) + if (*cursor >= 'a' && *cursor <= 'f') *cursor = static_cast(*cursor - ('a' - 'A')); + const std::string_view wanted(expected.data(), converted.ptr - expected.data()); + std::array actual; + actual.fill('@'); + std::int32_t length; + if constexpr (sizeof(TValue) == 4) length = T_CString_integerToString_nid_postfix(actual.data() + 1, value, radix); + else length = T_CString_int64ToString_nid_postfix(actual.data() + 1, value, static_cast(radix)); + Require(length == static_cast(wanted.size()), "format length excludes the terminator"); + Require(std::string_view(actual.data() + 1, wanted.size()) == wanted, "format matches independent standard oracle"); + Require(actual[1 + wanted.size()] == 0, "format terminates the string"); + Require(actual.front() == '@', "format preserves the prefix canary"); + Require(std::all_of(actual.begin() + wanted.size() + 2, actual.end(), [](char value) { return value == '@'; }), "format preserves the suffix canary"); +} + +void Formatting() { + constexpr std::array values32{0, 1, -1, 15, -15, 431, -431, INT32_MIN, INT32_MAX}; + constexpr std::array values64{0, 1, -1, 15, -15, 431, -431, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX}; + for (int radix = 2; radix <= 16; ++radix) { + for (auto value : values32) CheckFormat(value, radix); + for (auto value : values64) CheckFormat(value, radix); + } + std::uint64_t random = 0x18cb3a2ef4796d05; + for (int sample = 0; sample < 1024; ++sample) { + random ^= random << 13; + random ^= random >> 7; + random ^= random << 17; + for (int radix = 2; radix <= 16; ++radix) { + CheckFormat(std::bit_cast(static_cast(random)), radix); + CheckFormat(std::bit_cast(random), radix); + } + } +} + +void InvalidFormatting() { + std::array buffer; + buffer.fill('@'); + for (int radix : {-1, 0, 1, 17, 36, INT32_MAX}) { + Require(Rejects([&] { T_CString_integerToString_nid_postfix(buffer.data(), 42, radix); }), "invalid 32-bit format radix throws"); + Require(Rejects([&] { T_CString_int64ToString_nid_postfix(buffer.data(), 42, static_cast(radix)); }), "invalid 64-bit format radix throws"); + Require(std::all_of(buffer.begin(), buffer.end(), [](char value) { return value == '@'; }), "invalid radix leaves the buffer unchanged"); + } + Require(Rejects([] { T_CString_integerToString_nid_postfix(nullptr, 42, 10); }), "null 32-bit format buffer throws"); + Require(Rejects([] { T_CString_int64ToString_nid_postfix(nullptr, 42, 10); }), "null 64-bit format buffer throws"); +} + +void Parsing() { + struct Case { + const char* text; + int radix; + std::int32_t value; + int error; + }; + constexpr int SavedError = 1234; + constexpr Case cases[] = { + {"", 10, 0, SavedError}, + {"words", 10, 0, SavedError}, + {" +", 10, 0, SavedError}, + {"0", 10, 0, SavedError}, + {" \t\n\r\f\v-42tail", 10, -42, SavedError}, + {"+42", 10, 42, SavedError}, + {"2147483647", 10, INT32_MAX, SavedError}, + {"2147483648", 10, INT32_MIN, SavedError}, + {"-2147483648", 10, INT32_MIN, SavedError}, + {"4294967295", 10, -1, SavedError}, + {"4294967296", 10, 0, SavedError}, + {"4294967297", 10, 1, SavedError}, + {"-4294967297", 10, -1, SavedError}, + {"9223372036854775808", 10, 0, SavedError}, + {"18446744073709551615", 10, -1, SavedError}, + {"-18446744073709551615", 10, 1, SavedError}, + {"18446744073709551616", 10, -1, 34}, + {"-18446744073709551616", 10, -1, 34}, + {"999999999999999999999999999999", 10, -1, 34}, + {"101101tail", 2, 45, SavedError}, + {"0b11", 0, 0, SavedError}, + {"0b11", 2, 0, SavedError}, + {"077", 0, 63, SavedError}, + {"077", 10, 77, SavedError}, + {"0x2A", 0, 42, SavedError}, + {"0X2a", 16, 42, SavedError}, + {"0x", 16, 0, SavedError}, + {"FFFFFFFF", 16, -1, SavedError}, + {"100000001", 16, 1, SavedError}, + {"zZ", 36, 1295, SavedError}, + {"42", 1, 0, 22}, + {"42", 37, 0, 22}, + {"42", -1, 0, 22}, + }; + for (const auto& test : cases) { + *__error_nid_postfix() = SavedError; + const auto actual = T_CString_stringToInteger_nid_postfix(test.text, test.radix); + const int error = *__error_nid_postfix(); + Require(actual == test.value, "parser preserves LP64 strtoul results before narrowing"); + Require(error == test.error, "parser preserves strtoul error behavior"); + } + for (int radix = 2; radix <= 36; ++radix) { + for (std::uint64_t value : std::array{0, 1, 35, 431, UINT32_MAX, std::uint64_t{UINT32_MAX} + 1, INT64_MAX, UINT64_MAX}) { + std::array text{}; + const auto converted = std::to_chars(text.data(), text.data() + text.size() - 1, value, radix); + Require(converted.ec == std::errc{}, "standard parse input generation succeeds"); + const auto expected = std::bit_cast(static_cast(value)); + Require(T_CString_stringToInteger_nid_postfix(text.data(), radix) == expected, "parser accepts every supported radix"); + } + } + Require(Rejects([] { T_CString_stringToInteger_nid_postfix(nullptr, 10); }), "null parse source throws"); +} + +void CaseConversion() { + Require(T_CString_toLowerCase_nid_postfix(nullptr) == nullptr, "null lowercase input returns null"); + Require(T_CString_toUpperCase_nid_postfix(nullptr) == nullptr, "null uppercase input returns null"); + for (int value = 0; value <= 255; ++value) { + const char input = static_cast(value); + std::array lower{input, 0, 'A', 'Z'}; + std::array upper{input, 0, 'a', 'z'}; + Require(T_CString_toLowerCase_nid_postfix(lower.data()) == lower.data(), "lowercase returns the original pointer"); + Require(T_CString_toUpperCase_nid_postfix(upper.data()) == upper.data(), "uppercase returns the original pointer"); + Require(lower[0] == (value >= 65 && value <= 90 ? static_cast(value + 32) : input), "only ASCII uppercase letters become lowercase"); + Require(upper[0] == (value >= 97 && value <= 122 ? static_cast(value - 32) : input), "only ASCII lowercase letters become uppercase"); + Require(lower[1] == 0 && lower[2] == 'A' && lower[3] == 'Z', "lowercase stops at the terminator"); + Require(upper[1] == 0 && upper[2] == 'a' && upper[3] == 'z', "uppercase stops at the terminator"); + } + std::string mixed = "aBcD-zZ [\\]^_` @09\tI-i"; + T_CString_toLowerCase_nid_postfix(mixed.data()); + Require(mixed == "abcd-zz [\\]^_` @09\ti-i", "lowercase traverses complete strings"); + T_CString_toUpperCase_nid_postfix(mixed.data()); + Require(mixed == "ABCD-ZZ [\\]^_` @09\tI-I", "uppercase traverses complete strings"); +} + +} + +int main() { + Formatting(); + InvalidFormatting(); + Parsing(); + CaseConversion(); +} diff --git a/core/libs/tests/GuestKernelErrors.cpp b/core/libs/tests/GuestKernelErrors.cpp index 0cc54fbfe9..598af1167c 100644 --- a/core/libs/tests/GuestKernelErrors.cpp +++ b/core/libs/tests/GuestKernelErrors.cpp @@ -3,7 +3,6 @@ #include "prx/libkernel/Semaphore/include/Semaphore.hpp" #include "prx/libkernel/Pthread/include/Mutex.hpp" #include "prx/libkernel/Pthread/include/Rwlock.hpp" -#include "prx/libSceSaveData/SaveDataCore.hpp" #include "SceTypes.hpp" #include #include diff --git a/core/libs/tests/GuestKernelStackProtect.cpp b/core/libs/tests/GuestKernelStackProtect.cpp index 2f06f2fab3..2354076a1d 100644 --- a/core/libs/tests/GuestKernelStackProtect.cpp +++ b/core/libs/tests/GuestKernelStackProtect.cpp @@ -13,6 +13,10 @@ #define NOMINMAX #endif #include +#elif defined(__APPLE__) +#include +#include +#include #else #include #include @@ -39,6 +43,16 @@ static PageState QueryPage(std::uintptr_t address) { Require(VirtualQuery(reinterpret_cast(address), &memory, sizeof(memory)) == sizeof(memory)); const bool mapped = memory.State == MEM_COMMIT; return {mapped, mapped && (memory.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) != 0, (static_cast(memory.State) << 32) | memory.Protect}; +#elif defined(__APPLE__) + mach_vm_address_t region = address; + mach_vm_size_t size = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + if (mach_vm_region(mach_task_self(), ®ion, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast(&info), &count, &object) != KERN_SUCCESS || region > address) return {false, false, 0}; + const bool readable = (info.protection & VM_PROT_READ) != 0; + const bool writable = (info.protection & VM_PROT_WRITE) != 0; + return {true, readable && writable, 1ull | (readable ? 2ull : 0) | (writable ? 4ull : 0) | ((info.protection & VM_PROT_EXECUTE) != 0 ? 8ull : 0)}; #else std::FILE* maps = std::fopen("/proc/self/maps", "r"); Require(maps != nullptr); @@ -448,7 +462,7 @@ static void ProtectHostStacks() { for (void* partial : {static_cast(nullptr), reinterpret_cast(1)}) RunOnReservedStack(ProtectReservedStack, partial); RunOnReservedStack(ProtectStackBottom, nullptr); } -#else +#elif defined(__linux__) static int ProtectUnlimitedStack() { int local = 0; void* start = nullptr; @@ -526,12 +540,16 @@ int main(int argc, char** argv) { (void)argc; (void)argv; ProtectHostStacks(); -#else +#elif defined(__linux__) if (argc > 1 && std::strcmp(argv[1], "--unlimited-stack") == 0) return ProtectUnlimitedStack(); if (argc > 1 && std::strcmp(argv[1], "--read-only-bottom") == 0) return ProtectMainStackBottom({1}, 3); if (argc > 1 && std::strcmp(argv[1], "--reprotect-bottom") == 0) return ProtectMainStackBottom({0, 3}, 7); ProtectMainStacks(); ProtectStack(0); +#else + (void)argc; + (void)argv; + ProtectStack(0); #endif std::atomic passed{false}; Pthread thread = nullptr; diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 2a498b7e2c..7b2207bfa6 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #ifndef _WIN32 #include #include +#include #include #endif @@ -74,6 +76,14 @@ bool ThrowsNaming(Call call, const char* text) { return false; } +#ifndef _WIN32 +nlink_t LinkCount(const std::string& path) { + struct stat status {}; + Require(::lstat(path.c_str(), &status) == 0); + return status.st_nlink; +} +#endif + void OwnershipAndLinkTests(const std::filesystem::path& root) { constexpr std::uint32_t Unchanged = 0xFFFFFFFFu; const auto file = (root / "owned.txt").string(); @@ -101,13 +111,11 @@ void OwnershipAndLinkTests(const std::filesystem::path& root) { Require(fchownat_nid_postfix(-100, missing.c_str(), 1000, 0, 0) == -1 && Error() == 2); Require(fchownat_nid_postfix(4321, "owned.txt", 1000, 0, 0) == -1 && Error() == 9); Require(fchownat_nid_postfix(-100, file.c_str(), 1000, 0, 0x400) == -1 && Error() == 22); - Require(ThrowsNaming([&] { chown_nid_postfix(file.c_str(), 1000, 0); }, "chown: an owner")); - Require(ThrowsNaming([&] { lchown_nid_postfix(file.c_str(), 0, 1000); }, "lchown: a group")); - Require(ThrowsNaming([&] { fchownat_nid_postfix(-100, file.c_str(), 5, Unchanged, 0); }, "fchownat: an owner")); const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(directory >= 0); Require(fchownat_nid_postfix(directory, "owned.txt", 0, 0, 0) == 0); Require(fchownat_nid_postfix(directory, "missing.txt", 0, 0, 0) == -1 && Error() == 2); + Require(fchownat_nid_postfix(directory, "missing.txt", 1000, 0, 0) == -1 && Error() == 2); Require(linkat_nid_postfix(-100, file.c_str(), -100, linked.c_str(), 0) == 0); Require(std::filesystem::hard_link_count(file) == 2); Require(linkat_nid_postfix(-100, file.c_str(), -100, linked.c_str(), 0x400) == -1 && Error() == 17); @@ -144,7 +152,29 @@ void OwnershipAndLinkTests(const std::filesystem::path& root) { Require(lchown_nid_postfix(dangling.c_str(), 0, 0) == 0); Require(fchownat_nid_postfix(-100, dangling.c_str(), 0, 0, 0) == -1 && Error() == 2); Require(fchownat_nid_postfix(-100, dangling.c_str(), 0, 0, 0x200) == 0); - Require(ThrowsNaming([&] { linkat_nid_postfix(-100, symbolic.c_str(), -100, other.c_str(), 0); }, "symbolic link itself")); +#ifndef _WIN32 + const auto symbolicCopy = (root / "copy.link").string(); + const auto danglingCopy = (root / "dangling.copy").string(); + Require(linkat_nid_postfix(-100, symbolic.c_str(), -100, symbolicCopy.c_str(), 0) == 0); + Require(std::filesystem::read_symlink(symbolicCopy) == "owned.txt" && LinkCount(symbolic) == 2); + Require(std::filesystem::hard_link_count(file) == 2); + Require(linkat_nid_postfix(directory, "dangling.link", directory, "dangling.copy", 0) == 0); + Require(std::filesystem::read_symlink(danglingCopy) == "missing.txt" && LinkCount(dangling) == 2); + Require(linkat_nid_postfix(-100, dangling.c_str(), -100, other.c_str(), 0x400) == -1 && Error() == 2); + Require(linkat_nid_postfix(-100, symbolic.c_str(), -100, linked.c_str(), 0) == -1 && Error() == 17); + Require(linkat_nid_postfix(-100, symbolic.c_str(), -100, (missing + "/inner").c_str(), 0) == -1 && Error() == 2); + Require(linkat_nid_postfix(-100, symbolic.c_str(), -100, (throughFile + "/inner").c_str(), 0) == -1 && Error() == 20); + Require(LinkCount(symbolic) == 2 && !std::filesystem::exists(std::filesystem::symlink_status(other))); + const auto folder = root / "folder"; + const auto folderLink = (root / "folder.link").string(); + const auto folderCopy = (root / "folder.copy").string(); + Require(std::filesystem::create_directory(folder) && symlink_nid_postfix("folder", folderLink.c_str()) == 0); + Require(linkat_nid_postfix(-100, folderLink.c_str(), -100, other.c_str(), 0x400) == -1 && Error() == 1); + Require(linkat_nid_postfix(-100, folderLink.c_str(), -100, folderCopy.c_str(), 0) == 0); + Require(std::filesystem::read_symlink(folderCopy) == "folder" && LinkCount(folderLink) == 2); + Require(std::filesystem::remove(symbolicCopy) && std::filesystem::remove(danglingCopy) && std::filesystem::remove(folderCopy)); + Require(std::filesystem::remove(folderLink) && std::filesystem::remove(folder)); +#endif Require(linkat_nid_postfix(-100, symbolic.c_str(), -100, other.c_str(), 0x400) == 0); #ifdef _WIN32 Require((GetFileAttributesW(std::filesystem::path(other).c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) == 0); @@ -152,7 +182,7 @@ void OwnershipAndLinkTests(const std::filesystem::path& root) { Require(!std::filesystem::is_symlink(std::filesystem::symlink_status(other))); #endif Require(std::filesystem::hard_link_count(file) == 3); - Require(std::filesystem::remove(other) && std::filesystem::remove(symbolic) && std::filesystem::remove(dangling)); + Require(std::filesystem::remove(other) && unlink_nid_postfix(symbolic.c_str()) == 0 && unlink_nid_postfix(dangling.c_str()) == 0); } Require(sceKernelClose(directory) == 0); Require(std::filesystem::remove(linked) && std::filesystem::remove(file)); @@ -324,6 +354,100 @@ void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { } } +UserUcontext sigreturnContext{}; +UserUcontext sigreturnResume{}; +volatile int sigreturnPasses = 0; +volatile std::uint32_t sigreturnMxcsr = 0; + +std::uint32_t RoundingAndMasks(std::uint32_t mxcsr) { + return mxcsr & ~0x3fu; +} + +void SetContextMxcsr(UserUcontext& context, std::uint32_t mxcsr) { + std::memcpy(reinterpret_cast(context.mcontext.fpstate) + 24, &mxcsr, sizeof(mxcsr)); +} + +void CheckSigreturnRefusals(const UserUcontext& valid) { + UserUcontext bad = valid; + bad.mcontext.flags = 0x8; + Require(sigreturn_nid_postfix(&bad) == -1 && Error() == 22); + for (const std::uint64_t privileged : {0x200ull, 0x3000ull, 0x20000ull, 0x80000ull}) { + bad = valid; + bad.mcontext.rflags ^= privileged; + Require(sigreturn_nid_postfix(&bad) == -1 && Error() == 22); + } + bad = valid; + bad.mcontext.fpformat = 0x10001; + Require(sigreturn_nid_postfix(&bad) == -1 && Error() == 22); + bad = valid; + bad.mcontext.ownedfp = 0x20003; + Require(sigreturn_nid_postfix(&bad) == -1 && Error() == 22); + bad = valid; + bad.mcontext.cs &= ~std::uint64_t{3}; + Require(ThrowsNaming([&] { sigreturn_nid_postfix(&bad); }, "not a user selector")); +} + +void CheckSigreturn() { + Require(sigreturn_nid_postfix(nullptr) == -1 && Error() == 14); + const std::uint32_t savedMxcsr = _mm_getcsr(); + sigreturnPasses = 0; + Require(getcontext_nid_postfix(&sigreturnContext) == 0); + sigreturnMxcsr = _mm_getcsr(); + sigreturnPasses = sigreturnPasses + 1; + std::uint32_t mask[4] = {}; + Require(sigprocmask_nid_postfix(1, nullptr, mask) == 0); + const bool blocked = (mask[0] & (1u << 29)) != 0; + if (sigreturnPasses == 1) { + CheckSigreturnRefusals(sigreturnContext); + sigreturnResume = sigreturnContext; + sigreturnResume.mcontext.len = 0; + sigreturnResume.sigmask[0] |= 1u << 29; + SetContextMxcsr(sigreturnResume, 0x7f80); + Require(setcontext_nid_postfix(&sigreturnResume) == -1 && Error() == 22); + sigreturn_nid_postfix(&sigreturnResume); + Require(false); + } + if (sigreturnPasses == 2) { + Require(RoundingAndMasks(sigreturnMxcsr) == 0x7f80 && blocked); + sigreturnResume = sigreturnContext; + sigreturnResume.mcontext.fpformat = 0x10000; + SetContextMxcsr(sigreturnResume, 0x5f80); + _mm_setcsr(0x3f80); + sigreturn_nid_postfix(&sigreturnResume); + Require(false); + } + if (sigreturnPasses == 3) { + Require(RoundingAndMasks(sigreturnMxcsr) == 0x3f80 && !blocked); + sigreturnResume = sigreturnContext; + sigreturnResume.mcontext.ownedfp = 0x20000; + SetContextMxcsr(sigreturnResume, 0x5f80); + sigreturn_nid_postfix(&sigreturnResume); + Require(false); + } + Require(sigreturnPasses == 4 && RoundingAndMasks(sigreturnMxcsr) == 0x1f80 && !blocked); + _mm_setcsr(savedMxcsr); +} + +void CheckSchedSetscheduler() { + const Pthread self = scePthreadSelf(); + int savedPolicy = 0; + KernelSchedParam saved{}; + Require(scePthreadGetschedparam(self, &savedPolicy, &saved) == 0); + KernelSchedParam param{700}; + Require(sched_setscheduler_nid_postfix(0, 1, nullptr) == -1 && Error() == 14); + Require(sched_setscheduler_nid_postfix(getpid_nid_postfix() + 1, 1, ¶m) == -1 && Error() == 3); + Require(sched_setscheduler_nid_postfix(-1, 1, ¶m) == -1 && Error() == 3); + for (const int policy : {-1, 0, 4, 7}) Require(sched_setscheduler_nid_postfix(0, policy, ¶m) == -1 && Error() == 22); + int policy = 0; + KernelSchedParam current{}; + Require(sched_setscheduler_nid_postfix(0, 1, ¶m) == 0); + Require(scePthreadGetschedparam(self, &policy, ¤t) == 0 && policy == 1 && current.sched_priority == 700); + param.sched_priority = 300; + Require(sched_setscheduler_nid_postfix(0, 3, ¶m) == 0); + Require(scePthreadGetschedparam(self, &policy, ¤t) == 0 && policy == 3 && current.sched_priority == 300); + Require(scePthreadSetschedparam(self, savedPolicy, &saved) == 0); +} + void CheckCondattrClock() { constexpr KernelClockid Realtime = 0; constexpr KernelClockid Monotonic = 4; @@ -674,6 +798,107 @@ void CheckPipePathconf(F fn) { Require(close_nid_postfix(ends[0]) == 0 && close_nid_postfix(ends[1]) == 0); } +int Kenv(int what, const char* name, const char* value, int len) { + return kenv_nid_postfix(what, name, const_cast(value), len); +} + +void CheckKernelEnvironment() { + constexpr int get = 0, set = 1, unset = 2, dump = 3; + char buffer[300]; + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(Kenv(dump, nullptr, nullptr, 0) == 0); + Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(unset, "anyps5.alpha", nullptr, 0) == -1 && Error() == 2); + Require(Kenv(4, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 22); + Require(Kenv(-1, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 22); + Require(Kenv(set, "anyps5.alpha", "one", 0) == -1 && Error() == 22); + Require(Kenv(set, "anyps5.alpha", "one", 3) == -1 && Error() == 63); + Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(set, "anyps5.negative", "one", -1) == 0); + Require(Kenv(get, "anyps5.negative", buffer, sizeof(buffer)) == 4 && std::strcmp(buffer, "one") == 0); + Require(Kenv(set, "anyps5.negative", std::string(128, 'v').c_str(), -1) == 0); + Require(Kenv(set, "anyps5.negative", std::string(129, 'v').c_str(), -1) == -1 && Error() == 63); + Require(Kenv(unset, "anyps5.negative", nullptr, 0) == 0); + + Require(Kenv(set, "anyps5.alpha", "one", 4) == 0); + Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == 4 && std::strcmp(buffer, "one") == 0); + Require(Kenv(get, "anyps5", buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(get, "anyps5.alph", buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(get, "anyps5.alphaa", buffer, sizeof(buffer)) == -1 && Error() == 2); + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(Kenv(get, "anyps5.alpha", buffer, 2) == 2 && buffer[0] == 'o' && buffer[1] == 'n' && buffer[2] == 0x5a); + Require(Kenv(get, "anyps5.alpha", buffer, 0) == 0 && buffer[2] == 0x5a); + Require(Kenv(get, "anyps5.alpha", nullptr, 0) == 0); + Require(Kenv(get, "anyps5.alpha", nullptr, 4) == -1 && Error() == 14); + Require(Kenv(get, "anyps5.alpha", reinterpret_cast(0x10), 4) == -1 && Error() == 14); + Require(Kenv(set, "anyps5.alpha", "", 1) == 0); + Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == 1 && buffer[0] == '\0'); + Require(Kenv(set, "anyps5.alpha", "one", 129) == 0); + Require(Kenv(set, "anyps5.beta", "two", 1000) == 0); + Require(Kenv(set, "anyps5.alpha", "uno", 4) == 0); + Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == 4 && std::strcmp(buffer, "uno") == 0); + + constexpr char expected[] = "anyps5.alpha=uno\0anyps5.beta=two"; + constexpr int needed = sizeof(expected); + Require(Kenv(dump, nullptr, nullptr, 0) == needed); + Require(Kenv(dump, nullptr, buffer, 0) == needed); + Require(Kenv(dump, nullptr, nullptr, sizeof(buffer)) == needed); + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(Kenv(dump, nullptr, buffer, sizeof(buffer)) == 0 && std::memcmp(buffer, expected, needed) == 0 && buffer[needed] == 0x5a); + Require(Kenv(dump, nullptr, buffer, needed) == 0); + std::memset(buffer, 0x5a, sizeof(buffer)); + Require(Kenv(dump, nullptr, buffer, 20) == needed && std::memcmp(buffer, expected, 20) == 0 && buffer[20] == 0x5a); + Require(Kenv(dump, nullptr, reinterpret_cast(0x10), sizeof(buffer)) == -1 && Error() == 14); + Require(Kenv(dump, reinterpret_cast(0x10), buffer, sizeof(buffer)) == 0); + + Require(Kenv(get, nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14); + Require(Kenv(get, reinterpret_cast(0x10), buffer, sizeof(buffer)) == -1 && Error() == 14); + Require(Kenv(4, nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14); + Require(Kenv(set, "anyps5.gamma", nullptr, 4) == -1 && Error() == 14); + Require(Kenv(set, "anyps5.gamma", reinterpret_cast(0x10), 4) == -1 && Error() == 14); + Require(Kenv(get, "anyps5.gamma", buffer, sizeof(buffer)) == -1 && Error() == 2); + + const std::string longest(128, 'n'); + const std::string longestValue(128, 'v'); + Require(Kenv(set, (longest + "x").c_str(), "v", 2) == -1 && Error() == 63); + Require(Kenv(get, (longest + "x").c_str(), buffer, sizeof(buffer)) == -1 && Error() == 63); + Require(Kenv(set, longest.c_str(), longestValue.c_str(), 129) == 0); + Require(Kenv(get, longest.c_str(), buffer, sizeof(buffer)) == 129 && std::strcmp(buffer, longestValue.c_str()) == 0); + Require(Kenv(set, "anyps5.gamma", (longestValue + "v").c_str(), 1000) == -1 && Error() == 63); + Require(Kenv(set, "anyps5.gamma", longestValue.c_str(), 128) == -1 && Error() == 63); + Require(Kenv(unset, longest.c_str(), nullptr, 0) == 0); + Require(Kenv(get, longest.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2); + + Require(Kenv(set, "anyps5.eq=name", "tail", 5) == 0); + Require(Kenv(get, "anyps5.eq", buffer, sizeof(buffer)) == 10 && std::strcmp(buffer, "name=tail") == 0); + Require(Kenv(unset, "anyps5.eq", nullptr, 0) == 0); + Require(Kenv(unset, "anyps5.eq", nullptr, 0) == -1 && Error() == 2); + Require(Kenv(unset, "anyps5.alpha", nullptr, 0) == 0); + Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(dump, nullptr, nullptr, 0) == static_cast(sizeof("anyps5.beta=two"))); + Require(Kenv(unset, "anyps5.beta", nullptr, 0) == 0); + Require(Kenv(dump, nullptr, nullptr, 0) == 0); + + for (int i = 0; i < 512; ++i) Require(Kenv(set, ("anyps5.fill" + std::to_string(i)).c_str(), "x", 2) == 0); + Require(Kenv(set, "anyps5.overflow", "x", 2) == 0); + Require(Kenv(get, "anyps5.overflow", buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(set, "anyps5.fill0", "y", 2) == 0); + Require(Kenv(get, "anyps5.fill0", buffer, sizeof(buffer)) == 2 && buffer[0] == 'y'); + Require(Kenv(unset, "anyps5.fill511", nullptr, 0) == 0); + Require(Kenv(set, "anyps5.overflow", "x", 2) == 0); + Require(Kenv(get, "anyps5.overflow", buffer, sizeof(buffer)) == 2); + for (int i = 0; i < 511; ++i) Require(Kenv(unset, ("anyps5.fill" + std::to_string(i)).c_str(), nullptr, 0) == 0); + Require(Kenv(unset, "anyps5.overflow", nullptr, 0) == 0); + Require(Kenv(dump, nullptr, nullptr, 0) == 0); +} + +template +void CheckUnsupportedPathconf(F fn, int descriptor) { + Require(fn(descriptor, 53) == 200112); + for (const int name : {0, 1, 4, 5, 6, 7, 8, 12, 18, 54, 55, 61, -1}) + Require(fn(descriptor, name) == -1 && Error() == 45); +} + int main() { #ifdef _WIN32 auto hostDirectorySpelling = std::filesystem::current_path().native(); @@ -693,6 +918,9 @@ int main() { CheckAmmWriteCommands(); CheckAmmWaitCompletion(); CheckAmmWaitConcurrentSubmits(); + CheckSchedSetscheduler(); + CheckSigreturn(); + CheckKernelEnvironment(); const auto root = std::filesystem::path("anyps5-kernel-sys-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(root)); @@ -720,6 +948,19 @@ int main() { CheckDescriptorPathconf(_fpathconf_nid_postfix, descriptor); CheckPipePathconf(fpathconf_nid_postfix); CheckPipePathconf(_fpathconf_nid_postfix); + const int kqueueDescriptor = kqueue_nid_postfix(); + Require(kqueueDescriptor >= 0); + CheckUnsupportedPathconf(fpathconf_nid_postfix, kqueueDescriptor); + CheckUnsupportedPathconf(_fpathconf_nid_postfix, kqueueDescriptor); + Require(close_nid_postfix(kqueueDescriptor) == 0); + const std::string namedSharedMemory = "/anyps5-kernel-sys-pathconf-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const int sharedMemoryDescriptor = shm_open_nid_postfix(namedSharedMemory.c_str(), SCE_KERNEL_O_RDWR | SCE_KERNEL_O_CREAT, 0600); + Require(sharedMemoryDescriptor >= 0); + CheckUnsupportedPathconf(fpathconf_nid_postfix, sharedMemoryDescriptor); + CheckUnsupportedPathconf(_fpathconf_nid_postfix, sharedMemoryDescriptor); + Require(close_nid_postfix(sharedMemoryDescriptor) == 0); + Require(shm_unlink_nid_postfix(namedSharedMemory.c_str()) == 0); Require(close_nid_postfix(descriptor) == 0); Require(fstatfs_nid_postfix(-1, reinterpret_cast(viaPublic)) == -1 && Error() == 9); Require(fstat_nid_postfix(descriptor, &viaFstat) == -1 && Error() == 9); @@ -942,6 +1183,40 @@ int main() { Require(kldunload_nid_postfix(-1) == -1 && Error() == 1); Require(kldunload_nid_postfix(0) == -1 && Error() == 1); Require(kldunload_nid_postfix(1) == -1 && Error() == 1); + struct SymbolLookup { + std::int32_t version; + const char* name; + std::uint64_t value; + std::uint64_t size; + }; + const std::string longSymbol(1024, 's'); + SymbolLookup lookup{32, "symbol", 7, 9}; + Require(kldsym_nid_postfix(-1, 1, nullptr) == -1 && Error() == 14); + Require(kldsym_nid_postfix(-1, 1, reinterpret_cast(0x10)) == -1 && Error() == 14); + Require(kldsym_nid_postfix(-1, 1, &lookup) == -1 && Error() == 2); + Require(lookup.value == 7 && lookup.size == 9); + for (const std::int32_t version : {0, 24, 31, 33}) { + lookup = {version, "symbol", 0, 0}; + Require(kldsym_nid_postfix(-1, 1, &lookup) == -1 && Error() == 22); + lookup.name = nullptr; + Require(kldsym_nid_postfix(-1, 1, &lookup) == -1 && Error() == 22); + } + for (const int command : {0, 2, -1}) { + lookup = {32, nullptr, 0, 0}; + Require(kldsym_nid_postfix(-1, command, &lookup) == -1 && Error() == 22); + } + lookup = {32, nullptr, 0, 0}; + Require(kldsym_nid_postfix(-1, 1, &lookup) == -1 && Error() == 14); + lookup.name = reinterpret_cast(0x10); + Require(kldsym_nid_postfix(0, 1, &lookup) == -1 && Error() == 14); + lookup.name = longSymbol.c_str(); + Require(kldsym_nid_postfix(-1, 1, &lookup) == -1 && Error() == 63); + lookup.name = ""; + Require(kldsym_nid_postfix(-1, 1, &lookup) == -1 && Error() == 2); + lookup.name = "symbol"; + for (const int missing : {-2147483647 - 1, 1, 2, 3, 100, 2147483647}) { + Require(kldsym_nid_postfix(missing, 1, &lookup) == -1 && Error() == 2); + } const std::string throughFile = (root / "file.txt" / "child").string(); Require(pathconf_nid_postfix(throughFile.c_str(), 4) == -1 && Error() == 20); Require(fchmodat_nid_postfix(-100, throughFile.c_str(), 0600, 0) == -1 && Error() == 20); @@ -972,6 +1247,13 @@ int main() { Require(chdir_nid_postfix("/") == 0); Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && std::strcmp(directoryPath, "/") == 0); Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); + Require(lchflags_nid_postfix(file.c_str(), 0) == 0); + Require(lchflags_nid_postfix(nullptr, 0) == -1 && Error() == 14); + Require(lchflags_nid_postfix("", 0) == -1 && Error() == 2); + Require(lchflags_nid_postfix((root / "missing.bin").string().c_str(), 0) == -1 && Error() == 2); + Require(lchflags_nid_postfix((file + "/child").c_str(), 0) == -1 && Error() == 20); + Require(lchflags_nid_postfix(file.c_str(), 0x20) == -1 && Error() == 45); + Require(lchflags_nid_postfix(file.c_str(), 0x200000) == -1 && Error() == 1); const auto symbolic = (root / "symbolic").string(); const int created = symlink_nid_postfix("original.bin", symbolic.c_str()); if (created == 0) { @@ -1023,6 +1305,10 @@ int main() { Require(readlink_nid_postfix(dangling.c_str(), buffer, sizeof(buffer)) == 11 && !std::filesystem::exists(dangling)); Require(symlink_nid_postfix("missing.bin", dangling.c_str()) == -1 && Error() == 17); #endif + Require(lchflags_nid_postfix(dangling.c_str(), 0) == 0); + Require(lchflags_nid_postfix(symbolic.c_str(), 0) == 0); + Require(lchflags_nid_postfix(dangling.c_str(), 0x20) == -1 && Error() == 45); + Require(lchflags_nid_postfix(dangling.c_str(), 0x200000) == -1 && Error() == 1); } else { Require(created == -1 && Error() == 1); std::fprintf(stderr, "symbolic links unavailable: the symlink success cases not tested\n"); diff --git a/core/libs/tests/GuestKernelSysCpumode.cpp b/core/libs/tests/GuestKernelSysCpumode.cpp new file mode 100644 index 0000000000..3c5cb4fd65 --- /dev/null +++ b/core/libs/tests/GuestKernelSysCpumode.cpp @@ -0,0 +1,42 @@ +#include "prx/libkernel_sys/include/Api.hpp" +#include +#include +#include + +namespace { + +void Require(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "%s\n", what); + std::abort(); +} + +bool Throws(void (*action)()) { + try { + action(); + } catch (const std::exception&) { + return true; + } + return false; +} + +} + +int main() { + constexpr int invalid = static_cast(0x80020016u); + int mode = -1; + Require(sceKernelGetCpumodeGame(&mode) == 0 && mode == 1, "the game CPU mode does not start at 1"); + Require(sceKernelSetCpumodeGame(0) == 0, "mode 0 was refused"); + Require(sceKernelGetCpumodeGame(&mode) == 0 && mode == 0, "mode 0 was not reported back"); + for (int refused = 2; refused <= 7; ++refused) { + Require(sceKernelSetCpumodeGame(refused) == invalid, "a mode from 2 to 7 was not refused with EINVAL"); + Require(sceKernelGetCpumodeGame(&mode) == 0 && mode == 0, "a refused mode changed the game CPU mode"); + } + Require(sceKernelSetCpumodeGame(1) == 0, "mode 1 was refused"); + Require(sceKernelGetCpumodeGame(&mode) == 0 && mode == 1, "mode 1 was not reported back"); + Require(Throws([] { sceKernelSetCpumodeGame(8); }), "an unmeasured mode did not throw"); + Require(Throws([] { sceKernelSetCpumodeGame(-1); }), "a negative mode did not throw"); + Require(Throws([] { sceKernelGetCpumodeGame(nullptr); }), "a null output did not throw"); + std::puts("game CPU mode tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysPthreadNp.cpp b/core/libs/tests/GuestKernelSysPthreadNp.cpp index a1cd8ca017..29f905679f 100644 --- a/core/libs/tests/GuestKernelSysPthreadNp.cpp +++ b/core/libs/tests/GuestKernelSysPthreadNp.cpp @@ -1,17 +1,20 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "prx/libkernel/Pthread/include/Rwlock.hpp" #include "SceTypes.hpp" #include #include #include -#include #include +#include namespace { constexpr int GUEST_EDEADLK = 11; constexpr int GUEST_EINVAL = 22; constexpr int SCE_FAILED_CALL = static_cast(0x8001ffffu); +constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016u); constexpr unsigned SCE_KERNEL_ERROR_BASE = 0x80020000u; constexpr int KIND_ERRORCHECK = 1; constexpr int KIND_RECURSIVE = 2; @@ -31,16 +34,6 @@ void Case(const char* name, TCall call) { ++failures; } -template -bool Throws(TCall call) { - try { - static_cast(call()); - } catch (const TException&) { - return true; - } - return false; -} - bool HasKind(const PthreadMutexattr* attr, int kind) { int type = -1; return pthread_mutexattr_gettype_nid_postfix(attr, &type) == 0 && type == kind; @@ -63,11 +56,20 @@ void TestKind() { } if (!LocksTwice(&attr, GUEST_EDEADLK)) return false; if (scePthreadMutexattrSetkind(&attr, KIND_RECURSIVE) != 0 || !LocksTwice(&attr, 0)) return false; - for (const int kind : {0, 5, -1}) { - if (!Throws([&] { return scePthreadMutexattrSetkind(&attr, kind); }) || !HasKind(&attr, KIND_RECURSIVE)) return false; + for (const auto [kind, stored] : {std::pair{0, 0}, std::pair{5, 5}, std::pair{-1, -1}, std::pair{0x8000, -32768}, std::pair{0x12345, 0x2345}}) { + if (scePthreadMutexattrSetkind(&attr, kind) != 0 || pthread_mutexattr_getkind_np_nid_postfix(attr) != stored) return false; + PthreadMutex mutex = nullptr; + if (scePthreadMutexInit(&mutex, &attr, nullptr) != SCE_KERNEL_ERROR_EINVAL || mutex != nullptr) return false; } + if (scePthreadMutexattrSetkind(&attr, 0x10001) != 0 || !HasKind(&attr, KIND_ERRORCHECK) || !LocksTwice(&attr, GUEST_EDEADLK)) return false; if (pthread_mutexattr_destroy_nid_postfix(&attr) != 0) return false; - return scePthreadMutexattrSetkind(&attr, KIND_NORMAL) == SCE_FAILED_CALL && scePthreadMutexattrSetkind(nullptr, KIND_NORMAL) == SCE_FAILED_CALL; + for (PthreadMutexattr* target : {&attr, static_cast(nullptr)}) { + for (const int kind : {KIND_NORMAL, 5}) { + *__error_nid_postfix() = 0; + if (scePthreadMutexattrSetkind(target, kind) != SCE_FAILED_CALL || *__error_nid_postfix() != GUEST_EINVAL) return false; + } + } + return true; }); Case("scePthreadMutexattrGetkind", [] { PthreadMutexattr attr = nullptr; @@ -75,6 +77,9 @@ void TestKind() { for (const int kind : {KIND_RECURSIVE, KIND_NORMAL, KIND_ADAPTIVE, KIND_ERRORCHECK}) { if (scePthreadMutexattrSetkind(&attr, kind) != 0 || scePthreadMutexattrGetkind(attr) != static_cast(SCE_KERNEL_ERROR_BASE + static_cast(kind))) return false; } + for (const auto [kind, result] : {std::pair{0, 0}, std::pair{5, static_cast(SCE_KERNEL_ERROR_BASE + 5u)}, std::pair{0x12345, static_cast(SCE_KERNEL_ERROR_BASE + 0x2345u)}}) { + if (scePthreadMutexattrSetkind(&attr, kind) != 0 || scePthreadMutexattrGetkind(attr) != result) return false; + } if (pthread_mutexattr_destroy_nid_postfix(&attr) != 0) return false; return scePthreadMutexattrGetkind(nullptr) == SCE_FAILED_CALL; }); diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index beac08a160..7b81a77b4a 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -61,6 +61,7 @@ static constexpr std::uint32_t NoteTrigger = 0x01000000; static constexpr std::uint32_t NoteLowat = 0x0001; static constexpr std::uint32_t NoteFfcopy = 0xc0000000; static constexpr std::uint32_t NoteFfor = 0x80000000; +static constexpr std::uint32_t NoteMseconds = 0x0002; static constexpr std::uint32_t NoteUseconds = 0x0004; static constexpr std::uint32_t NoteNseconds = 0x0008; static constexpr std::uint32_t NoteDelete = 0x0001; @@ -215,13 +216,24 @@ static void TimerReadd() { Require(Apply(kq, Change(10, EvfiltTimer, EvAdd | EvOneshot, 0, 100000)) == 0); Require(Throws([kq] { Apply(kq, Change(10, EvfiltTimer, EvAdd, 0, 5)); })); Require(Apply(kq, Change(11, EvfiltTimer, EvAdd, 0, 100000)) == 0); - Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvAdd, NoteUseconds, 5)); })); + Require(Apply(kq, Change(11, EvfiltTimer, EvAdd, NoteMseconds, 100000)) == 0); + Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvAdd, NoteMseconds | NoteUseconds, 5)); })); + Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvAdd, 0x10, 5)); })); Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvAdd, 0, 0)); })); Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvAdd, 0, INTPTR_MAX)); })); Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvAdd | EvClear, 0, 5)); })); Require(Throws([kq] { Apply(kq, Change(11, EvfiltTimer, EvEnable, 0, 5)); })); Require(Apply(kq, Change(12, EvfiltTimer, EvAdd | EvDisable, 0, 100000)) == 0); - Require(Throws([kq] { Apply(kq, Change(12, EvfiltTimer, EvAdd, 0, 5)); })); + Require(Apply(kq, Change(12, EvfiltTimer, EvAdd, 0, 5)) == 0); + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + Require(Poll(kq, events, 4) == 0); + + Require(Apply(kq, Change(13, EvfiltTimer, EvAdd, 0, 30)) == 0); + const auto unitArmed = std::chrono::steady_clock::now(); + Require(Apply(kq, Change(13, EvfiltTimer, EvAdd, NoteMseconds, 30)) == 0); + Require(Wait(kq, events, 4) == 1 && events[0].ident == 13); + Require(std::chrono::steady_clock::now() - unitArmed >= std::chrono::milliseconds(20)); + Require(Apply(kq, Change(13, EvfiltTimer, EvDelete)) == 0); Require(close_nid_postfix(kq) == 0); } diff --git a/core/libs/tests/GuestLibreSslX509StoreContext.cpp b/core/libs/tests/GuestLibreSslX509StoreContext.cpp new file mode 100644 index 0000000000..edc80af33b --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509StoreContext.cpp @@ -0,0 +1,119 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_STORE_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +constexpr std::size_t ContextSize = 240; +constexpr std::size_t ChainOffset = 160; +constexpr std::size_t ErrorDepthOffset = 180; +constexpr std::size_t ErrorOffset = 184; +constexpr std::size_t CurrentCertOffset = 192; + +struct alignas(8) TestContext { + unsigned char bytes[ContextSize]; + + TestContext() { std::memset(bytes, 0xAA, sizeof(bytes)); } + + template void Write(std::size_t offset, TValue value) { std::memcpy(bytes + offset, &value, sizeof(value)); } + + template TValue Read(std::size_t offset) const { + TValue value; + std::memcpy(&value, bytes + offset, sizeof(value)); + return value; + } +}; + +static LibreSslX509StoreContext* Context(TestContext& context) { + return reinterpret_cast(context.bytes); +} + +static bool OnlyChanged(const TestContext& context, std::size_t offset, std::size_t size) { + for (std::size_t index = 0; index < ContextSize; ++index) + if ((index < offset || index >= offset + size) && context.bytes[index] != 0xAA) return false; + return true; +} + +static void TestGetError() { + TestContext context; + context.Write(ErrorOffset, 0); + Require(X509_STORE_CTX_get_error_nid_postfix(Context(context)) == 0); + context.Write(ErrorOffset, 20); + Require(X509_STORE_CTX_get_error_nid_postfix(Context(context)) == 20); + context.Write(ErrorOffset, -7); + Require(X509_STORE_CTX_get_error_nid_postfix(Context(context)) == -7); + Require(OnlyChanged(context, ErrorOffset, 4)); + RequireThrows([] { X509_STORE_CTX_get_error_nid_postfix(nullptr); }); +} + +static void TestSetError() { + TestContext context; + X509_STORE_CTX_set_error_nid_postfix(Context(context), 10); + Require(context.Read(ErrorOffset) == 10); + Require(X509_STORE_CTX_get_error_nid_postfix(Context(context)) == 10); + X509_STORE_CTX_set_error_nid_postfix(Context(context), -1); + Require(context.Read(ErrorOffset) == -1); + X509_STORE_CTX_set_error_nid_postfix(Context(context), 0); + Require(context.Read(ErrorOffset) == 0); + Require(OnlyChanged(context, ErrorOffset, 4)); + RequireThrows([] { X509_STORE_CTX_set_error_nid_postfix(nullptr, 1); }); +} + +static void TestGetErrorDepth() { + TestContext context; + context.Write(ErrorDepthOffset, 0); + Require(X509_STORE_CTX_get_error_depth_nid_postfix(Context(context)) == 0); + context.Write(ErrorDepthOffset, 3); + Require(X509_STORE_CTX_get_error_depth_nid_postfix(Context(context)) == 3); + context.Write(ErrorOffset, 9); + Require(X509_STORE_CTX_get_error_depth_nid_postfix(Context(context)) == 3); + X509_STORE_CTX_set_error_nid_postfix(Context(context), 11); + Require(X509_STORE_CTX_get_error_depth_nid_postfix(Context(context)) == 3); + RequireThrows([] { X509_STORE_CTX_get_error_depth_nid_postfix(nullptr); }); +} + +static void TestGetCurrentCert() { + TestContext context; + int certificate = 0; + context.Write(CurrentCertOffset, nullptr); + Require(X509_STORE_CTX_get_current_cert_nid_postfix(Context(context)) == nullptr); + context.Write(CurrentCertOffset, &certificate); + Require(X509_STORE_CTX_get_current_cert_nid_postfix(Context(context)) == &certificate); + Require(OnlyChanged(context, CurrentCertOffset, 8)); + RequireThrows([] { X509_STORE_CTX_get_current_cert_nid_postfix(nullptr); }); +} + +static void TestGetChain() { + TestContext context; + int chain = 0; + context.Write(ChainOffset, nullptr); + Require(X509_STORE_CTX_get_chain_nid_postfix(Context(context)) == nullptr); + context.Write(ChainOffset, &chain); + Require(X509_STORE_CTX_get_chain_nid_postfix(Context(context)) == &chain); + Require(OnlyChanged(context, ChainOffset, 8)); + RequireThrows([] { X509_STORE_CTX_get_chain_nid_postfix(nullptr); }); +} + +int main() { + TestGetError(); + TestSetError(); + TestGetErrorDepth(); + TestGetCurrentCert(); + TestGetChain(); + std::puts("libSceLibreSsl X509_STORE_CTX tests passed"); +} diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index f2ba945bb3..35c2a6ece2 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -30,6 +30,7 @@ extern std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsI extern std::uintptr_t _ZTVSt8time_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[13]; extern std::uintptr_t _ZTVSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[17]; extern std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[14]; +extern std::uintptr_t _ZTVSt5ctypeIcE_nid_postfix[14]; } @@ -500,6 +501,15 @@ static void CheckFacetGetcat() { Require(_ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&punct, &locale) == 4); } +static void CheckCtypeVtableObject() { + for (std::uintptr_t& slot : _ZTVSt5ctypeIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckNumPutVtableObject() { Require(sizeof(_ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kNumPutCharVtableSlots * sizeof(std::uintptr_t)); for (std::uintptr_t& slot : _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { @@ -565,6 +575,7 @@ int main() { CheckTimeGetWideVtableObject(); CheckNumGetWideVtableObject(); CheckNumPutVtableObject(); + CheckCtypeVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index f8e0f9da06..80f1217e86 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -309,72 +309,74 @@ static void CheckNanConversions() { bool extendedMeasured; bool wideDoubleMeasured; bool wideOtherMeasured; + std::uint32_t singleBits; + std::size_t singleUsed; }; static constexpr MeasuredNan cases[] = { - {"nan", 0x7ff8000000000000ull, 3, 3, true, true, true}, - {"NAN", 0x7ff8000000000000ull, 3, 3, false, false, false}, - {"nan()", 0x7ff8000000000000ull, 5, 5, false, false, false}, - {"nan(1)", 0x7ff8000000000001ull, 6, 6, true, true, true}, - {"nan(0x10)", 0x7ff8000000000010ull, 9, 9, false, false, false}, - {"nan(abc)", 0x7ff8000000000abcull, 8, 8, false, false, false}, - {"nan(", 0x7ff8000000000000ull, 3, 3, true, true, true}, - {"-nan", 0xfff8000000000000ull, 4, 4, true, true, true}, - {"+nan(5)", 0x7ff8000000000005ull, 7, 7, false, false, false}, - {"nan(10)", 0x7ff8000000000010ull, 7, 7, false, false, false}, - {"nan(xyz)", 0x7ff8000000000000ull, 8, 8, false, false, false}, - {"nan(_)", 0x7ff8000000000000ull, 6, 6, false, false, false}, - {"nan(12", 0x7ff8000000000012ull, 3, 3, false, false, false}, - {"-nan(5)", 0xfff8000000000005ull, 7, 7, false, false, false}, - {"nan(0x8000000000000)", 0x7ff8000000000000ull, 20, 20, false, false, false}, - {"nan(0x10000000000000)", 0x7ff8000000000001ull, 21, 21, true, true, false}, - {"nan(0x400000)", 0x7ff8000000400000ull, 13, 13, false, false, false}, - {"NaN(0X1F)", 0x7ff800000000001full, 9, 9, false, false, false}, - {"nan(0x)", 0x7ff8000000000000ull, 7, 7, false, false, false}, - {"nan(123abc)", 0x7ff8000000123abcull, 11, 11, true, true, false}, - {"nan(1x2)", 0x7ff8000000000000ull, 8, 8, true, true, false}, - {"nan(0x1g)", 0x7ff8000000000000ull, 9, 9, true, true, false}, - {"nan(g1)", 0x7ff8000000000000ull, 7, 7, true, true, false}, - {"nan(0x0x1)", 0x7ff8000000000000ull, 10, 10, true, true, false}, - {"nan(fffffffffffff)", 0x7fffffffffffffffull, 18, 18, true, true, false}, - {"nan(1fffffffffffff)", 0x7fffffffffffffffull, 19, 19, true, true, false}, - {"nan(10000000000000)", 0x7ff8000000000001ull, 19, 19, true, true, false}, - {"nan(20000000000000)", 0x7ff8000000000001ull, 19, 19, true, true, false}, - {"nan(0x20000000000000)", 0x7ff8000000000001ull, 21, 21, true, true, false}, - {"nan(ffffffffffffffff)", 0x7fffffffffffffffull, 21, 21, true, true, false}, - {"nan(10000000000000000)", 0x7ff8000000000001ull, 22, 22, true, true, false}, - {"nan(0X)", 0x7ff8000000000000ull, 7, 7, true, true, false}, - {"nan(-1)", 0x7ff8000000000000ull, 7, 3, true, true, false}, - {"nan(+1)", 0x7ff8000000000000ull, 7, 3, true, true, false}, - {"nan( 1)", 0x7ff8000000000001ull, 7, 3, true, true, false}, - {"nan(1 )", 0x7ff8000100000000ull, 7, 3, true, true, false}, - {"nan(0)", 0x7ff8000000000001ull, 6, 6, true, true, false}, - {"nan(00001)", 0x7ff8000000000001ull, 10, 10, true, true, false}, - {"nan(0x0001)", 0x7ff8000000000001ull, 11, 11, true, true, false}, - {"nan(8000000000000)", 0x7ff8000000000000ull, 18, 18, true, true, false}, - {"nan(7ffffffffffff)", 0x7fffffffffffffffull, 18, 18, true, true, false}, - {"nan(123", 0x7ff8000000000123ull, 3, 3, true, true, false}, - {"nan(1)x", 0x7ff8000000000001ull, 6, 6, true, true, false}, - {"-nan(1)", 0xfff8000000000001ull, 7, 7, true, true, false}, - {"nan(abcdefABCDEF)", 0x7ff8abcdefabcdefull, 17, 17, true, true, false}, - {"nan(0xABCDEF)", 0x7ff8000000abcdefull, 13, 13, true, true, false}, - {"nan(9)", 0x7ff8000000000009ull, 6, 6, true, true, false}, - {"nan(1_2)", 0x7ff8000000000000ull, 8, 8, true, true, false}, - {"nan(_1)", 0x7ff8000000000000ull, 7, 7, true, true, false}, - {"nan(1)(2)", 0x7ff8000000000001ull, 6, 6, true, true, false}, - {"+nan", 0x7ff8000000000000ull, 4, 4, true, true, true}, - {"NaN", 0x7ff8000000000000ull, 3, 3, true, true, true}, - {"-NAN(5)", 0xfff8000000000005ull, 7, 7, true, true, true}, - {"nan(5)", 0x7ff8000000000005ull, 6, 6, true, true, true}, - {"-nan(abc)", 0xfff8000000000abcull, 9, 9, true, true, true}, - {" -nan", 0xfff8000000000000ull, 6, 6, true, true, true}, - {"nan(12 x", 0x7ff8000000000000ull, 3, 3, true, true, true}, - {"nan(12x", 0x7ff8000000000000ull, 3, 3, true, true, true}, - {"nan(1", 0x7ff8000000000001ull, 3, 3, true, true, true}, - {"nan(a b)", 0x7ff8000a0000000bull, 8, 3, true, true, true}, - {"nan(ab)", 0x7ff80000000000abull, 7, 7, true, true, true}, - {"nanx", 0x7ff8000000000000ull, 3, 3, true, true, true}, - {"-nan()", 0xfff8000000000000ull, 6, 6, true, true, true}, - {"nan(0x8000000000001)", 0x7ff8000000000001ull, 20, 20, true, true, true}, + {"nan", 0x7ff8000000000000ull, 3, 3, true, true, true, 0x7fc00000u, 3}, + {"NAN", 0x7ff8000000000000ull, 3, 3, false, false, false, 0x7fc00000u, 3}, + {"nan()", 0x7ff8000000000000ull, 5, 5, false, false, false, 0x7fc00000u, 5}, + {"nan(1)", 0x7ff8000000000001ull, 6, 6, true, true, true, 0x7fc00001u, 6}, + {"nan(0x10)", 0x7ff8000000000010ull, 9, 9, false, false, false, 0x7fc00010u, 9}, + {"nan(abc)", 0x7ff8000000000abcull, 8, 8, false, false, false, 0x7fc00abcu, 8}, + {"nan(", 0x7ff8000000000000ull, 3, 3, true, true, true, 0x7fc00000u, 3}, + {"-nan", 0xfff8000000000000ull, 4, 4, true, true, true, 0xffc00000u, 4}, + {"+nan(5)", 0x7ff8000000000005ull, 7, 7, false, false, false, 0x7fc00005u, 7}, + {"nan(10)", 0x7ff8000000000010ull, 7, 7, false, false, false, 0x7fc00010u, 7}, + {"nan(xyz)", 0x7ff8000000000000ull, 8, 8, false, false, false, 0x7fc00000u, 8}, + {"nan(_)", 0x7ff8000000000000ull, 6, 6, false, false, false, 0x7fc00000u, 6}, + {"nan(12", 0x7ff8000000000012ull, 3, 3, false, false, false, 0x7fc00012u, 3}, + {"-nan(5)", 0xfff8000000000005ull, 7, 7, false, false, false, 0xffc00005u, 7}, + {"nan(0x8000000000000)", 0x7ff8000000000000ull, 20, 20, false, false, false, 0x7fc00001u, 20}, + {"nan(0x10000000000000)", 0x7ff8000000000001ull, 21, 21, true, true, false, 0x7fc00001u, 21}, + {"nan(0x400000)", 0x7ff8000000400000ull, 13, 13, false, false, false, 0x7fc00000u, 13}, + {"NaN(0X1F)", 0x7ff800000000001full, 9, 9, false, false, false, 0x7fc0001fu, 9}, + {"nan(0x)", 0x7ff8000000000000ull, 7, 7, false, false, false, 0x7fc00000u, 7}, + {"nan(123abc)", 0x7ff8000000123abcull, 11, 11, true, true, false, 0x7fd23abcu, 11}, + {"nan(1x2)", 0x7ff8000000000000ull, 8, 8, true, true, false, 0x7fc00000u, 8}, + {"nan(0x1g)", 0x7ff8000000000000ull, 9, 9, true, true, false, 0x7fc00000u, 9}, + {"nan(g1)", 0x7ff8000000000000ull, 7, 7, true, true, false, 0x7fc00000u, 7}, + {"nan(0x0x1)", 0x7ff8000000000000ull, 10, 10, true, true, false, 0x7fc00000u, 10}, + {"nan(fffffffffffff)", 0x7fffffffffffffffull, 18, 18, true, true, false, 0x7fffffffu, 18}, + {"nan(1fffffffffffff)", 0x7fffffffffffffffull, 19, 19, true, true, false, 0x7fffffffu, 19}, + {"nan(10000000000000)", 0x7ff8000000000001ull, 19, 19, true, true, false, 0x7fc00001u, 19}, + {"nan(20000000000000)", 0x7ff8000000000001ull, 19, 19, true, true, false, 0x7fc00001u, 19}, + {"nan(0x20000000000000)", 0x7ff8000000000001ull, 21, 21, true, true, false, 0x7fc00001u, 21}, + {"nan(ffffffffffffffff)", 0x7fffffffffffffffull, 21, 21, true, true, false, 0x7fffffffu, 21}, + {"nan(10000000000000000)", 0x7ff8000000000001ull, 22, 22, true, true, false, 0x7fc00001u, 22}, + {"nan(0X)", 0x7ff8000000000000ull, 7, 7, true, true, false, 0x7fc00000u, 7}, + {"nan(-1)", 0x7ff8000000000000ull, 7, 3, true, true, false, 0x7fc00000u, 7}, + {"nan(+1)", 0x7ff8000000000000ull, 7, 3, true, true, false, 0x7fc00000u, 7}, + {"nan( 1)", 0x7ff8000000000001ull, 7, 3, true, true, false, 0x7fc00001u, 7}, + {"nan(1 )", 0x7ff8000100000000ull, 7, 3, true, true, false, 0x7fc00001u, 7}, + {"nan(0)", 0x7ff8000000000001ull, 6, 6, true, true, false, 0x7fc00001u, 6}, + {"nan(00001)", 0x7ff8000000000001ull, 10, 10, true, true, false, 0x7fc00001u, 10}, + {"nan(0x0001)", 0x7ff8000000000001ull, 11, 11, true, true, false, 0x7fc00001u, 11}, + {"nan(8000000000000)", 0x7ff8000000000000ull, 18, 18, true, true, false, 0x7fc00001u, 18}, + {"nan(7ffffffffffff)", 0x7fffffffffffffffull, 18, 18, true, true, false, 0x7fffffffu, 18}, + {"nan(123", 0x7ff8000000000123ull, 3, 3, true, true, false, 0x7fc00123u, 3}, + {"nan(1)x", 0x7ff8000000000001ull, 6, 6, true, true, false, 0x7fc00001u, 6}, + {"-nan(1)", 0xfff8000000000001ull, 7, 7, true, true, false, 0xffc00001u, 7}, + {"nan(abcdefABCDEF)", 0x7ff8abcdefabcdefull, 17, 17, true, true, false, 0x7fcdefabu, 17}, + {"nan(0xABCDEF)", 0x7ff8000000abcdefull, 13, 13, true, true, false, 0x7febcdefu, 13}, + {"nan(9)", 0x7ff8000000000009ull, 6, 6, true, true, false, 0x7fc00009u, 6}, + {"nan(1_2)", 0x7ff8000000000000ull, 8, 8, true, true, false, 0x7fc00000u, 8}, + {"nan(_1)", 0x7ff8000000000000ull, 7, 7, true, true, false, 0x7fc00000u, 7}, + {"nan(1)(2)", 0x7ff8000000000001ull, 6, 6, true, true, false, 0x7fc00001u, 6}, + {"+nan", 0x7ff8000000000000ull, 4, 4, true, true, true, 0x7fc00000u, 4}, + {"NaN", 0x7ff8000000000000ull, 3, 3, true, true, true, 0x7fc00000u, 3}, + {"-NAN(5)", 0xfff8000000000005ull, 7, 7, true, true, true, 0xffc00005u, 7}, + {"nan(5)", 0x7ff8000000000005ull, 6, 6, true, true, true, 0x7fc00005u, 6}, + {"-nan(abc)", 0xfff8000000000abcull, 9, 9, true, true, true, 0xffc00abcu, 9}, + {" -nan", 0xfff8000000000000ull, 6, 6, true, true, true, 0xffc00000u, 6}, + {"nan(12 x", 0x7ff8000000000000ull, 3, 3, true, true, true, 0x7fc00000u, 3}, + {"nan(12x", 0x7ff8000000000000ull, 3, 3, true, true, true, 0x7fc00000u, 3}, + {"nan(1", 0x7ff8000000000001ull, 3, 3, true, true, true, 0x7fc00001u, 3}, + {"nan(a b)", 0x7ff8000a0000000bull, 8, 3, true, true, true, 0x7fc0000au, 8}, + {"nan(ab)", 0x7ff80000000000abull, 7, 7, true, true, true, 0x7fc000abu, 7}, + {"nanx", 0x7ff8000000000000ull, 3, 3, true, true, true, 0x7fc00000u, 3}, + {"-nan()", 0xfff8000000000000ull, 6, 6, true, true, true, 0xffc00000u, 6}, + {"nan(0x8000000000001)", 0x7ff8000000000001ull, 20, 20, true, true, true, 0x7fc00001u, 20}, }; bool correct = true; for (const auto& test : cases) { @@ -388,9 +390,8 @@ static void CheckNanConversions() { std::fprintf(stderr, "atof(\"%s\")\n", test.text); correct = false; } - const std::uint32_t expectedSingle = (test.bits >> 63 != 0 ? 0x80000000u : 0) | 0x7fc00000u; const auto single = std::bit_cast(strtof_nid_postfix(test.text, &end)); - if (single != expectedSingle || end != test.text + test.sequenceUsed) { + if (single != test.singleBits || end != test.text + test.singleUsed) { std::fprintf(stderr, "strtof(\"%s\") = 0x%08x\n", test.text, static_cast(single)); correct = false; } @@ -404,6 +405,11 @@ static void CheckNanConversions() { char16_t wide[64] = {}; for (std::size_t i = 0; test.text[i] != 0; ++i) wide[i] = static_cast(test.text[i]); char16_t* wideEnd = nullptr; + const auto wideSingle = std::bit_cast(wcstof_nid_postfix(wide, &wideEnd)); + if (wideSingle != test.singleBits || wideEnd != wide + test.singleUsed) { + std::fprintf(stderr, "wcstof(\"%s\") = 0x%08x\n", test.text, static_cast(wideSingle)); + correct = false; + } if (test.wideDoubleMeasured) { const auto wideValue = std::bit_cast(wcstod_nid_postfix(wide, &wideEnd)); if (wideValue != test.bits || wideEnd != wide + test.used) { @@ -412,11 +418,9 @@ static void CheckNanConversions() { } } if (test.wideOtherMeasured) { - const auto wideSingle = std::bit_cast(wcstof_nid_postfix(wide, &wideEnd)); - const bool singleOk = wideSingle == expectedSingle && wideEnd == wide + test.sequenceUsed; const long double wideExtended = wcstold_nid_postfix(wide, &wideEnd); - if (!singleOk || !ExtendedBits(wideExtended, 0xc000000000000000ull, 0x7fff) || wideEnd != wide + test.sequenceUsed) { - std::fprintf(stderr, "wcstof/wcstold(\"%s\")\n", test.text); + if (!ExtendedBits(wideExtended, 0xc000000000000000ull, 0x7fff) || wideEnd != wide + test.sequenceUsed) { + std::fprintf(stderr, "wcstold(\"%s\")\n", test.text); correct = false; } } @@ -834,6 +838,86 @@ static void CheckTanhConsoleBits() { } } +static void CheckSinhMsunBits() { + constexpr std::uint64_t cases[][2] = { + {0x0000000000000000ull, 0x0000000000000000ull}, {0x8000000000000000ull, 0x8000000000000000ull}, + {0x3e2fffffffffffffull, 0x3e2fffffffffffffull}, {0x3e30000000000000ull, 0x3e30000000000000ull}, + {0xbe30000000000000ull, 0xbe30000000000000ull}, {0x3ff0000000000000ull, 0x3ff2cd9fc44eb982ull}, + {0xbff0000000000000ull, 0xbff2cd9fc44eb982ull}, {0x3fefffffffffffffull, 0x3ff2cd9fc44eb982ull}, + {0x4035ffffffffffffull, 0x41dab5adb9c435e5ull}, {0xc035ffffffffffffull, 0xc1dab5adb9c435e5ull}, + {0x0000000000000001ull, 0x0000000000000001ull}, {0x7ff0000000000000ull, 0x7ff0000000000000ull}, + {0xfff0000000000000ull, 0xfff0000000000000ull}, + {0xbfd6a0128184d680ull, 0xbfd719794d320780ull}, {0xbfbdff8696a49900ull, 0xbfbe111cd84473d7ull}, + {0xbfd6d8bb6b4dca00ull, 0xbfd755bf29c06d04ull}, {0xbfcd8f04fd096d80ull, 0xbfcdd2743062c130ull}, + {0xbfdaafb23b684980ull, 0xbfdb7764637ce2b1ull}, {0x4030fc47aee85be4ull, 0x4166b421a358e13dull}, + {0x3ff0ed5ed6534a20ull, 0x3ff444323b77d92aull}, {0x402e263f514d91a0ull, 0x413ae005c1e986f0ull}, + {0x402dc690717af4e8ull, 0x41364b45352a81bdull}, {0x403270a47f0a53e2ull, 0x41884ea3c3c84121ull}, + {0xc012535983b8e876ull, 0xc04868fc3820124dull}, {0xc0304bcc37b4b009ull, 0xc156ca0b8a37acd3ull}, + {0xc01a5966b21f1396ull, 0xc076ae9222cc12f7ull}, {0xc02ddcff626eb852ull, 0xc1374ae500e7533full}, + {0xc020ed9a3019cd6eull, 0xc0a2854d2aab8a5full}, + {0x4035dab76b89b207ull, 0x41d716f7bb9c87dbull}, {0x4035d187a12d7d23ull, 0x41d6469c55754089ull}, + {0x403527ff77fdabb1ull, 0x41c6f9a671b8ec60ull}, {0x4035deab000d3561ull, 0x41d772ea071259caull}, + {0xc035232f2cb69934ull, 0xc1c68c16c49344beull}, + }; + for (const auto& [input, expected] : cases) { + const std::uint64_t result = std::bit_cast(sinh_nid_postfix(std::bit_cast(input))); + if (result != expected) { + std::fprintf(stderr, "Guest sinh failed for %016llx: got %016llx, expected %016llx\n", static_cast(input), static_cast(result), static_cast(expected)); + std::abort(); + } + } +} + +static bool SinhWithinUlps(std::uint64_t result, std::uint64_t expected, std::uint64_t ulps) { + return ((result ^ expected) >> 63) == 0 && (result > expected ? result - expected : expected - result) <= ulps; +} + +static void CheckSinhLargeArguments() { + constexpr std::uint64_t cases[][2] = { + {0x4036000000000000ull, 0x41dab5adb9c43600ull}, {0x4036000000000001ull, 0x41dab5adb9c4361aull}, + {0x4059000000000000ull, 0x48e3494a9b171bf5ull}, {0x4062c00000000000ull, 0x4d652cac29822593ull}, + {0x40862e41ffffffffull, 0x7fdffc045692fc9eull}, {0x40862e4200000000ull, 0x7fdffc045693009eull}, + {0x40862e4200000001ull, 0x7fdffc045693049eull}, {0x40862e42ffffffffull, 0x7fe000020b8c410bull}, + {0x4086330000000000ull, 0x7fecee0e3df7d1f3ull}, + {0x408633cdffffffffull, 0x7feffdc12c49df1cull}, {0x408633ce00000000ull, 0x7feffdc12c49e31cull}, + {0x408633ce8fb9e87dull, 0x7fefffffffbffd3cull}, {0x408633ce8fba087dull, 0x7ff0000000000000ull}, + {0x408633ceffffffffull, 0x7ff0000000000000ull}, {0x408633cf00000000ull, 0x7ff0000000000000ull}, + {0x7fefffffffffffffull, 0x7ff0000000000000ull}, + }; + for (const auto& [input, expected] : cases) { + for (const std::uint64_t sign : {std::uint64_t{0}, std::uint64_t{1} << 63}) { + const std::uint64_t result = std::bit_cast(sinh_nid_postfix(std::bit_cast(input | sign))); + const bool overflow = expected == 0x7ff0000000000000ull; + if (overflow ? result != (expected | sign) : !SinhWithinUlps(result, expected | sign, 4)) { + std::fprintf(stderr, "Guest sinh failed for %016llx: got %016llx, expected %016llx\n", static_cast(input | sign), static_cast(result), static_cast(expected | sign)); + std::abort(); + } + } + } +} + +static void CheckSinhOverflowErrno() { + constexpr int sentinel = 1234; + for (const std::uint64_t sign : {std::uint64_t{0}, std::uint64_t{1} << 63}) { + for (const std::uint64_t input : {0x408633cf00000000ull, 0x7fefffffffffffffull}) { + errno = sentinel; + const std::uint64_t result = std::bit_cast(sinh_nid_postfix(std::bit_cast(input | sign))); + if (result != (0x7ff0000000000000ull | sign) || errno != ERANGE) { + std::fprintf(stderr, "Guest sinh overflow failed for %016llx: got %016llx, errno %d\n", static_cast(input | sign), static_cast(result), errno); + std::abort(); + } + } + for (const std::uint64_t input : {0x408633ce8fb9e87dull, 0x7ff0000000000000ull, 0x3ff0000000000000ull, 0ull}) { + errno = sentinel; + sinh_nid_postfix(std::bit_cast(input | sign)); + if (errno != sentinel) { + std::fprintf(stderr, "Guest sinh changed errno for %016llx: errno %d\n", static_cast(input | sign), errno); + std::abort(); + } + } + } +} + static void CheckRoundingNaNConsoleBits() { constexpr std::uint64_t cases[][2] = { {0x7ff0000000000001ull, 0x7ff8000000000001ull}, {0xfff4000000000123ull, 0xfffc000000000123ull}, {0x7ff8000000000456ull, 0x7ff8000000000456ull}, @@ -917,6 +1001,44 @@ static void CheckNextafterEnvironment(T (APS5_VABI *next)(T, T), UInt sign, UInt _mm_setcsr(savedControl); } +static void CheckExpm1ScalingRounding() { + const RestoreFloatingPointState saved; + const unsigned savedCsr = _mm_getcsr(); + const struct { + std::uint64_t input; + std::uint64_t result[4]; + } cases[] = { + {0x407ff33f4d52628cull, {0x6e06c8c735268691ull, 0x6e06c8c73526868full, 0x6e06c8c735268691ull, 0x6e06c8c735268690ull}}, + {0x407eff5467ccdb38ull, {0x6ca6e270fe0a802eull, 0x6ca6e270fe0a802cull, 0x6ca6e270fe0a802eull, 0x6ca6e270fe0a802cull}}, + }; + const int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + double (APS5_VABI * volatile call)(double) = expm1_nid_postfix; + for (unsigned mode = 0; mode != 4; ++mode) { + for (unsigned denorm = 0; denorm != 4; ++denorm) { + const unsigned control = 0x1f80u | (mode << 13u) | ((denorm & 1u) ? 0x40u : 0u) | ((denorm & 2u) ? 0x8000u : 0u); + for (std::uint16_t sticky : {0u, 5u, 32u}) { + for (const auto& item : cases) { + Require(std::fesetenv(FE_DFL_ENV) == 0 && std::fesetround(rounding[mode]) == 0); + Require(fesetexceptflag_nid_postfix(&sticky, 0x3f) == 0); + _mm_setcsr(control | sticky); + GuestFloatingPointEnvironment before; + Require(fegetenv_nid_postfix(&before) == 0 && (before.status & 0x3fu) == sticky); + errno = 1234; + const auto result = std::bit_cast(call(std::bit_cast(item.input))); + const int error = errno; + GuestFloatingPointEnvironment after; + Require(fegetenv_nid_postfix(&after) == 0); + if (result != item.result[mode] || after.mxcsr != (control | sticky | 0x20u) || (after.status & 0x3fu) != sticky || after.control != before.control || error != 1234) { + std::fprintf(stderr, "expm1 scaling: input=%016llx mode=%u denorm=%u sticky=%u bits=%016llx csr=%x x87=%x errno=%d\n", static_cast(item.input), mode, denorm, sticky, static_cast(result), after.mxcsr, after.status, error); + Require(false); + } + } + } + } + } + _mm_setcsr(savedCsr); +} + static void CheckAsinl() { const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = { {0, 0x0000, 0, 0x0000}, {0, 0x8000, 0, 0x8000}, @@ -1923,10 +2045,12 @@ static void CheckNoConversionZero() { Require(parsedFloat == 0.f && std::signbit(parsedFloat) && end == text); Require(!std::signbit(strtold_nid_postfix(text, nullptr))); } - for (const char* text : {"-nan", "-NAN", " -nan(5)"}) { + struct SignedNan { const char* text; std::uint32_t bits; }; + for (const SignedNan& signedNan : {SignedNan{"-nan", 0xffc00000u}, SignedNan{"-NAN", 0xffc00000u}, SignedNan{" -nan(5)", 0xffc00005u}}) { + const char* const text = signedNan.text; char* end = nullptr; const float parsedFloat = strtof_nid_postfix(text, &end); - Require(std::bit_cast(parsedFloat) == 0xffc00000u && end == text + std::strlen(text)); + Require(std::bit_cast(parsedFloat) == signedNan.bits && end == text + std::strlen(text)); } Require(std::bit_cast(strtof_nid_postfix("nan", nullptr)) == 0x7fc00000u); char* end = nullptr; @@ -1984,6 +2108,28 @@ static void CheckDinkumwareFloatCharacteristics() { for (std::size_t index = 24; index < 32; ++index) Require(reinterpret_cast(table)[index] == 0); } +static void CheckCbrtConsoleBits() { + constexpr std::uint64_t cases[][2] = { + {0x0000000000000000ull, 0x0000000000000000ull}, {0x8000000000000000ull, 0x8000000000000000ull}, + {0x0000000000000001ull, 0x2990000000000000ull}, {0x8000000000000001ull, 0xa990000000000000ull}, + {0x000fffffffffffffull, 0x2aa428a2f98d728aull}, {0x0010000000000000ull, 0x2aa428a2f98d728bull}, + {0x3ff0000000000000ull, 0x3ff0000000000000ull}, {0xbff0000000000000ull, 0xbff0000000000000ull}, + {0x4020000000000000ull, 0x4000000000000000ull}, {0xc03b000000000000ull, 0xc008000000000000ull}, + {0x408f400000000000ull, 0x4024000000000000ull}, {0x4000000000000000ull, 0x3ff428a2f98d728bull}, + {0x3fe0000000000000ull, 0x3fe965fea53d6e3dull}, {0x7fefffffffffffffull, 0x554428a2f98d728bull}, + {0xffefffffffffffffull, 0xd54428a2f98d728bull}, {0x7ff0000000000000ull, 0x7ff0000000000000ull}, + {0xfff0000000000000ull, 0xfff0000000000000ull}, {0x7ff8000000000001ull, 0x7ff8000000000001ull}, + {0xfff8000000000002ull, 0xfff8000000000002ull}, {0x3ffa0ccfe147c94dull, 0x3ff2d29f09b6d6a3ull}, + {0xbf840fe48e07e54dull, 0xbfcb634d0e8f50ffull}, {0x3fa7d31e3ec31ee2ull, 0x3fd70508d1167185ull}, + {0xc050f6b81592ae3full, 0xc01050a4409443faull}, {0xc0506b7f30c60f3cull, 0xc0102385f7679661ull}, + {0xc039809ec6db32bcull, 0xc0078c3d13f2e055ull}, + }; + for (const auto& [input, expected] : cases) { + errno = 1234; + Require(std::bit_cast(cbrt_nid_postfix(std::bit_cast(input))) == expected && errno == 1234); + } +} + static void CheckLogbConsoleBits() { constexpr struct { std::uint64_t input; @@ -2080,15 +2226,23 @@ int main() { Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f); Require(tanf_nid_postfix(0.f) == 0.f); unsigned int randomState = 1; - Require(rand_r_nid_postfix(&randomState) == 33613 && randomState == 33613u); + Require(rand_r_nid_postfix(&randomState) == 16807 && randomState == 16807u); + Require(rand_r_nid_postfix(&randomState) == 282475249 && rand_r_nid_postfix(&randomState) == 1622650073); randomState = 0; - Require(rand_r_nid_postfix(&randomState) == 16806 && randomState == 16806u); - for (const unsigned int seed : {0u, 1u, 12345u, 0x7ffffffeu, 0xffffffffu}) { - srand_nid_postfix(seed); - - randomState = seed; - for (int step = 0; step < 8; ++step) Require(rand_r_nid_postfix(&randomState) == rand_nid_postfix()); - } + Require(rand_r_nid_postfix(&randomState) == 0x2ae1b767 && randomState == 0x2ae1b767u); + randomState = 0x7fffffffu; + Require(rand_r_nid_postfix(&randomState) == 0 && randomState == 0u); + randomState = 0x7ffffffeu; + Require(rand_r_nid_postfix(&randomState) == 2147466840 && randomState == 2147466840u); + randomState = 0xffffffffu; + Require(rand_r_nid_postfix(&randomState) == 2147466840 && randomState == 2147466840u); + srand_nid_postfix(1); + Require(rand_nid_postfix() == 408024109 && rand_nid_postfix() == 11635919 && rand_nid_postfix() == 196474438); + srand_nid_postfix(0); + Require(rand_nid_postfix() == 0 && rand_nid_postfix() == 408024109); + srand_nid_postfix(0xffffffffu); + Require(rand_nid_postfix() == 876841727 && rand_nid_postfix() == 830605684); + for (int step = 0; step < 1000; ++step) Require(static_cast(rand_nid_postfix()) <= 0x3fffffffu); bool threw = false; try { rand_r_nid_postfix(nullptr); } catch (...) { threw = true; } Require(threw); @@ -2202,14 +2356,19 @@ int main() { CheckFegetenv(); CheckLongDouble(); CheckNearbyintl(); + CheckCbrtConsoleBits(); CheckLogbConsoleBits(); CheckExpm1ConsoleBits(); CheckLog1pConsoleBits(); CheckNextafterConsoleBits(); CheckTanhConsoleBits(); + CheckSinhMsunBits(); + CheckSinhLargeArguments(); + CheckSinhOverflowErrno(); CheckRoundingNaNConsoleBits(); CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); + CheckExpm1ScalingRounding(); CheckAsinl(); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); diff --git a/core/libs/tests/GuestMathExact.cpp b/core/libs/tests/GuestMathExact.cpp index 5db64f610d..35d1e587db 100644 --- a/core/libs/tests/GuestMathExact.cpp +++ b/core/libs/tests/GuestMathExact.cpp @@ -130,6 +130,10 @@ static void CheckRoundingModes() { static void CheckClassification() { const long double pseudoDenormal = Extended(0x0000, 0x8000000000000000ull); Require(_LDtest_nid_postfix(&pseudoDenormal) == -1 && __fpclassifyl_nid_postfix(pseudoDenormal) == -2); + for (const long double unnormal : {Extended(0x3fff, 0x4000000000000000ull), Extended(0x8001, 0), Extended(0x7ffe, 0x7fffffffffffffffull)}) { + Require(_LDtest_nid_postfix(&unnormal) == -2 && _LDclass_nid_postfix(unnormal) == -2); + Require(__fpclassifyl_nid_postfix(unnormal) == -1 && __isnormall_nid_postfix(unnormal) == 1); + } 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); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index e427d03525..ebe541e160 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -184,10 +184,8 @@ static void CheckAudioCoprocessorProtection() { Require(sceKernelMprotect(writable, length, 0x3f2) == 0); static_cast(writable)[0] = 9; Require(sceKernelMunmap(reinterpret_cast(writable), length) == 0); - bool rejected = false; void* undefined = nullptr; - try { sceKernelMapFlexibleMemory(&undefined, length, 0x400, 0); } catch (const std::invalid_argument&) { rejected = true; } - Require(rejected && undefined == nullptr); + Require(sceKernelMapFlexibleMemory(&undefined, length, 0x400, 0) == SCE_KERNEL_ERROR_EINVAL && undefined == nullptr); } static void CheckMprotectIgnoresUndefinedBits() { @@ -268,14 +266,15 @@ static void CheckStrictMemoryProtectionBits() { }; std::size_t available = 0; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0); - for (const int protection : {-256, -129, -128, -14, -1, 8, 0x400, std::numeric_limits::min()}) { + for (const int protection : {-256, -129, -128, -14, -1, 8, 0x400, 0x800, 0x3ff, std::numeric_limits::min()}) { void* invalid = nullptr; - rejected([&] { sceKernelMapFlexibleMemory(&invalid, length, protection, 0); }); - Require(invalid == nullptr); + Require(sceKernelMapFlexibleMemory(&invalid, length, protection, 0) == SCE_KERNEL_ERROR_EINVAL && invalid == nullptr); + Require(sceKernelMapNamedFlexibleMemory(&invalid, length, protection, 0, "undefined") == SCE_KERNEL_ERROR_EINVAL && invalid == nullptr); std::size_t after = 0; Require(sceKernelAvailableFlexibleMemorySize(&after) == 0 && after == available); - rejected([&] { sceKernelMapDirectMemory(&invalid, length, protection, 0, physical, 0); }); - Require(invalid == nullptr); + Require(sceKernelMapDirectMemory(&invalid, length, protection, 0, physical, 0) == SCE_KERNEL_ERROR_EINVAL && invalid == nullptr); + Require(sceKernelMapDirectMemory2(&invalid, length, 0, protection, 0, physical, 0) == SCE_KERNEL_ERROR_EINVAL && invalid == nullptr); + Require(sceKernelMapNamedDirectMemory(&invalid, length, protection, 0, physical, 0, "undefined") == SCE_KERNEL_ERROR_EINVAL && invalid == nullptr); rejected([&] { sceKernelMtypeprotect(mapping, length, 2, protection); }); VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(mapping, 0, &info, sizeof(info)) == 0); @@ -425,6 +424,27 @@ static void CheckBatchMapStopsAtInvalidEntry() { Require(sceKernelMunmap(reinterpret_cast(entries[0].start), page) == 0); } +static void CheckBatchMapPlacesAtTheGivenStart() { + constexpr std::size_t page = 0x4000; + constexpr int mapFlexible = 3; + constexpr int unmap = 1; + void* reserved = nullptr; + Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, page) == 0 && reserved != nullptr); + auto* base = static_cast(reserved); + KernelBatchMapEntry entries[2] = {{base, 0, page, 3, 0, 0, mapFlexible}, {base + page, 0, page, 3, 0, 0, mapFlexible}}; + int processed = -1; + Require(sceKernelBatchMap(entries, 2, &processed) == 0 && processed == 2); + Require(entries[0].start == base && entries[1].start == base + page); + base[0] = 0x5a; + base[page * 2 - 1] = 0xa5; + Require(base[0] == 0x5a && base[page * 2 - 1] == 0xa5); + KernelBatchMapEntry unmaps[2] = {entries[0], entries[1]}; + unmaps[0].operation = unmap; + unmaps[1].operation = unmap; + processed = -1; + Require(sceKernelBatchMap(unmaps, 2, &processed) == 0 && processed == 2); +} + static void CheckVirtualQueryFindsNeighbours() { constexpr std::size_t page = 0x4000; void* low = nullptr; @@ -812,6 +832,42 @@ static void CheckGetDirectMemoryType() { Require(sceKernelGetDirectMemoryType(second, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT); } +static void CheckDirectMemoryQuery() { + struct QueryInfo { + std::int64_t start; + std::int64_t end; + int memoryType; + }; + constexpr std::size_t page = 0x4000; + std::int64_t first = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 5, 0, 3, &first) == 0); + Require(sceKernelReleaseDirectMemory(first + page * 2, page * 3) == 0); + std::int64_t second = 0; + Require(sceKernelAllocateDirectMemory(first + page * 4, first + page * 5, page, 0, 1, &second) == 0 && second == first + page * 4); + QueryInfo info{-1, -1, -1}; + Require(sceKernelDirectMemoryQuery(first, 0, &info, sizeof(info)) == 0); + Require(info.start == first && info.end == first + page * 2 && info.memoryType == 3); + info = {-1, -1, -1}; + Require(sceKernelDirectMemoryQuery(first + page * 2 - 1, 0, &info, sizeof(info)) == 0); + Require(info.start == first && info.end == first + page * 2 && info.memoryType == 3); + info = {-1, -1, -1}; + Require(sceKernelDirectMemoryQuery(first + page, 1, &info, sizeof(info)) == 0); + Require(info.start == first && info.end == first + page * 2 && info.memoryType == 3); + info = {-1, -1, -1}; + Require(sceKernelDirectMemoryQuery(first + page * 2, 0, &info, sizeof(info)) == SCE_KERNEL_ERROR_EACCES); + Require(info.start == -1 && info.end == -1 && info.memoryType == -1); + Require(sceKernelDirectMemoryQuery(first + page * 3, 1, &info, sizeof(info)) == 0); + Require(info.start == second && info.end == second + page && info.memoryType == 1); + info = {-1, -1, -1}; + Require(sceKernelDirectMemoryQuery(first, 0, nullptr, sizeof(info)) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelDirectMemoryQuery(-1, 1, &info, sizeof(info)) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelDirectMemoryQuery(first, 0, &info, sizeof(info) - 1) == SCE_KERNEL_ERROR_EINVAL); + Require(info.start == -1 && info.end == -1 && info.memoryType == -1); + Require(sceKernelReleaseDirectMemory(first, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(second, page) == 0); + Require(sceKernelDirectMemoryQuery(first, 0, &info, sizeof(info)) == SCE_KERNEL_ERROR_EACCES); +} + static void CheckMtypeprotect() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; @@ -3504,6 +3560,23 @@ static void CheckMunmapOverUnmappedPages() { Require(sceKernelMunmap(reinterpret_cast(memory), 0) == SCE_KERNEL_ERROR_EINVAL); } +static void CheckMemoryPoolBlockStats() { + KernelMemoryPoolBlockStats stats{-1, -1, -1, -1}; + Require(sceKernelMemoryPoolGetBlockStats(nullptr, sizeof(stats)) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelMemoryPoolGetBlockStats(&stats, sizeof(stats) - 1) == SCE_KERNEL_ERROR_EINVAL); + Require(stats.available_flushed_blocks == -1 && stats.allocated_flushed_blocks == -1); + Require(sceKernelMemoryPoolGetBlockStats(&stats, sizeof(stats)) == 0); + Require(stats.available_cached_blocks == 0 && stats.allocated_cached_blocks == 0); + Require(stats.available_flushed_blocks == 0 && stats.allocated_flushed_blocks == 0); + constexpr std::size_t block = std::size_t{2} << 20; + std::int64_t phys = -1; + Require(sceKernelMemoryPoolExpand(0, static_cast(sceKernelGetDirectMemorySize()), block, block, &phys) == 0); + Require(phys >= 0 && phys % static_cast(block) == 0); + Require(sceKernelMemoryPoolGetBlockStats(&stats, sizeof(stats)) == 0); + Require(stats.available_flushed_blocks == 1 && stats.allocated_flushed_blocks == 0); + Require(sceKernelReleaseDirectMemory(phys, block) == 0); +} + int main(int argc, char** argv) { #ifdef _WIN32 _putenv_s("APS5_PIN_WAIT_MS", "1000"); @@ -3522,6 +3595,7 @@ int main(int argc, char** argv) { CheckMappedLeasesPreserveReadableRuns(); CheckVirtualQuerySplitFlexibleRanges(); CheckVirtualQueryForNonReadableGuestRange(); + CheckMemoryPoolBlockStats(); CheckReleaseFlexibleMemory(); CheckFlexibleMappingIgnoresFlag8000(); CheckSceKernelMmap(); @@ -3536,6 +3610,7 @@ int main(int argc, char** argv) { CheckMunmapScopedFlexibleRelease(); CheckGuestModuleUsesFlexibleMemory(); CheckBatchMapStopsAtInvalidEntry(); + CheckBatchMapPlacesAtTheGivenStart(); CheckCheckedReleaseDirectMemory(); CheckReleaseSkipsFreePages(); CheckAudioCoprocessorProtection(); @@ -3565,6 +3640,7 @@ int main(int argc, char** argv) { CheckMlock(); CheckSharedDirectMemoryLifecycle(); CheckGetDirectMemoryType(); + CheckDirectMemoryQuery(); CheckMtypeprotect(); CheckHeapAlignment(); CheckHeapAfterMappingReuse(); diff --git a/core/libs/tests/GuestMlock.cpp b/core/libs/tests/GuestMlock.cpp index 43b0f46d48..1e001db318 100644 --- a/core/libs/tests/GuestMlock.cpp +++ b/core/libs/tests/GuestMlock.cpp @@ -16,6 +16,10 @@ #include #include #endif +#ifdef __APPLE__ +#include +#include +#endif static void Require(bool value) { if (!value) std::abort(); } @@ -47,6 +51,19 @@ static std::size_t LockedKilobytes() { } #endif +#ifdef __APPLE__ +static unsigned WiredCount(const void* page) { + mach_vm_address_t address = reinterpret_cast(page); + mach_vm_size_t size = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + Require(mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast(&info), &count, &object) == KERN_SUCCESS); + Require(address <= reinterpret_cast(page)); + return info.user_wired_count; +} +#endif + int main() { constexpr int untouched = 12345; constexpr std::size_t page = 0x4000; @@ -71,6 +88,14 @@ int main() { #endif Require(mlock_nid_postfix(mapped, length) == 0); Require(mlock_nid_postfix(mapped, length) == 0); +#ifdef __APPLE__ + Require(WiredCount(mapped) == 1); + void* remapped = mapped; + Require(sceKernelMapFlexibleMemory(&remapped, length, 3, 0x10) == 0 && remapped == mapped); + Require(WiredCount(mapped) == 0); + Require(mlock_nid_postfix(mapped, length) == 0); + Require(WiredCount(mapped) == 1); +#endif bytes[length - 1] = 7; *__error_nid_postfix() = untouched; @@ -85,6 +110,9 @@ int main() { *__error_nid_postfix() = untouched; Require(munlock_nid_postfix(mapped, length) == 0); Require(*__error_nid_postfix() == untouched); +#ifdef __APPLE__ + Require(WiredCount(mapped) == 0); +#endif bytes[0] = 7; Require(mlock_nid_postfix(mapped, page) == 0); #ifdef HAS_LOCKED_KILOBYTES @@ -107,11 +135,14 @@ int main() { #ifdef HAS_LOCKED_KILOBYTES Require(LockedKilobytes() - lockedBefore == page / 1024); #endif + Require(mlock_nid_postfix(mapped, page) == 0); *__error_nid_postfix() = untouched; Require(munlockall_nid_postfix() == 0); Require(*__error_nid_postfix() == untouched); #ifdef HAS_LOCKED_KILOBYTES Require(LockedKilobytes() == lockedBefore); +#elif defined(__APPLE__) + Require(WiredCount(mapped) == 0); #elif defined(_WIN32) Require(!VirtualUnlock(mapped, page) && GetLastError() == ERROR_NOT_LOCKED); #endif diff --git a/core/libs/tests/GuestNpCommerceDialog.cpp b/core/libs/tests/GuestNpCommerceDialog.cpp index 58383cb878..28a329dd02 100644 --- a/core/libs/tests/GuestNpCommerceDialog.cpp +++ b/core/libs/tests/GuestNpCommerceDialog.cpp @@ -195,7 +195,15 @@ int main() { Require(sceNpCommerceDialogUpdateStatus() == statusFinished); RequireCanceledResult(param2.userData, false); Require(sceNpCommerceDialogTerminate() == 0); + Require(sceNpCommerceDialogTerminate() == static_cast(0x80B80003u)); + Require(sceNpCommerceDialogClose() == static_cast(0x80B80003u)); + Require(sceNpCommerceDialogOpen(nullptr) == static_cast(0x80B8000Du)); + Require(sceNpCommerceDialogOpen2(nullptr) == static_cast(0x80B8000Du)); + Require(sceNpCommerceDialogOpen2(¶m2) == static_cast(0x80B80003u)); + Require(sceNpCommerceDialogGetResult(nullptr) == static_cast(0x80B8000Du)); + Require(sceNpCommerceDialogGetResult(&result) == static_cast(0x80B80003u)); Require(sceNpCommerceDialogGetStatus() == statusNone); + Require(sceNpCommerceDialogUpdateStatus() == statusNone); Require(sceUserServiceTerminate() == 0); return 0; } diff --git a/core/libs/tests/GuestNpCommonRuntime.cpp b/core/libs/tests/GuestNpCommonRuntime.cpp index 1747776629..9d646757ef 100644 --- a/core/libs/tests/GuestNpCommonRuntime.cpp +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -259,6 +259,16 @@ void TestJsonText() { Require(sceNpUtilJsonEscape(escaped, sizeof(escaped), "yz", 2) == 0 && Equals(escaped, "xyz"), "escaping appends to the buffer"); Require(sceNpUtilJsonEscape(escaped, 6, "a", 1) == NpUtilBufferTooSmall, "escaping needs room for one escape"); Require(sceNpUtilJsonEscape(escaped, sizeof(escaped), nullptr, 1) == NpUtilInvalid, "escaping needs a source"); + struct Guarded { + char buffer[16]; + char canary[16]; + } guarded{}; + std::strcpy(guarded.buffer, "0123456789"); + Require(sceNpUtilJsonEscape(guarded.buffer, sizeof(guarded.buffer), "\n\n\n", 3) == NpUtilBufferTooSmall && guarded.canary[0] == 0, + "escaping into a partly filled buffer counts the text already in it"); + std::memset(guarded.buffer, 'x', sizeof(guarded.buffer)); + Require(sceNpUtilJsonEscape(guarded.buffer, sizeof(guarded.buffer), "a", 1) == NpUtilBufferTooSmall && guarded.canary[0] == 0, + "escaping into a buffer without a terminator is refused"); char text[32] = {}; std::size_t length = 0; @@ -271,6 +281,8 @@ void TestJsonText() { "a bad unicode escape is copied and reported"); Require(sceNpUtilJsonUnescape(nullptr, sizeof(text), "abc", 3, &length) == 0 && length == 4, "a null buffer reports the size"); Require(sceNpUtilJsonUnescape(text, 3, "abc", 3, &length) == NpUtilBufferTooSmall, "unescaping needs room for the terminator"); + length = 0; + Require(sceNpUtilJsonUnescape(nullptr, 0, "abc", 3, &length) == 0 && length == 4, "a size query with no buffer and no size reports the size"); char one[4] = {}; std::size_t consumed = 0; @@ -284,6 +296,8 @@ void TestJsonText() { "a cut UTF-8 character reports its full length"); Require(sceNpUtilJsonGetOneChar(one, 1, "\xC3\xA9", 2, &length, &consumed) == NpUtilBufferTooSmall && length == 2, "a short buffer still reports the decoded size"); + Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\\ud83dXXde00", 12, &length, &consumed) == NpUtilBufferTooSmall, + "a high surrogate without a second unicode escape is not combined"); const char* character = nullptr; const char* japanese = "\xE6\x97\xA5" "a"; diff --git a/core/libs/tests/GuestNpCppWebApiOffline.cpp b/core/libs/tests/GuestNpCppWebApiOffline.cpp index 4ffa0c8e76..6fcbf72a07 100644 --- a/core/libs/tests/GuestNpCppWebApiOffline.cpp +++ b/core/libs/tests/GuestNpCppWebApiOffline.cpp @@ -20,6 +20,7 @@ static constexpr int ParameterNotInitialized = static_cast(0x80553502); static constexpr int ParameterInvalidArgument = static_cast(0x80553503); static constexpr int ParameterAlreadyInitialized = static_cast(0x8055350f); static constexpr int ParameterTerminatedUninitialized = static_cast(0x80553510); +static constexpr int ParameterBodyMissing = static_cast(0x80553512); struct ParameterLifecycle { int (APS5_VABI* construct)(void*); @@ -36,17 +37,17 @@ static void TestParameterLifecycle(const ParameterLifecycle& lifecycle, TInitial Require(lifecycle.construct(parameter) == 0, "a parameter constructor failed"); for (std::size_t index = 0; index < sizeof(parameter); ++index) Require(parameter[index] == (index < 0x10 ? 0 : 0x5a), "a parameter constructor wrote the wrong bytes"); - Require(lifecycle.setText(parameter, "text") == ParameterNotInitialized, "a setter accepted an uninitialized parameter"); + Require(lifecycle.setText == nullptr || lifecycle.setText(parameter, "text") == ParameterNotInitialized, "a setter accepted an uninitialized parameter"); Require(lifecycle.terminate(parameter) == ParameterTerminatedUninitialized, "terminate accepted an uninitialized parameter"); Require(initialize(parameter, nullptr, true) == ParameterInvalidArgument, "initialize accepted a null context"); Require(initialize(parameter, &context, true) == ParameterNotInitialized, "initialize accepted an uninitialized context"); Require(_ZN3sce2Np9CppWebApi6Common10initializeERKNS2_10InitParamsERNS2_10LibContextE(nullptr, &context) == 0, "the context did not initialize"); Require(initialize(parameter, &context, false) == ParameterInvalidArgument, "initialize accepted a null argument"); - Require(lifecycle.setText(parameter, "text") == ParameterNotInitialized, "a failed initialize left the parameter initialized"); + Require(lifecycle.terminate(parameter) == ParameterTerminatedUninitialized, "a failed initialize left the parameter initialized"); Require(initialize(parameter, &context, true) == 0, "initialize failed"); Require(initialize(parameter, &context, true) == ParameterAlreadyInitialized, "initialize ran twice"); - Require(lifecycle.setText(parameter, nullptr) == ParameterInvalidArgument, "a setter accepted a null string"); - Require(lifecycle.setText(parameter, "text") == 0, "a setter failed"); + Require(lifecycle.setText == nullptr || lifecycle.setText(parameter, nullptr) == ParameterInvalidArgument, "a setter accepted a null string"); + Require(lifecycle.setText == nullptr || lifecycle.setText(parameter, "text") == 0, "a setter failed"); Require(lifecycle.terminate(parameter) == 0, "terminate failed"); Require(lifecycle.terminate(parameter) == ParameterTerminatedUninitialized, "terminate ran twice"); Require(initialize(parameter, &context, true) == 0, "initialize after terminate failed"); @@ -94,6 +95,67 @@ static void TestParameterLifecycles() { }); } +static IntrusivePtr body{reinterpret_cast(0x1000), nullptr, nullptr}; +static IntrusivePtr missingBody{nullptr, nullptr, nullptr}; + +static void TestSessionManagerParameterLifecycles() { + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + const int result = _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE(parameter, context, present ? &body : &missingBody); + if (present || result != ParameterBodyMissing) return result; + return ParameterInvalidArgument; + }); + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + const auto initialize = _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE; + if (present) return initialize(parameter, context, "session", &body); + Require(initialize(parameter, context, "session", &missingBody) == ParameterBodyMissing, "initialize accepted a missing request body"); + return initialize(parameter, context, nullptr, &body); + }); + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + return _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_(parameter, context, "session", present ? "me" : nullptr); + }); + + std::uint64_t context = 0; + alignas(16) unsigned char parameter[0x10]; + std::memset(parameter, 0x5a, sizeof(parameter)); + const auto initialize = _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc; + const auto setFilter = _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc; + Require(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev(parameter) == 0, "the invitations parameter constructor failed"); + for (const auto byte : parameter) Require(byte == 0, "the invitations parameter constructor left bytes"); + Require(setFilter(parameter, "filter") == ParameterNotInitialized, "the filter setter accepted an uninitialized parameter"); + Require(initialize(parameter, nullptr, "me") == ParameterInvalidArgument, "the invitations initialize accepted a null context"); + Require(initialize(parameter, &context, "me") == ParameterNotInitialized, "the invitations initialize accepted an uninitialized context"); + Require(_ZN3sce2Np9CppWebApi6Common10initializeERKNS2_10InitParamsERNS2_10LibContextE(nullptr, &context) == 0, "the context did not initialize"); + Require(initialize(parameter, &context, nullptr) == ParameterInvalidArgument, "the invitations initialize accepted a null account"); + Require(setFilter(parameter, "filter") == ParameterNotInitialized, "a failed invitations initialize left the parameter initialized"); + Require(initialize(parameter, &context, "me") == 0, "the invitations initialize failed"); + Require(initialize(parameter, &context, "me") == ParameterAlreadyInitialized, "the invitations initialize ran twice"); + Require(setFilter(parameter, nullptr) == ParameterInvalidArgument, "the filter setter accepted a null string"); + Require(setFilter(parameter, "filter") == 0, "the filter setter failed"); + Require(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev(parameter) == 0, "the invitations parameter destructor failed"); + Require(_ZN3sce2Np9CppWebApi6Common9terminateERNS2_10LibContextE(&context) == 0, "the context did not terminate"); +} + static void TestIteratorDestructors() { unsigned char iterator[0x20]; std::memset(iterator, 0x5a, sizeof(iterator)); @@ -154,6 +216,7 @@ static void TestVectorSizeAndElements() { int main() { TestParameterLifecycles(); + TestSessionManagerParameterLifecycles(); TestIteratorDestructors(); TestVectorSizeAndElements(); std::puts("NpCppWebApi offline tests passed"); diff --git a/core/libs/tests/GuestNpMatching2Offline.cpp b/core/libs/tests/GuestNpMatching2Offline.cpp index 796c217bdf..16bc6848a9 100644 --- a/core/libs/tests/GuestNpMatching2Offline.cpp +++ b/core/libs/tests/GuestNpMatching2Offline.cpp @@ -2,6 +2,8 @@ #include "SceTypes.hpp" #include #include +#include +#include @@ -10,11 +12,16 @@ namespace { constexpr int ResolverTimedOut = static_cast(0x804101E2u); +constexpr int AlreadyInitialized = static_cast(0x80550C02u); constexpr int NotInitialized = static_cast(0x80550C03u); constexpr int ContextMax = static_cast(0x80550C04u); constexpr int ContextNotStarted = static_cast(0x80550C08u); constexpr int InvalidArgument = static_cast(0x80550C0Au); constexpr int InvalidContextId = static_cast(0x80550C0Bu); +constexpr std::int32_t ConnStatusInactive = 0; +constexpr std::int32_t StatusSentinel = 0x5A5A5A5A; +constexpr std::uint32_t PeerAddrSentinel = 0xA5A5A5A5u; +constexpr std::uint16_t PeerPortSentinel = 0xA5A5; int failures = 0; @@ -24,6 +31,15 @@ void Require(bool value, const char* name, const char* what) { ++failures; } +bool NullStatusThrows(std::uint16_t ctxId) { + try { + sceNpMatching2SignalingGetConnectionStatus(ctxId, 1, 1, nullptr, nullptr, nullptr); + } catch (const std::runtime_error& error) { + return std::string(error.what()).find("not implemented") != std::string::npos; + } + return false; +} + } int main() { @@ -37,14 +53,23 @@ int main() { Require(sceNpMatching2DestroyContext(1) == NotInitialized, "sceNpMatching2DestroyContext", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2ContextStart(1, 0) == NotInitialized, "sceNpMatching2ContextStart", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2ContextStop(1) == NotInitialized, "sceNpMatching2ContextStop", "a call before initialization did not return NOT_INITIALIZED"); + Require(sceNpMatching2AbortContextStart(1) == NotInitialized, "sceNpMatching2AbortContextStart", "a call before initialization did not return NOT_INITIALIZED"); + std::int32_t status = StatusSentinel; + Require(sceNpMatching2SignalingGetConnectionStatus(1, 1, 1, &status, nullptr, nullptr) == NotInitialized && status == StatusSentinel, "sceNpMatching2SignalingGetConnectionStatus", "a call before initialization did not return NOT_INITIALIZED without writing the status"); + Require(sceNpMatching2SignalingGetConnectionStatus(1, 1, 1, nullptr, nullptr, nullptr) == NotInitialized, "sceNpMatching2SignalingGetConnectionStatus", "a null status before initialization did not return NOT_INITIALIZED first"); Require(sceNpMatching2RegisterContextCallback(nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterContextCallback", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2RegisterRoomEventCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterRoomEventCallback", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2RegisterRoomMessageCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterRoomMessageCallback", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2RegisterSignalingCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterSignalingCallback", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2SetDefaultRequestOptParam(1, nullptr) == NotInitialized, "sceNpMatching2SetDefaultRequestOptParam", "a call before initialization did not return NOT_INITIALIZED first"); + Require(sceNpMatching2Terminate() == NotInitialized, "sceNpMatching2Terminate", "terminating before initialization did not return NOT_INITIALIZED"); + Require(sceNpMatching2Initialize(nullptr) == InvalidArgument, "sceNpMatching2Initialize", "a null parameter did not return INVALID_ARGUMENT"); + init.size = 0x10; + Require(sceNpMatching2Initialize(&init) == InvalidArgument, "sceNpMatching2Initialize", "an invalid parameter size did not return INVALID_ARGUMENT"); init.size = 0x30; Require(sceNpMatching2Initialize(&init) == 0, "sceNpMatching2Initialize", "initialization failed"); + Require(sceNpMatching2Initialize(&init) == AlreadyInitialized, "sceNpMatching2Initialize", "initializing when already initialized did not return ALREADY_INITIALIZED"); Require(sceNpMatching2CreateContext(nullptr, &context) == InvalidArgument, "sceNpMatching2CreateContext", "a null parameter was taken"); create.size = 0x30; @@ -68,6 +93,25 @@ int main() { Require(sceNpMatching2ContextStop(1) == ContextNotStarted, "sceNpMatching2ContextStop", "a context that never started did not return CONTEXT_NOT_STARTED"); Require(sceNpMatching2ContextStop(99) == InvalidContextId, "sceNpMatching2ContextStop", "an unknown context was taken"); + Require(sceNpMatching2AbortContextStart(1) == 0, "sceNpMatching2AbortContextStart", "aborting the start of a context failed"); + Require(sceNpMatching2AbortContextStart(99) == 0, "sceNpMatching2AbortContextStart", "an unknown context id was refused"); + Require(sceNpMatching2ContextStop(1) == ContextNotStarted, "sceNpMatching2AbortContextStart", "the abort changed the start state of the context"); + + std::uint32_t peerAddr = PeerAddrSentinel; + std::uint16_t peerPort = PeerPortSentinel; + status = StatusSentinel; + Require(sceNpMatching2SignalingGetConnectionStatus(1, 0x1122334455667788ull, 3, &status, &peerAddr, &peerPort) == 0 && status == ConnStatusInactive, "sceNpMatching2SignalingGetConnectionStatus", "a member of a room that was never joined is not reported inactive"); + Require(peerAddr == PeerAddrSentinel && peerPort == PeerPortSentinel, "sceNpMatching2SignalingGetConnectionStatus", "the peer address or port was written for an inactive member"); + status = StatusSentinel; + Require(sceNpMatching2SignalingGetConnectionStatus(1, 0, 0, &status, nullptr, nullptr) == 0 && status == ConnStatusInactive, "sceNpMatching2SignalingGetConnectionStatus", "null peer address and port outputs were refused"); + status = StatusSentinel; + Require(sceNpMatching2SignalingGetConnectionStatus(99, 1, 1, &status, &peerAddr, &peerPort) == InvalidContextId && status == StatusSentinel, "sceNpMatching2SignalingGetConnectionStatus", "an unknown context was taken or its status written"); + Require(NullStatusThrows(1), "sceNpMatching2SignalingGetConnectionStatus", "a null status for a known context did not throw"); + Require(NullStatusThrows(99), "sceNpMatching2SignalingGetConnectionStatus", "a null status for an unknown context did not throw before the context check"); + Require(sceNpMatching2DestroyContext(2) == 0, "sceNpMatching2DestroyContext", "destroying context 2 failed"); + status = StatusSentinel; + Require(sceNpMatching2SignalingGetConnectionStatus(2, 1, 1, &status, nullptr, nullptr) == InvalidContextId && status == StatusSentinel, "sceNpMatching2SignalingGetConnectionStatus", "a destroyed context was taken"); + Require(sceNpMatching2RegisterContextCallback(nullptr, nullptr) == 0, "sceNpMatching2RegisterContextCallback", "registering the context callback failed"); Require(sceNpMatching2RegisterRoomEventCallback(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterRoomEventCallback", "an unknown context was taken"); Require(sceNpMatching2RegisterRoomEventCallback(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterRoomEventCallback", "registering failed"); @@ -80,11 +124,16 @@ int main() { Require(sceNpMatching2SetDefaultRequestOptParam(1, requestOpt) == 0, "sceNpMatching2SetDefaultRequestOptParam", "setting the default option failed"); Require(sceNpMatching2Terminate() == 0, "sceNpMatching2Terminate", "termination failed"); + Require(sceNpMatching2Terminate() == NotInitialized, "sceNpMatching2Terminate", "terminating after termination did not return NOT_INITIALIZED"); Require(sceNpMatching2DestroyContext(1) == NotInitialized, "sceNpMatching2DestroyContext", "a context was destroyed after termination"); + Require(sceNpMatching2AbortContextStart(1) == NotInitialized, "sceNpMatching2AbortContextStart", "a call after termination did not return NOT_INITIALIZED"); + status = StatusSentinel; + Require(sceNpMatching2SignalingGetConnectionStatus(1, 1, 1, &status, nullptr, nullptr) == NotInitialized && status == StatusSentinel, "sceNpMatching2SignalingGetConnectionStatus", "a call after termination did not return NOT_INITIALIZED without writing the status"); Require(sceNpMatching2Initialize(&init) == 0, "sceNpMatching2Initialize", "initializing again failed"); Require(sceNpMatching2RegisterRoomEventCallback(5, nullptr, nullptr) == InvalidContextId, "sceNpMatching2Terminate", "a context created before termination is still found"); Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == 1, "sceNpMatching2Terminate", "termination did not restart the context ids at 1"); Require(sceNpMatching2Terminate() == 0, "sceNpMatching2Terminate", "the second termination failed"); + Require(sceNpMatching2Terminate() == NotInitialized, "sceNpMatching2Terminate", "the third termination did not return NOT_INITIALIZED"); if (failures != 0) return 1; std::puts("NpMatching2 offline tests passed"); diff --git a/core/libs/tests/GuestNpReachability.cpp b/core/libs/tests/GuestNpReachability.cpp index 1c16e32fe7..b2a400757a 100644 --- a/core/libs/tests/GuestNpReachability.cpp +++ b/core/libs/tests/GuestNpReachability.cpp @@ -6,6 +6,7 @@ static constexpr int ErrorArgument = static_cast(0x80550003); static constexpr int ErrorAlreadyRegistered = static_cast(0x80550008); static constexpr int ErrorNotRegistered = static_cast(0x80550009); static constexpr int ErrorUserNotFound = static_cast(0x80550007); +static constexpr int ErrorInvalidId = static_cast(0x80550015); static void Require(bool value) { if (!value) std::abort(); } static void APS5_VABI Callback(int, uint32_t, void*) { std::abort(); } @@ -19,13 +20,31 @@ int main() { Require(sceNpRegisterNpReachabilityStateCallback(nullptr, nullptr) == ErrorArgument); uint32_t state = 99; Require(sceNpGetNpReachabilityState(0, &state) == 0 && state == 0); + Require(sceNpGetNpReachabilityState(0, nullptr) == ErrorArgument); Require(sceNpCheckCallback() == 0); Require(sceNpUnregisterNpReachabilityStateCallback() == 0); Require(sceNpUnregisterNpReachabilityStateCallback() == ErrorNotRegistered); Require(sceNpRegisterNpReachabilityStateCallback(callback, nullptr) == 0); Require(sceNpUnregisterNpReachabilityStateCallback() == 0); + Require(sceNpRegisterPlusEventCallback(nullptr, nullptr) == ErrorArgument); + Require(sceNpRegisterPremiumEventCallback(nullptr, nullptr) == ErrorArgument); + Require(sceNpRegisterStateCallback(nullptr, nullptr) == ErrorArgument); + Require(sceNpUnregisterStateCallback() == ErrorNotRegistered); + Require(sceNpRegisterStateCallbackA(nullptr, nullptr) == ErrorArgument); + Require(sceNpUnregisterStateCallbackA(0) == ErrorInvalidId); + Require(sceNpUnregisterStateCallbackA(99) == ErrorInvalidId); + int targetUser = 0; + Require(sceNpGetUserIdByAccountId(0, &targetUser) == ErrorArgument); + Require(sceNpGetUserIdByAccountId(12345, nullptr) == ErrorArgument); + bool isPlus = true; + Require(sceNpIsPlusMember(0, nullptr) == ErrorArgument); + Require(sceNpIsPlusMember(0, &isPlus) == 0 && !isPlus); NpOnlineId onlineId{}; onlineId.data[0] = 'p'; + uint32_t presence = 99; + Require(sceNpGetGamePresenceStatus(nullptr, &presence) == ErrorArgument); + Require(sceNpGetGamePresenceStatus(&onlineId, nullptr) == ErrorArgument); + Require(sceNpGetGamePresenceStatus(&onlineId, &presence) == 0 && presence == 0); Require(sceNpCheckNpAvailability(1, &onlineId, nullptr) == ErrorUserNotFound); Require(sceNpCheckNpAvailability(1, nullptr, nullptr) == ErrorArgument); std::uint8_t available[3]{0x55, 0x55, 0x55}; @@ -35,5 +54,5 @@ int main() { Require(available[1] == 0x55); Require(sceNpCheckNpAvailability(1, &onlineId, &available[1]) == ErrorUserNotFound); Require(available[0] == 0x55 && available[1] == 0 && available[2] == 0x55); - + return 0; } diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp index 1cd76c46ca..d69593f7e5 100644 --- a/core/libs/tests/GuestNpSessionSignaling.cpp +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -145,12 +145,16 @@ int main() { Require(sceNpSessionSignalingTerminate() == 0, "terminate failed"); Require(sceNpSessionSignalingDestroyContext(contextId) == NotInitialized, "destroy after terminate"); Require(sceNpSessionSignalingActivateUser(contextId, nullptr, nullptr) == NotInitialized, "activate user after terminate"); + Require(sceNpSessionSignalingDeactivate(contextId, 1) == NotInitialized, "deactivate after terminate"); Require(sceNpSessionSignalingGetConnectionInfo(contextId, 1, 1, nullptr) == NotInitialized, "connection info after terminate"); + Require(sceNpSessionSignalingGetConnectionStatus(contextId, 1, nullptr, nullptr, nullptr) == NotInitialized, "connection status after terminate"); + Require(sceNpSessionSignalingGetLocalNetInfo(contextId, nullptr) == NotInitialized, "local net info after terminate"); Require(sceNpSessionSignalingGetGroupInfo(netContextId, 1, nullptr) == NotInitialized, "group info after terminate"); Require(sceNpSessionSignalingGetGroupFromSessionId(netContextId, nullptr, nullptr) == NotInitialized, "group from session id after terminate"); Require(sceNpSessionSignalingGetConnectionFromNetAddress(netContextId, 0, 0, nullptr) == NotInitialized, "connection from net address after terminate"); Require(sceNpSessionSignalingGetConnectionFromNetAddress2(netContextId, 1, 0, 0, nullptr) == NotInitialized, "connection from group net address after terminate"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress(netContextId, nullptr, nullptr) == NotInitialized, "connection from peer address after terminate"); + Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, nullptr, nullptr) == NotInitialized, "connection from peer address 2 after terminate"); Require(sceNpSessionSignalingActivateSession(contextId, nullptr, 0, nullptr, nullptr) == NotInitialized, "activate after terminate"); Require(sceNpSessionSignalingTerminate() == NotInitialized, "repeated terminate"); Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == 0, "reinitialize"); diff --git a/core/libs/tests/GuestNpTrophy.cpp b/core/libs/tests/GuestNpTrophy.cpp index 768dd77112..2564fbc361 100644 --- a/core/libs/tests/GuestNpTrophy.cpp +++ b/core/libs/tests/GuestNpTrophy.cpp @@ -97,6 +97,18 @@ int main() { } Require(saved == 1, "unlock state saved for the user"); + std::filesystem::create_directories(root / "_trophy/trophy00.1.tmp"); + bool saveFailed = false; + try { + sceNpTrophyUnlockTrophy(context, handle, 0, &platinum); + } catch (const std::exception&) { + saveFailed = true; + } + std::filesystem::remove_all(root / "_trophy/trophy00.1.tmp"); + Require(saveFailed, "an unlock whose state cannot be written reports the failure"); + Require(sceNpTrophyGetTrophyUnlockState(context, handle, reinterpret_cast(flags), &count) == 0 && flags[0] == 2u, "a failed save leaves the trophy locked"); + Require(sceNpTrophyUnlockTrophy(context, handle, 0, &platinum) == 0, "the unlock succeeds once the state can be written"); + Require(sceNpTrophyCreateContext(&missing, 1, 1, 0) == 0, "context for a label without a package"); Require(sceNpTrophyRegisterContext(missing, handle, 0) == TitleConfNotInstalled, "register without a trophy package"); Require(sceNpTrophyCreateHandle(nullptr) == InvalidArgument, "null handle creation"); diff --git a/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp b/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp new file mode 100644 index 0000000000..222d6f21a9 --- /dev/null +++ b/core/libs/tests/GuestNpUniversalDataSystemQueries.cpp @@ -0,0 +1,69 @@ +#include "prx/libSceNpUniversalDataSystem/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static void Require(bool value, int line) { + if (value) return; + std::fprintf(stderr, "UDS query check failed at line %d\n", line); + std::abort(); +} +#define CHECK(value) Require((value), __LINE__) + +template +static bool RejectsArgument(Call&& call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +template +static bool Zeroed(const Stat& stat) { + const auto* bytes = reinterpret_cast(&stat); + for (std::size_t index = 0; index < sizeof(stat); ++index) { + if (bytes[index] != 0) return false; + } + return true; +} + +int main() { + CHECK(RejectsArgument([] { sceNpUniversalDataSystemInitialize(nullptr); })); + const NpUniversalDataSystemInitParam init{sizeof(NpUniversalDataSystemInitParam), 0x10000}; + CHECK(sceNpUniversalDataSystemInitialize(&init) == 0); + + NpUniversalDataSystemMemoryStat memory; + std::memset(&memory, 0x5a, sizeof(memory)); + CHECK(RejectsArgument([] { sceNpUniversalDataSystemGetMemoryStat(nullptr); })); + CHECK(sceNpUniversalDataSystemGetMemoryStat(&memory) == 0 && Zeroed(memory)); + NpUniversalDataSystemStorageStat storage; + std::memset(&storage, 0x5a, sizeof(storage)); + CHECK(RejectsArgument([] { sceNpUniversalDataSystemGetStorageStat(1, nullptr); })); + CHECK(sceNpUniversalDataSystemGetStorageStat(1, &storage) == 0 && Zeroed(storage)); + + NpUniversalDataSystemEvent* event = nullptr; + CHECK(sceNpUniversalDataSystemCreateEvent("QueryEvent", nullptr, &event, nullptr) == 0 && event != nullptr); + std::size_t size = 0; + CHECK(RejectsArgument([&] { sceNpUniversalDataSystemEventEstimateSize(nullptr, &size); })); + CHECK(RejectsArgument([&] { sceNpUniversalDataSystemEventEstimateSize(event, nullptr); })); + CHECK(sceNpUniversalDataSystemEventEstimateSize(event, &size) == 0 && size == 3); + + char text[8]; + std::memset(text, 'x', sizeof(text)); + std::size_t length = 0; + CHECK(RejectsArgument([&] { sceNpUniversalDataSystemEventToString(nullptr, text, sizeof(text), &length); })); + CHECK(text[0] == 'x' && length == 0); + CHECK(sceNpUniversalDataSystemEventToString(event, text, sizeof(text), &length) == 0 && length == 3 && std::strcmp(text, "{}") == 0); + length = 0; + CHECK(sceNpUniversalDataSystemEventToString(event, nullptr, 0, &length) == 0 && length == 3); + char tiny[2] = {'x', 'x'}; + CHECK(sceNpUniversalDataSystemEventToString(event, tiny, sizeof(tiny), nullptr) == 0 && tiny[0] == '{' && tiny[1] == '\0'); + CHECK(sceNpUniversalDataSystemDestroyEvent(event) == 0); + std::puts("UDS query tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestOutputPointerFault.cpp b/core/libs/tests/GuestOutputPointerFault.cpp new file mode 100644 index 0000000000..6877c50c33 --- /dev/null +++ b/core/libs/tests/GuestOutputPointerFault.cpp @@ -0,0 +1,111 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/include/Api.hpp" + +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#endif + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "output pointer check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr int GuestEfault = 14; +static constexpr int KernelEfault = static_cast(0x8002000Eu); + +static bool Efault(int result) { + return result == -1 && *__error_nid_postfix() == GuestEfault; +} + +#ifdef __linux__ +static void RequireNonEfaultFailureThrows() { + const pid_t child = fork(); + Require(child >= 0); + if (child == 0) { + sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_process_vm_readv, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + sock_fprog program{static_cast(sizeof(filter) / sizeof(filter[0])), filter}; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 || prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program) != 0) _exit(2); + char source[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + char destination[8] = {}; + try { + GuestCopyIn_nid_no_patch(destination, source, sizeof(source)); + } catch (const std::runtime_error&) { + _exit(destination[0] == 0 ? 0 : 3); + } + _exit(1); + } + int status = 0; + Require(waitpid(child, &status, 0) == child); + Require(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} +#endif + +int main() { + alignas(8) unsigned char valid[512]{}; + alignas(8) unsigned char zeroTimer[32]{}; + auto* timeValid = reinterpret_cast(valid); + auto* zoneValid = reinterpret_cast(valid + 32); + auto* timerValid = reinterpret_cast(valid); + auto* zeroTimerValue = reinterpret_cast(zeroTimer); + for (const std::uintptr_t address : {std::uintptr_t{1}, ~std::uintptr_t{0xfff}}) { + void* bad = reinterpret_cast(address); + auto* badTime = reinterpret_cast(address); + auto* badZone = reinterpret_cast(address); + auto* badTimer = reinterpret_cast(address); + *__error_nid_postfix() = 0; + *__error_nid_postfix() = 0; + *__error_nid_postfix() = 0; + Require(Efault(gettimeofday_nid_postfix(badTime, nullptr))); + *__error_nid_postfix() = 0; + Require(Efault(gettimeofday_nid_postfix(timeValid, badZone))); + *__error_nid_postfix() = 0; + Require(Efault(getitimer_nid_postfix(0, badTimer))); + *__error_nid_postfix() = 0; + Require(Efault(setitimer_nid_postfix(0, zeroTimerValue, badTimer))); + *__error_nid_postfix() = 0; + Require(Efault(setitimer_nid_postfix(0, badTimer, timerValid))); + *__error_nid_postfix() = 0; + Require(Efault(getitimer_nid_postfix(1, badTimer))); + *__error_nid_postfix() = 0; + Require(Efault(setitimer_nid_postfix(1, zeroTimerValue, badTimer))); + Require(sceKernelGettimeofday(badTime) == KernelEfault); + Require(sceKernelGettimezone(badZone) == KernelEfault); + } + Require(gettimeofday_nid_postfix(timeValid, zoneValid) == 0); + Require(getitimer_nid_postfix(0, timerValid) == 0); + Require(setitimer_nid_postfix(0, zeroTimerValue, timerValid) == 0); + Require(getitimer_nid_postfix(1, timerValid) == 0); + Require(setitimer_nid_postfix(1, zeroTimerValue, timerValid) == 0); + GuestItimerval armed{}; + armed.value.tv_sec = 3600; + *__error_nid_postfix() = 0; + Require(Efault(setitimer_nid_postfix(0, &armed, reinterpret_cast(~std::uintptr_t{0xfff})))); + GuestItimerval current{}; + Require(getitimer_nid_postfix(0, ¤t) == 0 && current.value.tv_sec > 0); + Require(setitimer_nid_postfix(0, zeroTimerValue, nullptr) == 0); + Require(sceKernelGettimeofday(timeValid) == 0); + Require(sceKernelGettimezone(zoneValid) == 0); +#ifdef __linux__ + RequireNonEfaultFailureThrows(); +#endif +} diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index ca20015351..3f86b4ae82 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -79,8 +79,29 @@ static void CheckInputCommands() { Require(right.buttons == 0x4000 && !right.touchLeft && right.touchRight); write("10 8388608 300"); Require(commands.Poll(3801000) == none); + write("10 0 5000 touch(1919,942,127,1)"); + const auto contact = commands.Poll(3901000); + Require(contact.hasTouch && contact.touchActive && contact.touchX == 1919 && contact.touchY == 942 && contact.touchId == 127); + Require(contact.buttons == 0 && !contact.touchLeft && !contact.touchRight); + Require(commands.Poll(4001000) == contact); + write("11 0 5000 1 2 3 4 touch(0,0,127,1)"); + const auto moved = commands.Poll(4101000); + Require(moved.hasTouch && moved.touchActive && moved.touchX == 0 && moved.touchY == 0 && moved.touchId == contact.touchId); + Require(moved.hasSticks && moved.sticks[3] == 4); + write("12 0 5000 touch(0,0,127,0)"); + const auto lifted = commands.Poll(4201000); + Require(lifted.hasTouch && !lifted.touchActive && lifted.touchId == 127 && lifted.buttons == 0); + std::uint64_t poll = 4301000; + for (const char* invalid : {"touch(1920,0,0,1)", "touch(0,943,0,1)", "touch(0,0,128,1)", + "touch(0,0,0,2)", "touch(-1,0,0,1)", "touch(0,0,0)", "touch(0,0,0,1,1)", + "touch(0,,0,1)", "touch(0,0,0,1)x", "touch(0,0,0,1) trailing"}) { + std::ofstream(path) << "13 0 5000 " << invalid << '\n'; + Require(commands.Poll(poll) == lifted); + poll += 100000; + } + Require(commands.Poll(9201000) == none); std::filesystem::remove(path); - Require(commands.Buttons(4201000) == 0); + Require(commands.Buttons(9301000) == 0); Pad::InputCommandFile disabled(std::filesystem::path{}); Require(disabled.Buttons(0) == 0); } @@ -632,6 +653,34 @@ static void CheckInjectedState(const std::filesystem::path& path) { const auto lifted = Pad::ReadState(); Require(lifted.touch_data_touch_num == 0 && lifted.buttons == 0x2000); } + bool contactActive = false; + const auto touchCommand = [&](const char* command, int x, int y, int id, bool active) { + std::ofstream(path) << command << '\n'; + PadData touched{}; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + touched = Pad::ReadState(); + if (contactActive && active) Require(touched.touch_data_touch_num == 1 && touched.touch_data_touch0_id == id); + if (touched.touch_data_touch_num == (active ? 1 : 0) && + (!active || (touched.touch_data_touch0_x == x && touched.touch_data_touch0_y == y && touched.touch_data_touch0_id == id))) break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while (std::chrono::steady_clock::now() < deadline); + Require(touched.touch_data_touch_num == (active ? 1 : 0)); + if (active) Require(touched.touch_data_touch0_x == x && touched.touch_data_touch0_y == y && touched.touch_data_touch0_id == id); + Require(touched.buttons == 0x2000); + contactActive = active; + return touched; + }; + const auto start = touchCommand("5 0 5000 touch(1919,942,127,1)", 1919, 942, 127, true); + const auto turn = touchCommand("6 0 5000 touch(0,942,127,1)", 0, 942, 127, true); + Require(turn.timestamp > start.timestamp); + const auto end = touchCommand("7 0 5000 touch(0,0,127,1)", 0, 0, 127, true); + Require(end.timestamp > turn.timestamp); + const auto lifted = touchCommand("8 0 300 touch(0,0,127,0)", 0, 0, 127, false); + Require(lifted.timestamp > end.timestamp); + touchCommand("9 0 150 touch(960,471,0,1)", 960, 471, 0, true); + std::this_thread::sleep_for(std::chrono::milliseconds(160)); + Require(Pad::ReadState().touch_data_touch_num == 0); PadPublishInput_nid_postfix(PadInputState{}); } @@ -642,7 +691,7 @@ static void CheckInjectedQueueTimes(const std::filesystem::path& path) { physical.buttons = 0x2000; PadPublishInput_nid_postfix(physical); const auto before = Pad::ReadState(); - std::ofstream(path) << "5 16384 300\n"; + std::ofstream(path) << "10 16384 300\n"; PadData pressed{}; const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); do { diff --git a/core/libs/tests/GuestPlayerReviewDialog.cpp b/core/libs/tests/GuestPlayerReviewDialog.cpp index ecdea31224..1f608a2e58 100644 --- a/core/libs/tests/GuestPlayerReviewDialog.cpp +++ b/core/libs/tests/GuestPlayerReviewDialog.cpp @@ -39,6 +39,7 @@ int main() { Require(scePlayerReviewDialogInitialize() == COMMON_DIALOG_ERROR_ALREADY_INITIALIZED); Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_INITIALIZED); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_INITIALIZED); + Require(scePlayerReviewDialogGetResult(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); Require(scePlayerReviewDialogGetResult(reinterpret_cast(result)) == COMMON_DIALOG_ERROR_NOT_FINISHED); Require(scePlayerReviewDialogTerminate() == 0); Require(scePlayerReviewDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); @@ -100,6 +101,14 @@ int main() { Require(result[0] == -1 && result[1] == -1 && result[3] == -1); Require(result[2] == -1); Require(scePlayerReviewDialogTerminate() == 0); + Require(scePlayerReviewDialogInitialize() == 0); + param.mode = 1; + Require(scePlayerReviewDialogOpen(¶m) == 0); + Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); + result[2] = -1; + Require(scePlayerReviewDialogGetResult(reinterpret_cast(result)) == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(result[2] == COMMON_DIALOG_RESULT_USER_CANCELED); + Require(scePlayerReviewDialogTerminate() == 0); Require(scePlayerReviewDialogTerminate() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); Require(scePlayerReviewDialogOpen(nullptr) == static_cast(0x80B8000Du)); Require(scePlayerReviewDialogOpen(¶m) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); diff --git a/core/libs/tests/GuestPlayerSelectionDialog.cpp b/core/libs/tests/GuestPlayerSelectionDialog.cpp index bbdf154c9d..80a87cfd4a 100644 --- a/core/libs/tests/GuestPlayerSelectionDialog.cpp +++ b/core/libs/tests/GuestPlayerSelectionDialog.cpp @@ -1,8 +1,9 @@ #include "prx/libScePlayerSelectionDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include -#include +#include #include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libSceUserService/UserService.hpp" @@ -21,27 +22,36 @@ constexpr int kResultUserCanceled = 1; int main() { Require(sceUserServiceInitialize(nullptr) == 0); - PlayerSelectionDialogParam param{}; - param.base.size = sizeof(param.base); - param.base.magic = static_cast(reinterpret_cast(¶m)) - 1060003575u; - param.size = sizeof(param); - param.userId = USER_SERVICE_INITIAL_USER_ID; + struct GuardedParam { + PlayerSelectionDialogParam param; + std::uint8_t guard[8]; + }; + GuardedParam guarded; + std::memset(&guarded, 0xA5, sizeof(guarded)); + auto& param = guarded.param; PlayerSelectionDialogResult result{-1, 99, nullptr, 77, 55}; Require(scePlayerSelectionDialogTerminate() == kErrNotInitialized); Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); + Require(scePlayerSelectionDialogParamInitialize(nullptr) == kErrArgNull); + Require(scePlayerSelectionDialogParamInitialize(¶m) == 0); + Require(param.base.size == sizeof(param.base)); + Require(param.base.magic == static_cast(reinterpret_cast(¶m)) - 1060003575u); + Require(param.size == sizeof(param)); + Require(std::all_of(std::begin(param.base.reserved), std::end(param.base.reserved), [](std::uint8_t value) { return value == 0; })); + Require(param.userId == 0); + Require(std::all_of(std::begin(param.title), std::end(param.title), [](char value) { return value == 0; })); + Require(param.maxPlayers == 0); + Require(param.mode == 0); + Require(param.padding0 == 0); + Require(param.players == nullptr); + Require(param.playerCount == 0); + Require(std::all_of(std::begin(param.reserved), std::end(param.reserved), [](std::uint8_t value) { return value == 0; })); + Require(param.padding1 == 0); + Require(std::all_of(std::begin(guarded.guard), std::end(guarded.guard), [](std::uint8_t value) { return value == 0xA5; })); + param.userId = USER_SERVICE_INITIAL_USER_ID; Require(scePlayerSelectionDialogOpen(¶m) == kErrNotInitialized); Require(scePlayerSelectionDialogClose() == kErrNotInitialized); - Require(scePlayerSelectionDialogParamInitialize(nullptr) == kErrArgNull); - bool threw = false; - try { - scePlayerSelectionDialogParamInitialize(¶m); - - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); - Require(param.size == sizeof(param)); Require(scePlayerSelectionDialogGetResult(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogGetResult(&result) == kErrNotInitialized); Require(scePlayerSelectionDialogGetStatus() == 0); @@ -54,6 +64,8 @@ int main() { Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogGetResult(&result) == kErrNotFinished); + Require(scePlayerSelectionDialogOpen(¶m) == 0); + Require(scePlayerSelectionDialogGetStatus() == kStatusFinished); param.size = 0; Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); diff --git a/core/libs/tests/GuestPosixRandom.cpp b/core/libs/tests/GuestPosixRandom.cpp index e4f7307064..e5564d285b 100644 --- a/core/libs/tests/GuestPosixRandom.cpp +++ b/core/libs/tests/GuestPosixRandom.cpp @@ -54,5 +54,20 @@ int main() { std::size_t repeats = 0; for (std::size_t index = 1; index < Draws; ++index) repeats += words[index] == words[index - 1]; Require(repeats < 16); + Require(arc4random_uniform_nid_postfix(0) == 0); + Require(arc4random_uniform_nid_postfix(1) == 0); + for (std::size_t index = 0; index < 1000; ++index) { + Require(arc4random_uniform_nid_postfix(7) < 7); + } + std::array uniformCounts{}; + for (std::size_t index = 0; index < 20000; ++index) { + const auto value = arc4random_uniform_nid_postfix(10); + Require(value < 10); + ++uniformCounts[value]; + } + for (const auto count : uniformCounts) Require(count > 1500 && count < 2500); + for (std::size_t index = 0; index < 100; ++index) { + Require(arc4random_uniform_nid_postfix(0x80000000u) < 0x80000000u); + } return 0; } diff --git a/core/libs/tests/GuestPosixSpawn.cpp b/core/libs/tests/GuestPosixSpawn.cpp index a54d5e1335..473fb160c1 100644 --- a/core/libs/tests/GuestPosixSpawn.cpp +++ b/core/libs/tests/GuestPosixSpawn.cpp @@ -23,7 +23,7 @@ extern "C" int kill(int, int); #endif -#if defined(__linux__) +#ifndef _WIN32 extern char** environ; #endif @@ -346,6 +346,7 @@ int main() { #ifndef _WIN32 if (std::strcmp(role, "child-stop") == 0) { std::raise(SIGSTOP); + std::this_thread::sleep_for(std::chrono::seconds(2)); std::_Exit(44); } if (std::strcmp(role, "child-kill-bus") == 0) std::raise(SIGBUS); diff --git a/core/libs/tests/GuestPosixSpawnFlags.cpp b/core/libs/tests/GuestPosixSpawnFlags.cpp new file mode 100644 index 0000000000..8893cdddf7 --- /dev/null +++ b/core/libs/tests/GuestPosixSpawnFlags.cpp @@ -0,0 +1,68 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "Posix spawn flags check failed at line %d\n", line); + std::abort(); + } +} +#define Check(value) Require((value), __LINE__) + +static std::atomic observedSpawnFlags{-1}; + +extern "C" int posix_spawn(pid_t* process, const char* path, const posix_spawn_file_actions_t* actions, + const posix_spawnattr_t* attributes, char* const arguments[], char* const environment[]) { + short flags = 0; + Check(attributes == nullptr || posix_spawnattr_getflags(attributes, &flags) == 0); + static const auto spawn = reinterpret_cast(dlsym(RTLD_NEXT, "posix_spawn")); + Check(spawn != nullptr); + observedSpawnFlags.store(flags, std::memory_order_relaxed); + return spawn(process, path, actions, attributes, arguments, environment); +} + +int main(int count, char** arguments) { + if (count == 2 && std::strcmp(arguments[1], "spawn-flags-child") == 0) return 43; + std::filesystem::create_directories("app0"); + { + std::ofstream image("app0/eboot.bin"); + image << "image"; + Check(image.good()); + } + PosixSpawnAttributes* attributes = nullptr; + Check(posix_spawnattr_init_nid_postfix(&attributes) == 0); + Check(posix_spawnattr_setpgroup_nid_postfix(&attributes, 0) == 0); + constexpr int expectedFlags[] = {0, POSIX_SPAWN_RESETIDS, POSIX_SPAWN_SETPGROUP, + POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP}; + char program[] = "/app0/eboot.bin"; + char role[] = "spawn-flags-child"; + char* childArguments[] = {program, role, nullptr}; + for (std::int16_t flags = 0; flags < 4; ++flags) { + Check(posix_spawnattr_setflags_nid_postfix(&attributes, flags) == 0); + observedSpawnFlags.store(-1, std::memory_order_relaxed); + std::int32_t process = -1; + Check(posix_spawn_nid_postfix(&process, program, nullptr, &attributes, childArguments, nullptr) == 0); + Check(process > 0); + const int observed = observedSpawnFlags.load(std::memory_order_relaxed); + if (observed != expectedFlags[flags]) + std::fprintf(stderr, "Spawn flags %d: expected host flags %d, observed %d\n", flags, expectedFlags[flags], observed); + Check(observed == expectedFlags[flags]); + Check(getpgid(process) == ((flags & 2) != 0 ? process : getpgrp())); + int status = -1; + Check(waitpid_nid_postfix(process, &status, 0) == process); + Check((status & 0x7f) == 0 && ((status >> 8) & 0xff) == 43); + } + Check(posix_spawnattr_destroy_nid_postfix(&attributes) == 0); + Check(std::filesystem::remove("app0/eboot.bin")); + Check(std::filesystem::remove("app0")); +} diff --git a/core/libs/tests/GuestPosixSpecific.cpp b/core/libs/tests/GuestPosixSpecific.cpp new file mode 100644 index 0000000000..aaa308675a --- /dev/null +++ b/core/libs/tests/GuestPosixSpecific.cpp @@ -0,0 +1,77 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int Einval = 22; + +void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +void WaitFor(const std::atomic& flag, const char* message) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!flag && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(flag, message); +} + +} + +int main() { + int first = 0; + int second = 0; + for (const PthreadKey invalid : {static_cast(0), static_cast(0xFFFFFFFFu)}) { + Require(pthread_setspecific_nid_postfix(invalid, &first) == Einval, "setspecific of an out-of-range key returns EINVAL"); + Require(pthread_getspecific_nid_postfix(invalid) == nullptr, "getspecific of an out-of-range key returns NULL"); + } + + PthreadKey key = 0; + Require(pthread_key_create_nid_postfix(&key, nullptr) == 0 && key != 0, "key create"); + Require(pthread_getspecific_nid_postfix(key) == nullptr, "getspecific of a key never set returns NULL"); + Require(pthread_setspecific_nid_postfix(key, &first) == 0, "setspecific returns 0"); + Require(pthread_getspecific_nid_postfix(key) == &first, "getspecific returns the stored value"); + Require(pthread_setspecific_nid_postfix(key, nullptr) == 0, "setspecific of NULL returns 0"); + Require(pthread_getspecific_nid_postfix(key) == nullptr, "getspecific after storing NULL returns NULL"); + Require(pthread_setspecific_nid_postfix(key, &first) == 0, "setspecific again returns 0"); + + std::atomic stored{false}; + std::atomic release{false}; + std::atomic finished{false}; + std::atomic seenBefore{&first}; + std::atomic seenAfter{nullptr}; + std::thread other([&] { + seenBefore = pthread_getspecific_nid_postfix(key); + Require(pthread_setspecific_nid_postfix(key, &second) == 0, "helper setspecific returns 0"); + seenAfter = pthread_getspecific_nid_postfix(key); + stored = true; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!release && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + finished = true; + }); + WaitFor(stored, "helper never stored its value"); + Require(seenBefore.load() == nullptr, "another thread starts with NULL for a key this thread has set"); + Require(seenAfter.load() == &second, "another thread reads back its own value"); + Require(pthread_getspecific_nid_postfix(key) == &first, "this thread still reads its own value"); + release = true; + WaitFor(finished, "helper never finished"); + other.join(); + + Require(pthread_key_delete_nid_postfix(key) == 0, "key delete returns 0"); + Require(pthread_getspecific_nid_postfix(key) == nullptr, "getspecific of a deleted key returns NULL"); + Require(pthread_setspecific_nid_postfix(key, &first) == Einval, "setspecific of a deleted key returns EINVAL"); + + PthreadKey reused = 0; + Require(pthread_key_create_nid_postfix(&reused, nullptr) == 0 && reused == key, "a new key reuses the freed number"); + Require(pthread_getspecific_nid_postfix(reused) == nullptr, "the value stored under the old key is gone"); + Require(pthread_key_delete_nid_postfix(reused) == 0, "second key delete returns 0"); + return 0; +} diff --git a/core/libs/tests/GuestProcessTime.cpp b/core/libs/tests/GuestProcessTime.cpp index cfff04c3bd..0d75b8f43f 100644 --- a/core/libs/tests/GuestProcessTime.cpp +++ b/core/libs/tests/GuestProcessTime.cpp @@ -1,3 +1,4 @@ +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include #include @@ -22,6 +23,20 @@ int main(int argc, char** argv) { const auto minimumElapsedMicros = elapsedMicros * 19 / 20; const auto slept = std::chrono::steady_clock::now() + std::chrono::microseconds(sleptMicros); while (std::chrono::steady_clock::now() < slept) std::this_thread::sleep_until(slept); + if (argc > 1 && std::strcmp(argv[1], "tsc") == 0) { + const std::uint64_t frequency = sceKernelGetTscFrequency(); + const auto start = std::chrono::steady_clock::now(); + const std::uint64_t before = sceKernelReadTsc(); + const auto until = start + std::chrono::microseconds(sleptMicros); + while (std::chrono::steady_clock::now() < until) std::this_thread::sleep_until(until); + const std::uint64_t ticks = sceKernelReadTsc() - before; + const auto measuredMicros = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + const std::uint64_t tscMicros = ticks / frequency * 1000000 + ticks % frequency * 1000000 / frequency; + Require(tscMicros >= minimumElapsedMicros, "TSC advanced less than the sleep at the TSC frequency"); + Require(static_cast(tscMicros) <= static_cast(measuredMicros) * (scale > 0.0 ? scale : 1.0) * 1.05, "TSC advanced more than the measured time at the time scale"); + Require(sceKernelReadTsc() >= before + ticks, "TSC went backwards"); + return 0; + } if (argc > 1 && std::strcmp(argv[1], "counter") == 0) { const std::uint64_t counter = sceKernelGetProcessTimeCounter(); const std::uint64_t frequency = sceKernelGetProcessTimeCounterFrequency(); diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp index f0ec1a6ace..79ced5a38f 100644 --- a/core/libs/tests/GuestPsml.cpp +++ b/core/libs/tests/GuestPsml.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -66,11 +67,51 @@ void SetupBeforeInit() { Require(scePsmlMfsrIsCaptureInProgress(sentinel) == kErrNotInitialized); } +void EnableFlagsReturnZero() { + Require(scePsmlMfsrEnableSpecularAA(0) == 0); + Require(scePsmlMfsrEnableSpecularAA(1) == 0); + Require(scePsmlMfsrEnableQuality8K(0) == 0); + Require(scePsmlMfsrEnableQuality8K(1) == 0); + Require(scePsmlMfsrEnableLightWeight(0) == 0); + Require(scePsmlMfsrEnableLightWeight(1) == 0); +} + +void CreateContext1300SizeCheck() { + constexpr std::int32_t unsupportedResolution = static_cast(0x8A810013); + std::array param{}; + const auto setType = [&](std::uint32_t value) { std::memcpy(param.data(), &value, sizeof(value)); }; + const auto setDims = [&](std::uint16_t width, std::uint16_t height) { + std::memcpy(param.data() + 0x18, &width, sizeof(width)); + std::memcpy(param.data() + 0x1a, &height, sizeof(height)); + }; + std::uint64_t contextStorage = 0; + void* const sentinel = &contextStorage; + void* context = sentinel; + + setType(0); + for (const auto [w, h] : {std::pair{3841, 2160}, {3840, 2161}, {7680, 4320}}) { + setDims(static_cast(w), static_cast(h)); + Require(scePsmlMfsrCreateContext1300(&context, param.data()) == unsupportedResolution); + } + for (const auto [w, h] : {std::pair{3840, 2160}, {1920, 1080}}) { + setDims(static_cast(w), static_cast(h)); + Require(scePsmlMfsrCreateContext1300(&context, param.data()) == kErrNotInitialized); + } + setType(1); + setDims(7680, 4320); + Require(scePsmlMfsrCreateContext1300(&context, param.data()) == kErrNotInitialized); + Require(scePsmlMfsrCreateContext1300(&context, nullptr) == kErrNotInitialized); + Require(context == sentinel); +} + void Mfsr1OnBaseConsole() { constexpr std::int32_t unsupportedConsole = static_cast(0x8A810012); + EnableFlagsReturnZero(); Require(scePsmlMfsrInit() == unsupportedConsole); Require(scePsmlMfsrInit() == unsupportedConsole); SetupBeforeInit(); + EnableFlagsReturnZero(); + CreateContext1300SizeCheck(); } void ConcurrentCreatesUninitialized() { diff --git a/core/libs/tests/GuestPthreadAttr.cpp b/core/libs/tests/GuestPthreadAttr.cpp index 52a348796c..b29f772084 100644 --- a/core/libs/tests/GuestPthreadAttr.cpp +++ b/core/libs/tests/GuestPthreadAttr.cpp @@ -319,7 +319,23 @@ static void CheckEndedThreads() { Require(scePthreadAttrDestroy(&detachedAttr) == SCE_OK); } +static void CheckGetprioOfTheCallingThread() { + const Pthread self = scePthreadSelf(); + int original = 0; + Require(scePthreadGetprio(self, &original) == SCE_OK); + int priority = 0; + Require(scePthreadSetprio(self, UPDATED_PRIORITY) == SCE_OK); + Require(scePthreadGetprio(self, &priority) == SCE_OK && priority == UPDATED_PRIORITY); + Require(scePthreadSetprio(self, CREATION_PRIORITY) == SCE_OK); + Require(scePthreadGetprio(self, &priority) == SCE_OK && priority == CREATION_PRIORITY); + Require(scePthreadGetprio(self, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadGetprio(nullptr, &priority) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadSetprio(self, original) == SCE_OK); + Require(scePthreadGetprio(self, &priority) == SCE_OK && priority == original); +} + int main() { + CheckGetprioOfTheCallingThread(); CheckStackSizeLimit(); CheckSoloSched(); CheckSchedArguments(); diff --git a/core/libs/tests/GuestPthreadCancelInactiveWait.cpp b/core/libs/tests/GuestPthreadCancelInactiveWait.cpp new file mode 100644 index 0000000000..c7fddeca47 --- /dev/null +++ b/core/libs/tests/GuestPthreadCancelInactiveWait.cpp @@ -0,0 +1,160 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "%s:%u: requirement failed\n", location.file_name(), location.line()); + std::_Exit(EXIT_FAILURE); +} + +template +static void Await(TPredicate predicate, std::source_location location = std::source_location::current()) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (!predicate() && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + Require(predicate(), location); +} + +struct Gate { + std::mutex mutex; + std::condition_variable changed; + bool entered = false; + bool released = false; + + void Park() { + std::unique_lock lock(mutex); + entered = true; + changed.notify_all(); + changed.wait(lock, [&] { return released; }); + } + + bool Entered() { + std::lock_guard lock(mutex); + return entered; + } + + void Release() { + std::lock_guard lock(mutex); + released = true; + changed.notify_all(); + } +}; + +struct GatedLock { + std::mutex mutex; + Gate gate; + bool first = true; + + void lock() { mutex.lock(); } + void unlock() { + mutex.unlock(); + if (first) { + first = false; + gate.Park(); + } + } +}; + +struct Context { + GatedLock lock; + TimedWait::Condition condition; + PthreadKey key = 0; + bool timed = false; + bool destructorWait = false; + bool naturalReturn = false; + bool ready = false; + std::atomic waitReturned{false}; + std::atomic returned{false}; + std::atomic cleanup{0}; + std::atomic destructors{0}; +}; + +static void Wait(Context& context) { + { + std::unique_lock lock(context.lock); + const auto deadline = context.timed ? std::optional(TimedWait::DeadlineNanos(60000000)) : std::nullopt; + while (!context.ready) Require(ThreadCancel::Wait(context.condition, lock, deadline)); + } + context.waitReturned = true; + ThreadCancel::Check(); +} + +static void APS5_VABI DestroySpecific(void* argument) { + auto& context = *static_cast(argument); + if (context.destructorWait) Wait(context); + Require(context.waitReturned && context.cleanup == (context.naturalReturn ? 0 : 1)); + ++context.destructors; +} + +static void APS5_VABI Cleanup(void* argument) { + auto& context = *static_cast(argument); + Require(context.cleanup.fetch_add(1) == 0); + if (!context.destructorWait) Wait(context); +} + +static void* APS5_VABI Worker(void* argument) { + auto& context = *static_cast(argument); + Require(scePthreadSetspecific(context.key, &context) == 0); + if (context.naturalReturn) { + context.returned = true; + return &context; + } + CleanupHandler cleanup{}; + __pthread_cleanup_push_imp_nid_postfix(Cleanup, &context, &cleanup); + scePthreadExit(&context); + context.returned = true; + return nullptr; +} + +static void Check(bool timed, bool destructorWait, bool naturalReturn, bool posix) { + Context context; + context.timed = timed; + context.destructorWait = destructorWait; + context.naturalReturn = naturalReturn; + const int cleanupCount = naturalReturn ? 0 : 1; + Require(scePthreadKeyCreate(&context.key, reinterpret_cast(DestroySpecific)) == 0); + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Worker, &context, nullptr) == 0); + Await([&] { return context.lock.gate.Entered(); }); + std::atomic cancelReturned{false}; + std::thread cancel([&] { + for (int repeat = 0; repeat < 3; ++repeat) + Require((posix ? pthread_cancel_nid_postfix(thread) : scePthreadCancel(thread)) == 0); + cancelReturned = true; + }); + Await([&] { return cancelReturned.load(); }); + Require(!context.waitReturned && context.returned == naturalReturn && context.destructors == 0); + Require(context.cleanup == cleanupCount); + Require(thread->cancelPending.load()); + cancel.join(); + context.lock.gate.Release(); + { + std::lock_guard lock(context.lock.mutex); + context.ready = true; + context.condition.NotifyOne(); + } + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == 0); + Require(result == &context); + Require(context.waitReturned && context.returned == naturalReturn && context.cleanup == cleanupCount && context.destructors == 1); + Require(scePthreadKeyDelete(context.key) == 0); +} + +int main(int count, char** arguments) { + Require(count == 4); + const std::string_view state(arguments[1]), mode(arguments[2]), api(arguments[3]); + Require(state == "cleanup" || state == "destructor" || state == "return"); + Require(mode == "untimed" || mode == "timed"); + Require(api == "sce" || api == "posix"); + Check(mode == "timed", state != "cleanup", state == "return", api == "posix"); +} diff --git a/core/libs/tests/GuestPthreadCondattr.cpp b/core/libs/tests/GuestPthreadCondattr.cpp index 208bbb7776..0aab5e73df 100644 --- a/core/libs/tests/GuestPthreadCondattr.cpp +++ b/core/libs/tests/GuestPthreadCondattr.cpp @@ -35,10 +35,10 @@ static void CheckClockRefused(const PthreadCondattr* attr, KernelClockid* clockI int main() { PthreadCondattr unset = nullptr; KernelClockid clockId = -1; - CheckClockRefused(nullptr, &clockId); - Require(clockId == -1); - CheckClockRefused(&unset, &clockId); - Require(clockId == -1); + Require(pthread_condattr_getclock_nid_postfix(nullptr, &clockId) == GuestEinval && clockId == -1); + Require(pthread_condattr_getclock_nid_postfix(&unset, &clockId) == GuestEinval && clockId == -1); + Require(pthread_condattr_getclock_nid_postfix(nullptr, nullptr) == GuestEinval); + Require(pthread_condattr_getclock_nid_postfix(&unset, nullptr) == GuestEinval); int sharing = -1; Require(scePthreadCondattrSetclock(nullptr, Monotonic) == SceEinval); Require(scePthreadCondattrSetclock(&unset, Monotonic) == SceEinval); @@ -70,8 +70,7 @@ int main() { CheckClock(&other, Monotonic); CheckClock(&attr, Monotonic); Require(pthread_condattr_destroy_nid_postfix(&other) == 0 && other == nullptr); - CheckClockRefused(&other, &clockId); - Require(clockId == -1); + Require(pthread_condattr_getclock_nid_postfix(&other, &clockId) == GuestEinval && clockId == -1); Require(scePthreadCondattrSetpshared(&attr, 1) == SceEinval); Require(scePthreadCondattrSetpshared(&attr, 0) == SCE_OK); Require(scePthreadCondattrGetpshared(&attr, &sharing) == SCE_OK && sharing == 0); @@ -99,8 +98,7 @@ int main() { } Require(threw); Require(scePthreadCondattrDestroy(&attr) == SCE_OK && attr == nullptr); - CheckClockRefused(&attr, &clockId); - Require(clockId == -1); + Require(pthread_condattr_getclock_nid_postfix(&attr, &clockId) == GuestEinval && clockId == -1); Require(scePthreadCondattrDestroy(&attr) == SceEinval); sharing = -1; Require(pthread_condattr_getpshared_nid_postfix(&attr, &sharing) == GuestEinval && sharing == -1); diff --git a/core/libs/tests/GuestPthreadMutexErrors.cpp b/core/libs/tests/GuestPthreadMutexErrors.cpp index 7d7947f824..6a99ea4c97 100644 --- a/core/libs/tests/GuestPthreadMutexErrors.cpp +++ b/core/libs/tests/GuestPthreadMutexErrors.cpp @@ -101,8 +101,8 @@ static void CheckLoops(PthreadMutexattr* attr) { Require(pthread_mutex_getspinloops_np_nid_postfix(&mutex, &count) == POSIX_EINVAL); Require(pthread_mutex_setspinloops_np_nid_postfix(&mutex, 1) == POSIX_EINVAL); Require(pthread_mutex_getyieldloops_np_nid_postfix(&mutex, &count) == POSIX_EINVAL && count == -1); - Require(scePthreadMutexSetyieldloops(&mutex, 1) == SCE_OK); - Require(pthread_mutex_setyieldloops_np_nid_postfix(&mutex, 1) == 0); + Require(scePthreadMutexSetyieldloops(&mutex, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_mutex_setyieldloops_np_nid_postfix(&mutex, 1) == POSIX_EINVAL); Require(mutex == DestroyedMutex); for (const int type : {1, 2, 3}) { diff --git a/core/libs/tests/GuestPthreadMutexattr.cpp b/core/libs/tests/GuestPthreadMutexattr.cpp index 8af4a8fda3..4568fc1464 100644 --- a/core/libs/tests/GuestPthreadMutexattr.cpp +++ b/core/libs/tests/GuestPthreadMutexattr.cpp @@ -82,10 +82,35 @@ static void CheckKind() { } Require(pthread_mutexattr_settype_nid_postfix(&attr, 2) == 0); Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == 2); - for (const int invalid : {0, 5, -1}) - Require(Throws([&] { pthread_mutexattr_setkind_np_nid_postfix(&attr, invalid); })); - Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == 2); PthreadMutex mutex = nullptr; + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, 0) == 0); + Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == 0); + for (const int invalid : {0, 5, -1}) { + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, invalid) == 0); + Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == invalid); + type = -1; + if (invalid == 5) Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == GUEST_EINVAL && type == -1); + if (invalid != 5) Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == invalid); + Require(pthread_mutex_init_nid_postfix(&mutex, &attr) == GUEST_EINVAL && mutex == nullptr); + } + for (const int negative : {-2, -32768}) { + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, negative) == 0); + type = 0; + Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == negative); + } + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, 0x8000) == 0); + Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == -32768); + for (const int large : {32767, 0x12345}) { + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, large) == 0); + type = -1; + Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == GUEST_EINVAL && type == -1); + } + Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == 0x2345); + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, 0x10001) == 0); + Require(pthread_mutexattr_getkind_np_nid_postfix(attr) == 1); + Require(pthread_mutex_init_nid_postfix(&mutex, &attr) == 0); + Require(pthread_mutex_destroy_nid_postfix(&mutex) == 0); + Require(pthread_mutexattr_setkind_np_nid_postfix(&attr, 2) == 0); Require(pthread_mutex_init_nid_postfix(&mutex, &attr) == 0); Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); Require(pthread_mutex_lock_nid_postfix(&mutex) == 0); diff --git a/core/libs/tests/GuestPthreadRwlock.cpp b/core/libs/tests/GuestPthreadRwlock.cpp index 4e07e7dacb..f57954b010 100644 --- a/core/libs/tests/GuestPthreadRwlock.cpp +++ b/core/libs/tests/GuestPthreadRwlock.cpp @@ -420,7 +420,49 @@ static void CheckAttrType() { Require(Throws([&] { scePthreadRwlockattrGettype(&unset, &type); })); } +static void CheckSceAttrLifecycle() { + PthreadRwlockattr attr = nullptr; + Require(scePthreadRwlockattrInit(&attr) == SCE_OK && attr != nullptr); + PthreadRwlock lock = nullptr; + Require(scePthreadRwlockInit(&lock, &attr, "sce attr") == SCE_OK && lock != nullptr); + Require(pthread_rwlock_wrlock_nid_postfix(&lock) == 0); + Require(pthread_rwlock_tryrdlock_nid_postfix(&lock) == GUEST_EBUSY); + Require(pthread_rwlock_unlock_nid_postfix(&lock) == 0); + Require(pthread_rwlock_destroy_nid_postfix(&lock) == 0); + Require(scePthreadRwlockattrDestroy(&attr) == SCE_OK && attr == nullptr); + const auto throws = [](auto&& call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + return false; + }; + Require(throws([&] { scePthreadRwlockattrDestroy(&attr); })); + Require(throws([] { scePthreadRwlockattrDestroy(nullptr); })); + Require(throws([] { scePthreadRwlockattrInit(nullptr); })); +} + +static void CheckSceTryLocks() { + PthreadRwlock lock = nullptr; + Require(scePthreadRwlockInit(&lock, nullptr, "sce try") == SCE_OK && lock != nullptr); + Require(scePthreadRwlockTryrdlock(&lock) == SCE_OK); + Require(scePthreadRwlockTryrdlock(&lock) == SCE_OK); + Require(scePthreadRwlockTrywrlock(&lock) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadRwlockUnlock(&lock) == SCE_OK); + Require(scePthreadRwlockTrywrlock(&lock) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadRwlockUnlock(&lock) == SCE_OK); + Require(scePthreadRwlockTrywrlock(&lock) == SCE_OK); + Require(scePthreadRwlockTryrdlock(&lock) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadRwlockUnlock(&lock) == SCE_OK); + Require(scePthreadRwlockTryrdlock(&lock) == SCE_OK); + Require(scePthreadRwlockUnlock(&lock) == SCE_OK); + Require(scePthreadRwlockDestroy(&lock) == SCE_OK); +} + int main() { + CheckSceAttrLifecycle(); + CheckSceTryLocks(); CheckAttrSettype(); CheckAttrProcessSharing(); CheckAttrType(); diff --git a/core/libs/tests/GuestRaiseExitingThread.cpp b/core/libs/tests/GuestRaiseExitingThread.cpp new file mode 100644 index 0000000000..0af6dcc35c --- /dev/null +++ b/core/libs/tests/GuestRaiseExitingThread.cpp @@ -0,0 +1,55 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +static constexpr int SIGUSR1 = 30; +static constexpr int SCE_OK = 0; +static constexpr int Rounds = 100; +static constexpr std::size_t Threads = 32; + +static void Require(bool value) { if (!value) std::abort(); } + +static std::atomic handled{0}; +static std::atomic waiting{0}; +static HANDLE release = nullptr; + +static void APS5_VABI Handler(int signum, void*) { + Require(signum == SIGUSR1); + handled.fetch_add(1); +} + +static void* APS5_VABI Exiting(void*) { + waiting.fetch_add(1); + Require(WaitForSingleObject(release, INFINITE) == WAIT_OBJECT_0); + return nullptr; +} + +int main() { + release = CreateEventW(nullptr, TRUE, FALSE, nullptr); + Require(release != nullptr); + Require(sceKernelInstallExceptionHandler(SIGUSR1, reinterpret_cast(&Handler)) == 0); + int raised = 0; + for (int round = 0; round < Rounds; ++round) { + Require(ResetEvent(release) != 0); + waiting.store(0); + std::array threads{}; + for (auto& thread : threads) Require(scePthreadCreate(&thread, nullptr, Exiting, nullptr, "exiting") == SCE_OK); + while (waiting.load() != static_cast(Threads)) std::this_thread::yield(); + for (const auto thread : threads) { + Require(sceKernelRaiseException(thread, SIGUSR1) == SCE_OK); + ++raised; + } + Require(SetEvent(release) != 0); + for (const auto thread : threads) Require(scePthreadJoin(thread, nullptr) == SCE_OK); + } + Require(handled.load() <= raised); + Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0); + Require(CloseHandle(release) != 0); + std::printf("%d raises at threads that exit after their host wait, %d handled\n", raised, handled.load()); + return 0; +} diff --git a/core/libs/tests/GuestRazorCpu.cpp b/core/libs/tests/GuestRazorCpu.cpp index f938b7a80b..63f449ac1d 100644 --- a/core/libs/tests/GuestRazorCpu.cpp +++ b/core/libs/tests/GuestRazorCpu.cpp @@ -15,14 +15,20 @@ void Require(bool value) { if (!value) std::abort(); } int main() { Require(sceRazorCpuIsCapturing() == 0); Require(sceRazorCpuJobManagerDispatch(nullptr, 1, 2, 3, 4, 5, 6) == 0); + Require(sceRazorCpuJobManagerDispatch("job", 0, 0, 0, 0, 0, 0) == 0); Require(sceRazorCpuJobManagerJob(1, 2, 3, 16) == 0); + Require(sceRazorCpuJobManagerJob(0, 0, 0, 0) == 0); Require(sceRazorCpuJobManagerSequence(nullptr, 1, 2, 3) == 0); + Require(sceRazorCpuJobManagerSequence("sequence", 0, 0, 0) == 0); Require(sceRazorCpuNamedSync("frame") == 0); + Require(sceRazorCpuNamedSync(nullptr) == 0); Require(sceRazorCpuPushMarkerStatic("outer", 0x80ffffffu, 2) == 0); Require(sceRazorCpuPushMarkerStatic("inner", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPushMarkerStatic(nullptr, 0, 0) == 0); Require(sceRazorCpuPopMarker() == 0); Require(sceRazorCpuPopMarker() == 0); Require(sceRazorCpuPushMarker("copied", 0xff00ff00u, 1) == 0); + Require(sceRazorCpuPushMarker(nullptr, 0, 0) == 0); Require(sceRazorCpuPopMarker() == 0); Require(sceRazorCpuPushMarkerStatic("unbalanced", 0x80ffffffu, 2) == 0); Require(sceRazorCpuIsCapturing() == 0); @@ -31,8 +37,12 @@ int main() { Require(timeSpentInFlush == 0x123456789abcdef0ull); Require(sceRazorCpuFlushOccurred(nullptr) == 0); Require(sceRazorCpuPlotValue("read time (ms)", 1.5f) == 0); + Require(sceRazorCpuPlotValue(nullptr, 0.0f) == 0); + Require(sceRazorCpuPlotValue("read time (ms)", -1.0f) == 0); Require(sceRazorCpuWriteBookmark("read timeout", "lba=0x10") == 0); Require(sceRazorCpuWriteBookmark("read timeout", nullptr) == 0); + Require(sceRazorCpuWriteBookmark(nullptr, nullptr) == 0); + Require(sceRazorCpuWriteBookmark(nullptr, "description") == 0); Require(sceRazorCpuSync() == 0); Require(sceRazorCpuSync() == 0); Require(sceRazorCpuIsCapturing() == 0); @@ -45,6 +55,7 @@ int main() { Require(sceRazorCpuBeginLogicalFileAccess("data/level.pak", 0x0123456789abcdefull, 4096, 0) == 0); Require(sceRazorCpuEndLogicalFileAccess() == 0); Require(sceRazorCpuBeginLogicalFileAccess("save/slot0.bin", 0, 512, 1) == 0); + Require(sceRazorCpuBeginLogicalFileAccess(nullptr, 0, 0, 0) == 0); Require(sceRazorCpuEndLogicalFileAccess() == 0); Require(sceRazorCpuEndLogicalFileAccess() == 0); @@ -53,11 +64,16 @@ int main() { int elements[16]{}; unsigned char buffer[64]{}; Require(sceRazorCpuInitDataTags(storage, sizeof(storage)) == 0); + Require(sceRazorCpuInitDataTags(nullptr, 0) == 0); Require(sceRazorCpuTagArray("elements", 7, elements, sizeof(elements), sizeof(elements[0])) == 0); + Require(sceRazorCpuTagArray(nullptr, 0, nullptr, 0, 0) == 0); Require(sceRazorCpuTagBuffer("buffer", 7, buffer, sizeof(buffer)) == 0); + Require(sceRazorCpuTagBuffer(nullptr, 0, nullptr, 0) == 0); Require(sceRazorCpuResizeTaggedBuffer(7, buffer, sizeof(buffer) / 2) == 0); + Require(sceRazorCpuResizeTaggedBuffer(0, nullptr, 0) == 0); Require(sceRazorCpuUnTagBuffer(7, buffer) == 0); Require(sceRazorCpuUnTagBuffer(7, elements) == 0); + Require(sceRazorCpuUnTagBuffer(0, nullptr) == 0); Require(sceRazorCpuShutdownDataTags() == 0); for (const unsigned char byte : storage) Require(byte == 0x5a); Require(sceRazorCpuTagBuffer("untracked", 3, buffer, sizeof(buffer)) == 0); diff --git a/core/libs/tests/GuestRudpPoll.cpp b/core/libs/tests/GuestRudpPoll.cpp index 21274887b3..3d84a2db62 100644 --- a/core/libs/tests/GuestRudpPoll.cpp +++ b/core/libs/tests/GuestRudpPoll.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,7 @@ int main() { constexpr int notInitialized = static_cast(0x80770001u); constexpr int invalidContext = static_cast(0x80770003u); constexpr int invalidArgument = static_cast(0x80770004u); + constexpr int cancelled = static_cast(0x80770014u); constexpr int invalidPoll = static_cast(0x8077001Fu); constexpr int tooManyContexts = static_cast(0x80770020u); constexpr int add = 1; @@ -41,6 +43,7 @@ int main() { Require(sceRudpPollControl(1, add, 0, read) == notInitialized); Require(sceRudpPollWait(1, events.data(), eventCount, 0) == notInitialized); Require(sceRudpPollDestroy(1) == notInitialized); + Require(sceRudpPollCancel(0) == notInitialized); Require(sceRudpInit_nid_postfix(pool.data(), static_cast(pool.size())) == 0); Require(sceRudpPollCreate(0) == invalidArgument); @@ -108,6 +111,67 @@ int main() { Require(sceRudpPollControl(poll, add, third, read) == 0); Require(sceRudpPollControl(other, add, third, read) == 0); + Require(sceRudpPollCancel(-1) == invalidArgument); + Require(sceRudpPollCancel(std::numeric_limits::min()) == invalidArgument); + Require(sceRudpPollCancel(12345) == invalidPoll); + Require(sceRudpPollCancel(poll) == 0); + Require(sceRudpPollCancel(poll) == 0); + Require(sceRudpPollWait(other, events.data(), eventCount, 1000) == 0); + Require(sceRudpPollWait(poll, events.data(), eventCount, 1000) == cancelled); + Require(sceRudpPollWait(poll, events.data(), eventCount, 1000) == 0); + Require(sceRudpPollCancel(poll) == 0); + Require(sceRudpPollWait(poll, events.data(), eventCount, 0) == cancelled); + Require(sceRudpPollWait(poll, events.data(), eventCount, 0) == 0); + + const int cancelPoll = sceRudpPollCreate(1); + Require(cancelPoll > 0); + int foreverResult = 0; + int timedResult = 0; + std::thread foreverWaiter([&] { foreverResult = sceRudpPollWait(cancelPoll, events.data(), eventCount, forever); }); + std::thread timedWaiter([&] { timedResult = sceRudpPollWait(cancelPoll, events.data(), eventCount, 10000000); }); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + const auto cancelledAt = std::chrono::steady_clock::now(); + Require(sceRudpPollCancel(cancelPoll) == 0); + Require(std::chrono::steady_clock::now() - cancelledAt < std::chrono::milliseconds(500)); + foreverWaiter.join(); + timedWaiter.join(); + Require(foreverResult == cancelled && timedResult == cancelled); + Require(sceRudpPollWait(cancelPoll, events.data(), eventCount, 1000) == 0); + Require(sceRudpPollDestroy(cancelPoll) == 0); + Require(sceRudpPollCancel(cancelPoll) == invalidPoll); + + const int latePoll = sceRudpPollCreate(1); + Require(latePoll > 0); + for (int round = 0; round < 40; ++round) { + std::array earlyEvents{}; + std::array lateEvents{}; + std::atomic earlyResult{1}; + std::atomic lateResult{1}; + std::atomic cancelReturned{false}; + std::thread earlyWaiter([&] { earlyResult = sceRudpPollWait(latePoll, earlyEvents.data(), eventCount, forever); }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + std::thread canceller([&] { + Require(sceRudpPollCancel(latePoll) == 0); + cancelReturned = true; + }); + std::thread lateWaiter([&] { lateResult = sceRudpPollWait(latePoll, lateEvents.data(), eventCount, forever); }); + const auto cancelledAt = std::chrono::steady_clock::now(); + while (!cancelReturned && std::chrono::steady_clock::now() - cancelledAt < std::chrono::seconds(2)) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(cancelReturned); + canceller.join(); + while (earlyResult == 1 || lateResult == 1) { + Require(sceRudpPollCancel(latePoll) == 0); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + earlyWaiter.join(); + lateWaiter.join(); + Require(earlyResult == cancelled && lateResult == cancelled); + sceRudpPollWait(latePoll, events.data(), eventCount, 0); + Require(sceRudpPollWait(latePoll, events.data(), eventCount, 0) == 0); + } + Require(sceRudpPollDestroy(latePoll) == 0); + const int finitePoll = sceRudpPollCreate(1); Require(finitePoll > 0); int finiteResult = 0; @@ -137,6 +201,7 @@ int main() { Require(sceRudpEnd() == 0); endedWaiter.join(); Require(endedResult == notInitialized); + Require(sceRudpPollCancel(other) == notInitialized); Require(sceRudpInit_nid_postfix(pool.data(), static_cast(pool.size())) == 0); Require(sceRudpPollDestroy(other) == invalidPoll); diff --git a/core/libs/tests/GuestSaveDataCancel.cpp b/core/libs/tests/GuestSaveDataCancel.cpp index 5f9612d5ce..ed4a92ebaf 100644 --- a/core/libs/tests/GuestSaveDataCancel.cpp +++ b/core/libs/tests/GuestSaveDataCancel.cpp @@ -2,18 +2,33 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include -int main() { - try { - sceSaveDataCancel(0); +namespace { - } catch (const std::runtime_error& error) { - if (std::string(error.what()) == "sceSaveDataCancel not implemented") return 0; - std::fprintf(stderr, "sceSaveDataCancel threw: %s\n", error.what()); - return 1; +void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); } - std::fprintf(stderr, "sceSaveDataCancel returned instead of reporting that it is not implemented\n"); - return 1; +} + +template +void RequireThrows(TCall call, const std::string& expectedMessage) { + try { + call(); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()) == expectedMessage, "unexpected error message"); + return; + } + Require(false, "call returned instead of reporting that it is not implemented"); +} + +} + +int main() { + RequireThrows([] { sceSaveDataCancel(0); }, "sceSaveDataCancel not implemented"); + return 0; } diff --git a/core/libs/tests/GuestSaveDataConvert.cpp b/core/libs/tests/GuestSaveDataConvert.cpp index 13085f00db..5833f5029d 100644 --- a/core/libs/tests/GuestSaveDataConvert.cpp +++ b/core/libs/tests/GuestSaveDataConvert.cpp @@ -27,15 +27,6 @@ void Check(bool condition, const std::string& what) { ++failures; } -bool Throws(const SaveDataConvert* convert) { - try { - sceSaveDataConvert(convert); - } catch (const std::runtime_error&) { - return true; - } - return false; -} - int Convert(const char* data, std::size_t size) { SceSaveDataDirName name{}; std::memcpy(name.data, data, size); @@ -63,13 +54,13 @@ int main() { float progress = 0.0f; Check(sceSaveDataGetConvertProgress(&progress) == SaveDataErrorNotInitialized, "query before initialize fails with not initialized"); - Check(Throws(nullptr), "null convert pointer throws"); - SaveDataConvert nullDir{}; - Check(Throws(&nullDir), "null dir_name throws"); Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes"); Check(sceSaveDataGetConvertProgress(&progress) == 0, "get convert progress succeeds"); Check(progress == 0.0f, "convert progress reports 0.0f initially"); Check(sceSaveDataGetConvertProgress(nullptr) == SaveDataErrorParameter, "null progress pointer is rejected"); + Check(sceSaveDataConvert(nullptr) == SaveDataErrorParameter, "null convert pointer is rejected"); + SaveDataConvert nullDir{}; + Check(sceSaveDataConvert(&nullDir) == SaveDataErrorParameter, "null dir_name pointer is rejected"); Check(Convert("missing", sizeof("missing")) == SaveDataErrorNotFound, "a missing save is not found"); Check(sceSaveDataGetConvertProgress(&progress) == 0, "get convert progress succeeds after failed convert"); Check(progress < 1.0f, "convert progress is below 1.0f after failed convert"); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 89578a68c7..963c1eb14a 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -176,7 +176,8 @@ static void CheckStreamSender(int family) { for (const int descriptor : {client, accepted, listener}) Require(sceNetSocketClose(descriptor) == 0); } -static std::string CaptureNetstat(int pool) { +template +static std::string CaptureDiagnostic(Diagnostic diagnostic) { auto* file = std::tmpfile(); Require(file != nullptr); Require(std::fflush(stdout) == 0); @@ -189,7 +190,7 @@ static std::string CaptureNetstat(int pool) { const int saved = dup(output); Require(saved >= 0 && dup2(fileno(file), output) == output); #endif - const int result = sceNetShowNetstatWithMemory(pool); + const int result = diagnostic(); Require(std::fflush(stdout) == 0); #ifdef _WIN32 Require(_dup2(saved, output) == 0 && _close(saved) == 0); @@ -204,16 +205,50 @@ static std::string CaptureNetstat(int pool) { return text; } +static std::string CaptureNetstat(int pool) { + return CaptureDiagnostic([pool] { return sceNetShowNetstatWithMemory(pool); }); +} + +static std::string NetstatEntry(int id, const char* kind, char nonblocking, const char* state, const std::string& name) { + std::array line{}; + std::snprintf(line.data(), line.size(), "ID: %4d (%5s, 0) *....-%c %s pid=0x%08x ", id, kind, nonblocking, state, static_cast(getpid_nid_postfix())); + return line.data() + name + "\n"; +} + +static void CheckDiagnostics() { + const auto ifconfig = CaptureDiagnostic(sceNetShowIfconfig); + Require(ifconfig.find("lo0 (index=2)\n inet:127.0.0.1/255.0.0.0\n inet6:::1/128\n UP MULTICAST CONNECTED MTU:16384(max:16384) \n") != std::string::npos); + Require(ifconfig.find("\ndns addresses\n ") != std::string::npos && ifconfig.ends_with("\n ::, ::\n")); + Require(ifconfig.starts_with("lo0 (index=2)\n") || ifconfig.starts_with("eth0 (index=1)\n Type:Ethernet HWaddr:02:50:53:35:00:01\n inet:")); + const std::string padding(28, ' '); + const std::string longName(40, 'x'); + const int udp = sceNetSocket(longName.c_str(), 2, 2, 0); + const int tcp = sceNetSocket("netstat-tcp", 2, 1, 0); + const int epoll = sceNetEpollCreate("netstat-epoll", 0); + Require(udp >= 0 && tcp >= 0 && epoll >= 0); + const int nonblocking = 1; + Require(sceNetSetsockopt(tcp, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(sceNetBind_nid_postfix(tcp, address.data(), address.size()) == 0 && sceNetListen(tcp, 1) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(tcp, address.data(), &size) == 0); + const auto port = std::to_string((address[2] << 8) | address[3]); + const auto netstat = CaptureDiagnostic(sceNetShowNetstat); + Require(netstat.starts_with("--- ID, type, [self/R/S/RE/SE//NB], ... ---\n")); + const auto udpAt = netstat.find(NetstatEntry(udp, "udp", '.', "OPENED", longName.substr(0, 31)) + " Local : *:*" + padding + "\n Remote: *:*" + padding + "\n Send-Q: 0/"); + const auto tcpAt = netstat.find(NetstatEntry(tcp, "tcp", '*', "LISTEN", "netstat-tcp") + " Local : 127.0.0.1:" + port + padding + "\n Remote: *:*" + padding + "\n Send-Q: 0/"); + std::array epollLine{}; + std::snprintf(epollLine.data(), epollLine.size(), "ID: %4d (epoll) *....-. OPENED pid=0x%08x netstat-epoll\n", epoll, static_cast(getpid_nid_postfix())); + const auto epollAt = netstat.find(epollLine.data()); + Require(udpAt != std::string::npos && tcpAt != std::string::npos && epollAt != std::string::npos && udpAt < tcpAt && tcpAt < epollAt); + Require(netstat.find(longName.substr(0, 32)) == std::string::npos); + Require(sceNetSocketClose(udp) == 0 && sceNetSocketClose(tcp) == 0 && sceNetEpollDestroy(epoll) == 0); + const auto closed = CaptureDiagnostic(sceNetShowNetstat); + Require(closed.find("netstat-tcp") == std::string::npos && closed.find("netstat-epoll") == std::string::npos && closed.find(longName.substr(0, 31)) == std::string::npos); +} + static void CheckNetstat() { - for (const auto diagnostic : {sceNetShowNetstat, sceNetShowIfconfig}) { - bool unsupported = false; - try { - diagnostic(); - } catch (const std::runtime_error&) { - unsupported = true; - } - Require(unsupported); - } + CheckDiagnostics(); Require(sceNetShowPolicy() == 0); Require(sceNetShowNetstatEx(0) == 0); Require(sceNetShowRoute() == 0); diff --git a/core/libs/tests/GuestScreenShot.cpp b/core/libs/tests/GuestScreenShot.cpp index 47e86f8874..d947dd67e1 100644 --- a/core/libs/tests/GuestScreenShot.cpp +++ b/core/libs/tests/GuestScreenShot.cpp @@ -67,6 +67,8 @@ void TestDisableState() { Require(sceScreenShotIsDisabled(-1, &value) == ErrorArgument && value == 7, "id -1 is rejected"); Require(sceScreenShotIsDisabled(0, nullptr) == ErrorArgument, "null result is rejected"); Require(sceScreenShotDisableNotification() == 0 && sceScreenShotEnableNotification() == 0, "notification toggles"); + Require(sceScreenShotIsDisabled(1, &value) == 0 && value == 0, "positive id 1 is accepted"); + Require(sceScreenShotIsDisabled(2, &value) == 0 && value == 0, "positive id 2 is accepted"); } void TestOverlay(bool large) { diff --git a/core/libs/tests/GuestSignals.cpp b/core/libs/tests/GuestSignals.cpp index 8afec0ead8..fe96c136d1 100644 --- a/core/libs/tests/GuestSignals.cpp +++ b/core/libs/tests/GuestSignals.cpp @@ -66,8 +66,49 @@ void APS5_VABI MaskCallback(int value) { } static void Require(bool value) { if (!value) std::abort(); } +static void CheckSignalPointerFaults() { + constexpr std::size_t page = 0x4000; + void* memory = nullptr; + Require(sceKernelMapFlexibleMemory(&memory, page, 3, 0) == 0 && memory != nullptr); + Require(sceKernelMprotect(memory, page, 1) == 0); + const auto readOnly = memory; + const auto unreadable = reinterpret_cast(std::uintptr_t{0x10}); + const auto fails = [](int result) { return result == -1 && *__error_nid_postfix() == 14; }; + constexpr int user = 30; + GuestSigaction previous{}; + Require(fails(sigaction_nid_postfix(user, static_cast(unreadable), nullptr))); + Require(fails(sigaction_nid_postfix(0, static_cast(unreadable), nullptr))); + Require(fails(sigaction_nid_postfix(user, nullptr, static_cast(unreadable)))); + Require(fails(sigaction_nid_postfix(user, nullptr, static_cast(readOnly)))); + Require(sigaction_nid_postfix(0, nullptr, &previous) == -1 && *__error_nid_postfix() == 22); + Require(sigaction_nid_postfix(user, nullptr, &previous) == 0); + GuestSignalSet blocked{}, set{{1u << (user - 1), 0, 0, 0}}, none{}; + Require(sigprocmask_nid_postfix(3, nullptr, &blocked) == 0); + Require(fails(sigprocmask_nid_postfix(1, unreadable, nullptr))); + Require(fails(sigprocmask_nid_postfix(9, unreadable, nullptr))); + Require(sigprocmask_nid_postfix(9, &set, nullptr) == -1 && *__error_nid_postfix() == 22); + Require(fails(sigprocmask_nid_postfix(1, nullptr, unreadable))); + Require(fails(sigprocmask_nid_postfix(1, nullptr, readOnly))); + Require(sigprocmask_nid_postfix(1, nullptr, &none) == 0 && std::memcmp(&none, &blocked, sizeof(none)) == 0); + Require(fails(sigprocmask_nid_postfix(1, &set, readOnly))); + Require(sigprocmask_nid_postfix(1, nullptr, &none) == 0 && (none.bits[0] & set.bits[0]) != 0); + Require(sigprocmask_nid_postfix(3, &blocked, nullptr) == 0); + Require(pthread_sigmask_nid_postfix(1, static_cast(unreadable), nullptr) == 14); + Require(pthread_sigmask_nid_postfix(1, nullptr, static_cast(readOnly)) == 14); + Require(pthread_sigmask_nid_postfix(9, static_cast(unreadable), nullptr) == 14); + Require(pthread_sigmask_nid_postfix(9, &set, nullptr) == 22); + Require(sigprocmask_nid_postfix(3, &blocked, nullptr) == 0); + Require(fails(sigaltstack_nid_postfix(static_cast(unreadable), nullptr))); + Require(fails(sigaltstack_nid_postfix(nullptr, static_cast(unreadable)))); + Require(fails(sigaltstack_nid_postfix(nullptr, static_cast(readOnly)))); + Require(fails(sigpending_nid_postfix(static_cast(unreadable)))); + Require(fails(sigpending_nid_postfix(static_cast(readOnly)))); + Require(fails(sigpending_nid_postfix(nullptr))); +} + int main() { const auto invalid = reinterpret_cast(static_cast(-1)); + CheckSignalPointerFaults(); const auto ignore = reinterpret_cast(std::uintptr_t{1}); Require(signal_nid_postfix(9, Callback) == invalid); Require(*__error_nid_postfix() == 22); diff --git a/core/libs/tests/GuestSleepCancel.cpp b/core/libs/tests/GuestSleepCancel.cpp index a77fbe126f..95d3b3ff42 100644 --- a/core/libs/tests/GuestSleepCancel.cpp +++ b/core/libs/tests/GuestSleepCancel.cpp @@ -1,6 +1,8 @@ #include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include #include #include @@ -24,16 +26,38 @@ static std::atomic returned{false}; static bool disabled = false; static bool pending = false; static unsigned seconds = LongSleep; +static std::string_view api = "sleep"; +static unsigned cleanupCalls = 0; +static std::chrono::steady_clock::time_point sleepStart{}; static std::chrono::steady_clock::duration elapsed{}; +static void APS5_VABI Cleanup(void*) { + elapsed = std::chrono::steady_clock::now() - sleepStart; + ++cleanupCalls; +} + +static int Sleep() { + if (api == "sleep") return static_cast(sleep_nid_postfix(seconds)); + if (api == "sce_sleep") return static_cast(sceKernelSleep(seconds)); + if (api == "usleep") return usleep_nid_postfix(seconds * 1000000U); + if (api == "sce_usleep") return sceKernelUsleep_nid_postfix(seconds * 1000000U); + const KernelTimespec request{seconds, 0}; + if (api == "nanosleep") return nanosleep_nid_postfix(&request, nullptr); + if (api == "_nanosleep") return _nanosleep_nid_postfix(&request, nullptr); + Require(api == "sce_nanosleep"); + return sceKernelNanosleep(&request, nullptr); +} + static void* APS5_VABI Sleeper(void*) { if (disabled) Require(scePthreadSetcancelstate(1, nullptr) == 0); + CleanupHandler cleanup{}; + __pthread_cleanup_push_imp_nid_postfix(Cleanup, nullptr, &cleanup); ready.store(true); ready.notify_all(); if (pending) release.wait(false); - const auto start = std::chrono::steady_clock::now(); - Require(sleep_nid_postfix(seconds) == 0); - elapsed = std::chrono::steady_clock::now() - start; + sleepStart = std::chrono::steady_clock::now(); + Require(Sleep() == 0); + elapsed = std::chrono::steady_clock::now() - sleepStart; returned.store(true); if (disabled) { Require(scePthreadSetcancelstate(0, nullptr) == 0); @@ -41,6 +65,7 @@ static void* APS5_VABI Sleeper(void*) { Require(false); } + __pthread_cleanup_pop_imp_nid_postfix(0); return nullptr; } @@ -53,49 +78,63 @@ static bool WaitForCompletion(Pthread thread) { return true; } -static bool WaitForSleep(Pthread thread) { +static bool WaitForSleep(Pthread thread, bool hostSleep) { const auto limit = std::chrono::steady_clock::now() + ObservationLimit; for (;;) { { std::lock_guard lock(thread->cancelLock); if (thread->cancelWait != nullptr) return true; } + if (hostSleep && thread->waitCount.load(std::memory_order_seq_cst) > 0) return true; + if (thread->_finished.load(std::memory_order_acquire)) return false; if (std::chrono::steady_clock::now() >= limit) return false; std::this_thread::yield(); } } int main(int count, char** arguments) { - Require(count == 2); + Require(count == 2 || count == 3); + if (count == 3) api = arguments[2]; const std::string_view mode(arguments[1]); disabled = mode == "disabled"; const bool zero = mode == "pending"; - pending = zero || disabled; + pending = zero || disabled || mode == "pending_long"; const bool normal = mode == "normal"; - Require(mode == "blocked" || disabled || pending || normal); - if (disabled || normal) seconds = 1; + const bool deferred = mode == "deferred"; + Require(mode == "blocked" || disabled || pending || normal || deferred); + Require(!deferred || api == "sce_usleep" || api == "sce_nanosleep" || api == "sce_sleep"); + if (disabled || normal || deferred) seconds = 1; if (zero) seconds = 0; Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, Sleeper, nullptr, nullptr) == 0); - ready.wait(false); + const auto readyLimit = std::chrono::steady_clock::now() + ObservationLimit; + while (!ready.load()) { + Require(std::chrono::steady_clock::now() < readyLimit); + std::this_thread::yield(); + } bool wasSleeping = false; if (!normal) { - if (!pending) wasSleeping = WaitForSleep(thread); + if (!pending) { + wasSleeping = WaitForSleep(thread, deferred); + Require(wasSleeping); + } Require(scePthreadCancel(thread) == 0); release.store(true); release.notify_all(); } const bool completed = WaitForCompletion(thread); - void* result = nullptr; - Require(scePthreadJoin(thread, &result) == 0); if (!completed) std::fputs("sleep stayed blocked after cancellation\n", stderr); Require(completed); + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == 0); if (normal) { Require(result == nullptr && returned.load()); + Require(cleanupCalls == 0); } else { Require(result == reinterpret_cast(std::uintptr_t{1})); + Require(cleanupCalls == 1); Require(returned.load() == disabled); if (!pending) Require(wasSleeping); } - if (normal || disabled) Require(elapsed >= std::chrono::seconds(1)); + if (normal || disabled || deferred) Require(elapsed >= std::chrono::seconds(1)); } diff --git a/core/libs/tests/GuestSocialScreenDialog.cpp b/core/libs/tests/GuestSocialScreenDialog.cpp index eb261f831e..c86884d5f1 100644 --- a/core/libs/tests/GuestSocialScreenDialog.cpp +++ b/core/libs/tests/GuestSocialScreenDialog.cpp @@ -66,7 +66,10 @@ int main() { Require(sceSocialScreenDialogTerminate() == 0); Require(sceSocialScreenDialogTerminate() == kErrNotInitialized); Require(sceSocialScreenDialogOpen(nullptr) == kErrNotInitialized); + Require(sceSocialScreenDialogOpen(¶m) == kErrNotInitialized); + Require(sceSocialScreenDialogClose() == kErrNotInitialized); Require(sceSocialScreenDialogGetResult(nullptr) == kErrNotInitialized); + Require(sceSocialScreenDialogGetResult(result) == kErrNotInitialized); Require(sceSocialScreenDialogGetStatus() == kStatusNone); Require(sceSocialScreenDialogUpdateStatus() == kStatusNone); } diff --git a/core/libs/tests/GuestSocketEvents.cpp b/core/libs/tests/GuestSocketEvents.cpp index 59bd2bb4ad..ac1f57119f 100644 --- a/core/libs/tests/GuestSocketEvents.cpp +++ b/core/libs/tests/GuestSocketEvents.cpp @@ -149,10 +149,42 @@ static void CloseWhileWaitingOnFile() { Require(std::filesystem::remove(path)); } +static void Dup2OntoWatchedFile() { + const std::string path = "anyps5-equeue-dup2-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const int created = open_nid_postfix(path.c_str(), 0x0002 | 0x0200, 0644); + Require(created >= 0); + Require(close_nid_postfix(created) == 0); + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "dup2") == SCE_OK); + std::atomic stop{false}; + std::thread waiter([eq, &stop] { + const KernelUseconds timeout = 1000; + std::array events{}; + int count = 0; + while (!stop.load()) { + Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_KERNEL_ERROR_ETIMEDOUT); + } + }); + for (int i = 0; i < 2000; ++i) { + const int watched = open_nid_postfix(path.c_str(), 0x0002, 0); + const int replacement = open_nid_postfix(path.c_str(), 0x0002, 0); + Require(watched >= 0 && replacement >= 0); + Require(sceKernelAddReadEvent(eq, watched, 0, nullptr) == SCE_OK); + Require(dup2_nid_postfix(replacement, watched) == watched); + Require(close_nid_postfix(replacement) == 0); + Require(close_nid_postfix(watched) == 0); + } + stop.store(true); + waiter.join(); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); + Require(std::filesystem::remove(path)); +} + int main() { PipeReadEvents(); RegularFileEvents(); CloseWhileWaitingOnFile(); + Dup2OntoWatchedFile(); const int listener = socket_nid_postfix(2, 1, 6); Require(listener >= 0); std::array address{16, 2, 0, 0, 127, 0, 0, 1}; diff --git a/core/libs/tests/GuestSslSystemRoots.cpp b/core/libs/tests/GuestSslSystemRoots.cpp new file mode 100644 index 0000000000..915f98b32e --- /dev/null +++ b/core/libs/tests/GuestSslSystemRoots.cpp @@ -0,0 +1,206 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceSsl/include/Ssl.hpp" +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include "prx/libSceSsl/SslTypes.hpp" +#include "SslCertificateFixture.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int SslTrustSystemRoots_nid_no_patch(int sslConnectionId); + +namespace { + +constexpr int ErrorInvalidId = static_cast(0x8095f006); +constexpr int ErrorUnknownCa = static_cast(0x8095f00c); + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "%s\n", message); + std::exit(1); +} + +std::string ThrownMessage(int connection) { + try { + SslTrustSystemRoots_nid_no_patch(connection); + } catch (const std::exception& error) { + return error.what(); + } + return {}; +} + +void ServeSystemList(const std::string& text) { + const auto path = std::filesystem::current_path() / sceKernelGetFsSandboxRandomWord() / "common" / "cert" / "CA_LIST.cer"; + std::filesystem::create_directories(path.parent_path()); + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file << text; + Require(static_cast(file.flush()), "write the system CA list"); +} + +struct Server { + mbedtls_net_context listener; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context drbg; + mbedtls_x509_crt certificate; + mbedtls_pk_context key; + mbedtls_ssl_config config; + std::uint16_t port = 0; + + Server() { + mbedtls_net_init(&listener); + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&drbg); + mbedtls_x509_crt_init(&certificate); + mbedtls_pk_init(&key); + mbedtls_ssl_config_init(&config); + Require(mbedtls_ctr_drbg_seed(&drbg, mbedtls_entropy_func, &entropy, nullptr, 0) == 0, "seed server random generator"); + Require(mbedtls_x509_crt_parse(&certificate, reinterpret_cast(ServerPem), sizeof(ServerPem)) == 0, + "parse server certificate"); + Require(mbedtls_pk_parse_key(&key, reinterpret_cast(ServerKey), sizeof(ServerKey), nullptr, 0, + mbedtls_ctr_drbg_random, &drbg) == 0, "parse server key"); + Require(mbedtls_ssl_config_defaults(&config, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) == 0, + "configure server"); + mbedtls_ssl_conf_rng(&config, mbedtls_ctr_drbg_random, &drbg); + Require(mbedtls_ssl_conf_own_cert(&config, &certificate, &key) == 0, "set server certificate"); + mbedtls_ssl_conf_authmode(&config, MBEDTLS_SSL_VERIFY_NONE); + Require(mbedtls_net_bind(&listener, "127.0.0.1", "0", MBEDTLS_NET_PROTO_TCP) == 0, "bind loopback listener"); + sockaddr_in address{}; +#ifdef _WIN32 + int size = sizeof(address); +#else + socklen_t size = sizeof(address); +#endif + Require(getsockname(listener.fd, reinterpret_cast(&address), &size) == 0, "read listener port"); + port = ntohs(address.sin_port); + } + + ~Server() { + mbedtls_ssl_config_free(&config); + mbedtls_pk_free(&key); + mbedtls_x509_crt_free(&certificate); + mbedtls_ctr_drbg_free(&drbg); + mbedtls_entropy_free(&entropy); + mbedtls_net_free(&listener); + } + + void Serve() { + mbedtls_net_context client; + mbedtls_net_init(&client); + mbedtls_ssl_context ssl; + mbedtls_ssl_init(&ssl); + if (mbedtls_net_accept(&listener, &client, nullptr, 0, nullptr) == 0 && mbedtls_ssl_setup(&ssl, &config) == 0) { + mbedtls_ssl_set_bio(&ssl, &client, mbedtls_net_send, mbedtls_net_recv, nullptr); + if (mbedtls_ssl_handshake(&ssl) == 0) mbedtls_ssl_close_notify(&ssl); + } + mbedtls_ssl_free(&ssl); + mbedtls_net_free(&client); + } +}; + +int LoadAuthority(const char* pem) { + const int context = sceSslInit_nid_postfix(256 * 1024); + Require(context > 0, "create an SSL context"); + std::string text = pem; + SceSsl::Data authority{reinterpret_cast(text.data()), text.size()}; + auto* authorities = &authority; + Require(sceSslLoadCert(context, 1, &authorities, nullptr, nullptr) == 0, "load the context authority"); + return context; +} + +int Connect(int context, std::uint16_t port, bool systemRoots) { + const int descriptor = socket_nid_postfix(2, 1, 6); + Require(descriptor >= 0, "create guest socket"); + std::array address{16, 2, static_cast(port >> 8), static_cast(port), 127, 0, 0, 1}; + Require(connect_nid_postfix(descriptor, address.data(), 16) == 0, "connect guest socket"); + const int connection = sceSslCreateConnection(context, descriptor, "localhost"); + Require(connection > 0, "create an SSL connection"); + if (systemRoots) Require(SslTrustSystemRoots_nid_no_patch(connection) == 0, "trust the system CA list"); + const int result = sceSslConnect(connection); + if (result == 0) { + Require(!ThrownMessage(connection).empty(), "the system CA list was added to a started connection"); + std::array scratch{}; + Require(sceSslRead(connection, scratch.data(), scratch.size(), 0) == 0, "close notify did not end the stream"); + } + Require(sceSslDeleteConnection(connection) == 0 && close_nid_postfix(descriptor) == 0, "release the connection"); + return result; +} + +void CheckRejectedList(const std::string& text, const char* message) { + ServeSystemList(text); + const int context = sceSslInit_nid_postfix(256 * 1024); + Require(context > 0, "create an SSL context"); + const int connection = sceSslCreateConnection(context, 1000, "localhost"); + Require(connection > 0, "create an SSL connection"); + const auto path = std::string("/") + sceKernelGetFsSandboxRandomWord() + "/common/cert/CA_LIST.cer"; + Require(ThrownMessage(connection).find(path) != std::string::npos, message); + Require(ThrownMessage(connection).find(path) != std::string::npos, "a rejected system CA list was cached"); + Require(sceSslDeleteConnection(connection) == 0 && sceSslTerm_nid_postfix(context) == 0, "release the context"); +} + +} + +int main(int argc, char** argv) { + Require(argc == 2, "usage: system|context|empty|blank"); +#ifndef _WIN32 + std::signal(SIGPIPE, SIG_IGN); +#endif + const std::string mode = argv[1]; + if (mode == "empty") { + CheckRejectedList("", "an empty system CA list did not throw with its path"); + return 0; + } + if (mode == "blank") { + CheckRejectedList("no certificate here\n", "a system CA list without a certificate did not throw with its path"); + return 0; + } + Require(mode == "system" || mode == "context", "usage: system|context|empty|blank"); + ServeSystemList(mode == "system" ? RootPem : ClientPem); + Server server; + std::thread serving([&] { + for (int index = 0; index < 4; ++index) server.Serve(); + }); + const int empty = sceSslInit_nid_postfix(256 * 1024); + Require(empty > 0, "create an SSL context without authorities"); + Require(SslTrustSystemRoots_nid_no_patch(9999) == ErrorInvalidId, "an unknown connection id was accepted"); + if (mode == "system") { + Require(Connect(empty, server.port, false) == ErrorUnknownCa, "a connection without the system CA list trusted the fixture root"); + Require(Connect(empty, server.port, true) == 0, "the system CA list did not trust the fixture root"); + const int unrelated = LoadAuthority(ClientPem); + Require(Connect(unrelated, server.port, false) == ErrorUnknownCa, "an unrelated context authority trusted the server"); + Require(Connect(unrelated, server.port, true) == 0, "the system CA list was not trusted after the context's authorities"); + Require(sceSslTerm_nid_postfix(unrelated) == 0, "release the context with an unrelated authority"); + } else { + Require(Connect(empty, server.port, true) == ErrorUnknownCa, "a system CA list without the fixture root trusted the server"); + const int fixture = LoadAuthority(RootPem); + Require(Connect(fixture, server.port, false) == 0, "the context's fixture root was not trusted"); + Require(Connect(fixture, server.port, true) == 0, "the context's fixture root was not trusted alongside the system CA list"); + Require(Connect(empty, server.port, false) == ErrorUnknownCa, "a connection without authorities trusted the server"); + Require(sceSslTerm_nid_postfix(fixture) == 0, "release the context with the fixture root"); + } + serving.join(); + Require(sceSslTerm_nid_postfix(empty) == 0, "release the context without authorities"); + std::puts("SSL system roots passed"); + return 0; +} diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index ffdf41f6ff..87abe1f087 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -570,6 +570,46 @@ static void CheckClosedStreamWrites() { Require(fclose_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9); } +static std::string FileText(const std::string& filename) { + std::ifstream input(filename, std::ios::binary); + return std::string(std::istreambuf_iterator(input), std::istreambuf_iterator()); +} + +static void CheckClosedStreamReopen() { + const auto filename = "anyps5-closed-reopen-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + FileStream stream(std::tmpfile()); + Require(fclose_nid_postfix(&stream) == 0); + Require(freopen_nid_postfix("missing-closed-stream-dir/out.log", "w", &stream) == nullptr && *__error_nid_postfix() == 2); + Require(fputs_nid_postfix("text", &stream) == -1 && *__error_nid_postfix() == 9); + const std::string overlong(300, 'n'); + Require(freopen_nid_postfix(overlong.c_str(), "w", &stream) == nullptr && *__error_nid_postfix() == 63); + Require(fputs_nid_postfix("text", &stream) == -1 && *__error_nid_postfix() == 9); + Require(freopen_nid_postfix(nullptr, "w", &stream) == nullptr && *__error_nid_postfix() == 9); + Require(freopen_nid_postfix(filename.c_str(), "w", &stream) == &stream); + Require(fputs_nid_postfix("reopened\n", &stream) >= 0 && fflush_nid_postfix(&stream) == 0); + Require(fileno_nid_postfix(&stream) >= 0); + Require(freopen_nid_postfix(filename.c_str(), "r", &stream) == &stream); + Require(fgetc_nid_postfix(&stream) == 'r'); + Require(fclose_nid_postfix(&stream) == 0); + Require(FileText(filename) == "reopened\n"); + Require(std::filesystem::remove(filename)); +} + +static int CheckReopenedClosedStandardOutput() { + const auto filename = "anyps5-closed-stdout-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + Require(fclose_nid_postfix(__stdoutp_nid_postfix) == 0); + Require(printf_nid_postfix("%d", 1) == -1 && *__error_nid_postfix() == 9); + const std::string overlong(300, 'n'); + Require(freopen_nid_postfix(overlong.c_str(), "w", __stdoutp_nid_postfix) == nullptr && *__error_nid_postfix() == 63); + Require(printf_nid_postfix("%d", 1) == -1 && *__error_nid_postfix() == 9); + Require(freopen_nid_postfix(filename.c_str(), "w", __stdoutp_nid_postfix) == __stdoutp_nid_postfix); + Require(printf_nid_postfix("%d\n", 1) == 2 && puts_nid_postfix("text") >= 0 && fflush_nid_postfix(__stdoutp_nid_postfix) == 0); + Require(fclose_nid_postfix(__stdoutp_nid_postfix) == 0); + Require(FileText(filename) == "1\ntext\n"); + Require(std::filesystem::remove(filename)); + return 0; +} + static int CheckClosedStandardOutput() { Require(freopen_nid_postfix("missing-closed-stream-dir/out.log", "w", __stdoutp_nid_postfix) == nullptr); Require(printf_nid_postfix("%d", 1) == -1 && *__error_nid_postfix() == 9); @@ -662,7 +702,9 @@ int main(int argc, char** argv) { if (argc > 1 && std::string_view(argv[1]) == "--update-stdout") return CheckUpdateStandardOutput(); if (argc > 1 && std::string_view(argv[1]) == "--closed-stdout") return CheckClosedStandardOutput(); if (argc > 1 && std::string_view(argv[1]) == "--exclusive-stdout") return CheckExclusiveStandardOutput(); + if (argc > 1 && std::string_view(argv[1]) == "--reopen-closed-stdout") return CheckReopenedClosedStandardOutput(); CheckClosedStreamWrites(); + CheckClosedStreamReopen(); CheckDescriptorAccessModes(); Require(_Getmbcurmax_nid_postfix() == 3 && ___mb_cur_max_nid_postfix() == 1); CheckFlushWhileReading(); diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index c7ee8ee32e..7493440bc0 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -422,14 +422,14 @@ int main() { Require(std::strcmp(transformed, "abc") == 0 && transformed[4] == 'z'); std::memset(transformed, 'z', sizeof(transformed)); Require(strxfrm_nid_postfix(transformed, "abcdefgh", 4) == 8); - Require(std::strcmp(transformed, "abc") == 0 && transformed[4] == 'z'); + Require(std::memcmp(transformed, "abcd", 4) == 0 && transformed[4] == 'z'); std::memset(transformed, 'z', sizeof(transformed)); Require(strxfrm_nid_postfix(transformed, "abcdefg", sizeof(transformed)) == 7 && std::strcmp(transformed, "abcdefg") == 0); std::memset(transformed, 'z', sizeof(transformed)); - Require(strxfrm_nid_postfix(transformed, "abcdefgh", sizeof(transformed)) == 8 && std::strcmp(transformed, "abcdefg") == 0); + Require(strxfrm_nid_postfix(transformed, "abcdefgh", sizeof(transformed)) == 8 && std::memcmp(transformed, "abcdefgh", 8) == 0); std::memset(transformed, 'z', sizeof(transformed)); Require(strxfrm_nid_postfix(transformed, "", sizeof(transformed)) == 0 && transformed[0] == '\0' && transformed[1] == 'z'); - Require(strxfrm_nid_postfix(transformed, "abc", 1) == 3 && transformed[0] == '\0' && transformed[1] == 'z'); + Require(strxfrm_nid_postfix(transformed, "abc", 1) == 3 && transformed[0] == 'a' && transformed[1] == 'z'); Require(strxfrm_nid_postfix(nullptr, "abc", 0) == 3); Require(strpbrk_nid_postfix(buffer, "ay") == buffer + 1); Require(strpbrk_nid_postfix(buffer, "") == nullptr); @@ -542,6 +542,18 @@ int main() { Require(std::memcmp(dest, "abc:", 4) == 0); Require(memccpy_nid_postfix(dest, "abcdef", ':', 6) == nullptr); Require(memccpy_nid_postfix(dest, "abcdef", ':', 0) == nullptr); + const char safeA[] = "abcdef"; + const char safeB[] = "abcdeg"; + const char safeC[] = "abcdef"; + Require(timingsafe_memcmp_nid_postfix(safeA, safeC, 6) == 0); + Require(timingsafe_memcmp_nid_postfix(safeA, safeB, 6) < 0); + Require(timingsafe_memcmp_nid_postfix(safeB, safeA, 6) > 0); + Require(timingsafe_memcmp_nid_postfix(safeA, safeB, 5) == 0); + Require(timingsafe_memcmp_nid_postfix(safeA, safeB, 0) == 0); + const unsigned char highA[] = {0x00, 0x80}; + const unsigned char highB[] = {0x00, 0x7f}; + Require(timingsafe_memcmp_nid_postfix(highA, highB, 2) > 0); + Require(timingsafe_memcmp_nid_postfix(highB, highA, 2) < 0); CheckSecureCopySizeLimits(); CheckSecureStringSizeLimits(); return CheckMemcpyOverlap() ? 0 : 1; diff --git a/core/libs/tests/GuestSulphaConfig.cpp b/core/libs/tests/GuestSulphaConfig.cpp index 007930e68a..897771c754 100644 --- a/core/libs/tests/GuestSulphaConfig.cpp +++ b/core/libs/tests/GuestSulphaConfig.cpp @@ -14,9 +14,12 @@ static void Require(bool condition, const char* message) { } int main() { - errno = 7; - Require(sceSulphaGetDefaultConfig(nullptr) == static_cast(0x804d0003u) && errno == 7, - "A null configuration must return the native argument error without changing errno"); + for (const int expectedErrno : {0, 7, 22, 42}) { + errno = expectedErrno; + Require(sceSulphaGetDefaultConfig(nullptr) == static_cast(0x804d0003u) && errno == expectedErrno, + "A null configuration must return the native argument error without changing errno"); + } + struct Config { std::uint64_t size; std::uint64_t capacity; @@ -24,23 +27,52 @@ int main() { std::uint32_t port; }; static_assert(sizeof(Config) == 24); - for (const auto shift : {0u, 1u}) for (const auto initial : std::array{0, 24, 560, UINT64_MAX}) { - for (const unsigned char pattern : {0xa5, 0x5a}) { - std::array storage; - storage.fill(pattern); - auto* config = storage.data() + 8 + shift; - std::memcpy(config, &initial, sizeof(initial)); - errno = 7; - Require(sceSulphaGetDefaultConfig(config) == 0 && errno == 7, - "Default configuration must accept non-null storage regardless of its incoming size"); - Config result; - std::memcpy(&result, config, sizeof(result)); - Require(result.size == 24 && result.capacity == 4194304 && result.setting == 3 && result.port == 4001, - "Default configuration must match the native 24-byte record"); - for (std::size_t i = 0; i < storage.size(); ++i) { - if (i < 8 + shift || i >= 8 + shift + sizeof(Config)) - Require(storage[i] == pattern, "Default configuration must preserve bytes outside its record"); + + std::array exactStorage; + exactStorage.fill(0xee); + errno = 7; + Require(sceSulphaGetDefaultConfig(exactStorage.data()) == 0 && errno == 7, + "Default configuration must accept exact 24-byte buffer without changing errno"); + Config exactResult; + std::memcpy(&exactResult, exactStorage.data(), sizeof(exactResult)); + Require(exactResult.size == 24 && exactResult.capacity == 4194304 && exactResult.setting == 3 && exactResult.port == 4001, + "Default configuration in exact 24-byte buffer must match expected fields"); + + std::uint64_t rawWords[3]{0, 0, 0}; + Require(sceSulphaGetDefaultConfig(rawWords) == 0, + "Default configuration must accept 64-bit array destination"); + Require(rawWords[0] == 24ULL && rawWords[1] == 0x400000ULL && rawWords[2] == 0xFA100000003ULL, + "Default configuration raw 64-bit words must match native defaults"); + + Config idempotentConfig{}; + Require(sceSulphaGetDefaultConfig(&idempotentConfig) == 0, + "First call to default configuration must succeed"); + Require(sceSulphaGetDefaultConfig(&idempotentConfig) == 0, + "Second call to default configuration must succeed idempotently"); + Require(idempotentConfig.size == 24 && idempotentConfig.capacity == 4194304 && + idempotentConfig.setting == 3 && idempotentConfig.port == 4001, + "Default configuration must remain consistent across repeated calls"); + + for (const auto shift : {0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u}) { + for (const auto initial : std::array{0, 1, 24, 560, UINT64_MAX}) { + for (const unsigned char pattern : {0x00, 0x5a, 0xa5, 0xff}) { + std::array storage; + storage.fill(pattern); + auto* config = storage.data() + 16 + shift; + std::memcpy(config, &initial, sizeof(initial)); + errno = 7; + Require(sceSulphaGetDefaultConfig(config) == 0 && errno == 7, + "Default configuration must accept non-null storage regardless of its incoming size"); + Config result; + std::memcpy(&result, config, sizeof(result)); + Require(result.size == 24 && result.capacity == 4194304 && result.setting == 3 && result.port == 4001, + "Default configuration must match the native 24-byte record"); + for (std::size_t i = 0; i < storage.size(); ++i) { + if (i < 16 + shift || i >= 16 + shift + sizeof(Config)) + Require(storage[i] == pattern, "Default configuration must preserve bytes outside its record"); + } } } } + } diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index 9c19aa6e91..bcef6300ff 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -1010,10 +1010,96 @@ static void CheckUnknownRootsAndTopLevelNames() { *__error_nid_postfix() = 13; Require(sysctlbyname_nid_postfix(tooLong.c_str(), &value, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 2); } +static void CheckSysctlPointerFaults() { + constexpr std::size_t page = 0x4000; + void* memory = nullptr; + Require(sceKernelMapFlexibleMemory(&memory, page, 3, 0) == 0 && memory != nullptr); + auto* readOnlyLength = static_cast(memory); + *readOnlyLength = sizeof(int); + Require(sceKernelMprotect(memory, page, 1) == 0); + const auto fails = [](int result) { return result == -1 && *__error_nid_postfix() == 14; }; + auto* guard = static_cast(nullptr); + void* pages = nullptr; + Require(sceKernelMapFlexibleMemory(&pages, page * 2, 3, 0) == 0 && pages != nullptr); + guard = static_cast(pages) + page; + Require(sceKernelMprotect(guard, page, 0) == 0); + char* crossing = guard - 4; + std::memcpy(crossing, "kern", 4); + std::size_t nameLength = sizeof(int); + int nameValue = 0; + Require(fails(sysctlbyname_nid_postfix(reinterpret_cast(0x10), &nameValue, &nameLength, nullptr, 0))); + Require(fails(sysctlbyname_nid_postfix(nullptr, &nameValue, &nameLength, nullptr, 0))); + Require(fails(sysctlbyname_nid_postfix(crossing, &nameValue, &nameLength, nullptr, 0))); + Require(sysctlbyname_nid_postfix("hw.ncpu", &nameValue, &nameLength, nullptr, 0) == 0 && nameValue > 0); + Require(fails(sysctlbyname_nid_postfix("hw.ncpu", &nameValue, reinterpret_cast(0x10), nullptr, 0))); + Require(fails(sysctlbyname_nid_postfix("hw.ncpu", reinterpret_cast(0x10), &nameLength, nullptr, 0))); + const int mib[] = {6, 3}; + const int absent[] = {1, 6}; + const auto unreadable = reinterpret_cast(0x10); + const auto badLength = reinterpret_cast(0x10); + int value = 0; + std::size_t length = sizeof(value); + Require(fails(sysctl_nid_postfix(unreadable, 2, &value, &length, nullptr, 0))); + Require(fails(sysctl_nid_postfix(nullptr, 2, &value, &length, nullptr, 0))); + Require(fails(sysctl_nid_postfix(mib, 2, &value, badLength, nullptr, 0))); + Require(fails(sysctl_nid_postfix(absent, 2, &value, badLength, nullptr, 0))); + Require(fails(sysctl_nid_postfix(mib, 2, reinterpret_cast(0x10), &length, nullptr, 0)) && length == sizeof(value)); + Require(fails(sysctl_nid_postfix(mib, 2, nullptr, badLength, nullptr, 0))); + Require(fails(sysctl_nid_postfix(mib, 2, &value, readOnlyLength, nullptr, 0)) && value != 0); + Require(fails(sysctl_nid_postfix(mib, 2, nullptr, readOnlyLength, nullptr, 0))); + length = 0; + Require(sysctl_nid_postfix(mib, 2, reinterpret_cast(0x10), &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12 && length == 0); + Require(sysctl_nid_postfix(unreadable, 1, &value, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); + Require(sysctl_nid_postfix(unreadable, 25, &value, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 22); + Require(sysctl_nid_postfix(absent, 2, &value, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 2); + const int arandom[] = {1, 37}; + std::uint8_t bytes[8] = {}; + length = sizeof(bytes); + Require(fails(sysctl_nid_postfix(arandom, 2, reinterpret_cast(0x10), &length, nullptr, 0)) && length == sizeof(bytes)); + Require(fails(sysctl_nid_postfix(arandom, 2, bytes, badLength, nullptr, 0))); + Require(sysctl_nid_postfix(arandom, 2, bytes, &length, nullptr, 0) == 0 && length == sizeof(bytes)); +} + +static void CheckSysconfMeasuredNames() { + struct Entry { + int name; + std::int64_t value; + }; + static constexpr Entry constants[] = { + {6, 1}, {7, -1}, {8, 200112}, {9, 99}, {10, 2048}, {11, 99}, {12, 1000}, {13, 0}, {14, 32}, + {15, 2048}, {16, 255}, {17, 199212}, {18, 200112}, {19, -1}, {20, 1}, {21, -1}, {22, 200112}, + {23, -1}, {24, -1}, {25, 200112}, {28, 200112}, {29, 200112}, {30, -1}, {31, 200112}, {32, 200112}, + {33, 200112}, {34, -1}, {35, 200112}, {36, 200112}, {37, 200112}, {38, 200112}, {39, 200112}, + {40, -1}, {41, 200112}, {42, 16}, {43, 1024}, {44, 0}, {45, 2147483647}, {46, -1}, {48, 62}, + {49, 30}, {50, 2147483647}, {51, 128}, {52, 32}, {59, -1}, {60, -1}, {61, -1}, {62, -1}, {63, -1}, + {64, -1}, {65, 200112}, {66, 200112}, {67, -1}, {68, 200112}, {74, 200112}, {75, 64}, {76, 200112}, + {77, 1}, {78, 1}, {79, 200112}, {80, 200112}, {81, -1}, {82, 200112}, {83, 200112}, {84, 200112}, + {85, 4}, {86, 256}, {87, 200112}, {88, 200112}, {89, 200112}, {90, -1}, {91, -1}, {93, 16384}, + {94, -1}, {95, 200112}, {96, 200112}, {97, -1}, {101, 255}, {102, -1}, {103, -1}, {104, -1}, + {105, 1}, {106, -1}, {107, 64}, {108, -1}, {109, -1}, {110, -1}, {111, -1}, {112, -1}, {113, -1}, + {114, -1}, {115, -1}, {118, 200112}, {119, 200112}, {120, 32}, {122, 8}, + {1, 262144}, {3, 128}, {4, 1023}, {47, 16384}, {56, 1024}, {72, 255}, {73, 33}, + }; + for (const Entry& entry : constants) { + *__error_nid_postfix() = 77; + Require(sysconf_nid_postfix(entry.name) == entry.value); + Require(*__error_nid_postfix() == 77); + } + *__error_nid_postfix() = 77; + Require(sysconf_nid_postfix(27) == -1); + Require(*__error_nid_postfix() == 2); + static constexpr int rejected[] = {-1, 0, 53, 54, 55, 69, 70, 71, 92, 98, 99, 100, 116, 117, 123, 124, 125, 126, 127, 128, 129, 130, 150, 200, 201, 1000}; + for (int name : rejected) { + *__error_nid_postfix() = 77; + Require(sysconf_nid_postfix(name) == -1); + Require(*__error_nid_postfix() == 22); + } +} int main() { CheckUnknownRootsAndTopLevelNames(); CheckProcessorCountSysctl(); + CheckSysctlPointerFaults(); CheckPageSizeSysctl(); CheckPhysicalMemorySysctl(5, "hw.physmem"); CheckPhysicalMemorySysctl(12, "hw.realmem"); @@ -1040,6 +1126,7 @@ int main() { CheckEnvSysctl(); CheckPseudoRandomMibSysctl(); CheckNameToMib(); + CheckSysconfMeasuredNames(); *__error_nid_postfix() = 13; Require(sysconf_nid_postfix(1) == 262144); Require(sysconf_nid_postfix(3) == 128); diff --git a/core/libs/tests/GuestSystemGesture.cpp b/core/libs/tests/GuestSystemGesture.cpp index 23970e8b82..54eb9642fd 100644 --- a/core/libs/tests/GuestSystemGesture.cpp +++ b/core/libs/tests/GuestSystemGesture.cpp @@ -277,5 +277,10 @@ int main() { for (std::size_t index = 1; index < otherHandles.size(); ++index) Require(sceSystemGestureClose(otherHandles[index]) == 0); Require(sceSystemGestureClose(handle) == 0); Require(sceSystemGestureFinalizePrimitiveTouchRecognizer() == 0); + Require(sceSystemGestureOpen(0, nullptr) == static_cast(0x80890003)); + Require(sceSystemGestureClose(handle) == static_cast(0x80890003)); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == static_cast(0x80890003)); + Require(sceSystemGestureAppendTouchRecognizer(handle, &recognizer) == static_cast(0x80890003)); + Require(sceSystemGestureFinalizePrimitiveTouchRecognizer() == 0); return 0; } diff --git a/core/libs/tests/GuestSystemServiceUsbStorage.cpp b/core/libs/tests/GuestSystemServiceUsbStorage.cpp new file mode 100644 index 0000000000..0c1153aca2 --- /dev/null +++ b/core/libs/tests/GuestSystemServiceUsbStorage.cpp @@ -0,0 +1,128 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" +#include "prx/libSceUsbStorage/UsbStorage.hpp" +#include "prx/libSceUsbStorage/include/HostVolumes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void APS5_VABI CallbackStub(const UsbStorageEvent*, void*) {} + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "SYSTEMSERVICE USBSTORAGE: %s\n", message); + std::abort(); + } +} + +void WriteFile(const std::filesystem::path& path, std::string_view text) { + std::filesystem::create_directories(path.parent_path()); + std::ofstream file(path, std::ios::binary); + file << text; + file.close(); + Require(static_cast(file), "write a fake host file"); +} + +std::string Utf8(const std::filesystem::path& path) { + const auto text = path.u8string(); + return std::string(text.begin(), text.end()); +} + +void SetPassthrough(bool enabled) { +#ifdef _WIN32 + Require(_putenv_s("APS5_USB_STORAGE_PASSTHROUGH", enabled ? "1" : "") == 0, "set the passthrough switch"); +#else + Require((enabled ? ::setenv("APS5_USB_STORAGE_PASSTHROUGH", "1", 1) : ::unsetenv("APS5_USB_STORAGE_PASSTHROUGH")) == 0, "set the passthrough switch"); +#endif +} + +void TestNoDevice() { + Require(sceSystemServiceUsbStorageInit(nullptr) == 0, "initialize through the alias"); + std::uint32_t devices[UsbStorage::MaxDevices]{}; + std::int32_t count = -1; + Require(sceSystemServiceUsbStorageGetDeviceList(devices, &count) == 0 && count == 0, "the alias lists no device"); + Require(sceSystemServiceUsbStorageGetDeviceList(nullptr, &count) == UsbStorage::InvalidArgument, "the alias refuses a null device list"); + UsbStorageDeviceInfo info{}; + Require(sceSystemServiceUsbStorageGetDeviceInfo(0, &info) == UsbStorage::DeviceNotFound, "the alias finds no device info"); + std::uint8_t exists = 1; + Require(sceSystemServiceUsbStorageIsExist(0, "/mnt/usb0", &exists) == true && exists == 0, "the alias reports no device"); + char mountPoint[UsbStorage::MountPointSize]; + std::uint64_t mountId = 0; + Require(sceSystemServiceUsbStorageRequestMap(0, "/", 0, 0, mountPoint, &mountId) == UsbStorage::DeviceNotFound, "the alias maps no device"); + Require(sceSystemServiceUsbStorageRequestUnmap(0, nullptr) == UsbStorage::InvalidArgument, "the alias refuses a null unmap"); + std::int32_t fd = -1; + std::uint64_t bufferSize = 0; + Require(sceSystemServiceUsbStorageGetdentsOpen(0, &fd, &bufferSize) == UsbStorage::DeviceNotFound, "the alias opens no scan"); + std::uint8_t buffer[64]; + std::uint64_t bytesRead = 1; + Require(sceSystemServiceUsbStorageGetdentsRead(1, buffer, sizeof(buffer), &bytesRead) == UsbStorage::NotSupported && bytesRead == 0, + "the alias reads no scan"); + Require(sceSystemServiceUsbStorageRegisterCallback(1, &CallbackStub, nullptr) == 0, "register a callback through the alias"); + Require(sceSystemServiceUsbStorageRegisterCallback(3, &CallbackStub, nullptr) == UsbStorage::InvalidArgument, "the alias refuses event type 3"); + Require(sceUsbStorageUnregisterCallback(1, &CallbackStub) == 0, "the alias registered with the USB storage service"); + Require(sceSystemServiceUsbStorageRegisterCallback(2, &CallbackStub, nullptr) == 0, "register an unmount callback through the alias"); + Require(sceSystemServiceUsbStorageUnregisterCallback(2, &CallbackStub) == 0, "unregister a callback through the alias"); + Require(sceSystemServiceUsbStorageUnregisterCallback(0, &CallbackStub) == UsbStorage::InvalidArgument, "the alias refuses an unregistration of event type 0"); + Require(sceSystemServiceUsbStorageRegisterCallbackForMapAvailable(0, nullptr, &CallbackStub, nullptr) == UsbStorage::InvalidArgument, + "the map-available alias refuses a null directory"); + Require(sceSystemServiceUsbStorageUnregisterCallbackForMapAvailable(0, "/", nullptr) == UsbStorage::InvalidArgument, + "the map-available alias refuses a null callback"); + Require(sceSystemServiceUsbStorageRegisterCallbackForMapUnavailable(0, nullptr, &CallbackStub, nullptr) == UsbStorage::InvalidArgument, + "the map-unavailable alias refuses a null directory"); + Require(sceSystemServiceUsbStorageUnregisterCallbackForMapUnavailable(0, "/", nullptr) == UsbStorage::InvalidArgument, + "the map-unavailable alias refuses a null callback"); + Require(sceSystemServiceUsbStorageTerm() == 0, "terminate through the alias"); +} + +void TestDevice() { + Require(sceSystemServiceUsbStorageInit(nullptr) == 0, "initialize through the alias with a device"); + std::uint32_t devices[UsbStorage::MaxDevices]{}; + std::int32_t count = -1; + Require(sceUsbStorageGetDeviceList(devices, &count) == 0 && count == 1 && devices[0] == 0, "the alias initialized the USB storage service"); + UsbStorageDeviceInfo info{}; + Require(sceSystemServiceUsbStorageGetDeviceInfo(0, &info) == 0 && info.vendorId == 0x0781 && info.productId == 0x5581, "the alias reads the device info"); + std::uint8_t exists = 0; + Require(sceSystemServiceUsbStorageIsExist(0, "/", &exists) == true && exists == 1, "the alias reports the root of the device"); + char mountPoint[UsbStorage::MountPointSize]{}; + std::uint64_t mountId = 1; + Require(sceSystemServiceUsbStorageRequestMap(0, "/", 0, 0, mountPoint, &mountId) == 0, "map the device through the alias"); + Require(std::string_view(mountPoint) == "/mnt/usb0" && mountId == 0, "the alias returns the mount point"); + Require(sceSystemServiceUsbStorageRequestUnmap(0, "/mnt/usb0") == 0, "unmap the device through the alias"); + std::int32_t fd = -1; + std::uint64_t bufferSize = 0; + Require(sceSystemServiceUsbStorageGetdentsOpen(0, &fd, &bufferSize) == 0 && bufferSize == 0x8000, "open a scan through the alias"); + std::uint8_t buffer[0x100]{}; + std::uint64_t bytesRead = 0; + Require(sceSystemServiceUsbStorageGetdentsRead(fd, buffer, sizeof(buffer), &bytesRead) == 0 && bytesRead == 24, "read the scan through the alias"); + Require(std::string_view(reinterpret_cast(buffer + 8)) == "song.txt", "the alias scan lists the device root"); + Require(sceSystemServiceUsbStorageGetdentsClose(fd) == 0, "close the scan through the alias"); + Require(sceSystemServiceUsbStorageTerm() == 0, "terminate through the alias with a device"); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5_system_service_usb_storage_" + std::to_string(std::random_device{}())); + WriteFile(root / "drive" / "song.txt", "song"); + static const UsbStorageTestVolume none{}; + SetUsbStorageHostVolumesForTest_nid_no_patch(&none, 0); + TestNoDevice(); + const auto drive = Utf8(root / "drive"); + const UsbStorageTestVolume volume{"drive", drive.c_str(), 61530439680ull, 0x0781, 0x5581, 0x0100, "SanDisk", "Ultra Fit", "4C530001", false, false}; + SetPassthrough(true); + SetUsbStorageHostVolumesForTest_nid_no_patch(&volume, 1); + TestDevice(); + SetUsbStorageHostVolumesForTest_nid_no_patch(nullptr, 0); + std::error_code error; + std::filesystem::remove_all(root, error); + return 0; +} diff --git a/core/libs/tests/GuestTextToSpeech2.cpp b/core/libs/tests/GuestTextToSpeech2.cpp index 13cab7e16c..c6ba365c6e 100644 --- a/core/libs/tests/GuestTextToSpeech2.cpp +++ b/core/libs/tests/GuestTextToSpeech2.cpp @@ -8,10 +8,25 @@ int main() { constexpr std::int32_t unsupported = static_cast(0x8002002D); const std::uint32_t param[16]{0x2000000, 0x26c}; - if (sceTextToSpeech2Initialize(reinterpret_cast(param)) != unsupported) { + if (sceTextToSpeech2Initialize(reinterpret_cast(param)) != unsupported || + sceTextToSpeech2Initialize(nullptr) != unsupported) { std::puts("TextToSpeech2: initialization did not report the unsupported operation"); std::abort(); } + + std::int32_t status = 0x55AA; + if (sceTextToSpeech2GetSystemStatus(&status) != unsupported || status != 0x55AA || + sceTextToSpeech2GetSpeechStatus(&status) != unsupported || status != 0x55AA) { + std::puts("TextToSpeech2: status queries did not preserve the output value"); + std::abort(); + } + + const TextToSpeechOpenParameter openParam{}; + const TextToSpeechSpeakParameter speakParam{}; + if (sceTextToSpeech2Open(&openParam) != unsupported || sceTextToSpeech2Speak(&speakParam) != unsupported) { + std::puts("TextToSpeech2: configured open or speak did not report the unsupported operation"); + std::abort(); + } if (sceTextToSpeech2Open(nullptr) != unsupported || sceTextToSpeech2Cancel() != unsupported || sceTextToSpeech2Close() != unsupported || sceTextToSpeech2GetSpeechStatus(nullptr) != unsupported || sceTextToSpeech2Speak(nullptr) != unsupported || sceTextToSpeech2Terminate() != unsupported || diff --git a/core/libs/tests/GuestUltSync.cpp b/core/libs/tests/GuestUltSync.cpp index f8e54452d0..601110ab9c 100644 --- a/core/libs/tests/GuestUltSync.cpp +++ b/core/libs/tests/GuestUltSync.cpp @@ -63,6 +63,23 @@ void MutexTryLock() { CHECK(sceUltMutexTryLock(&mutex) == State); } +void MutexAndRuntimeOptParams() { + alignas(8) std::uint8_t misaligned[sizeof(UltMutexOptParam) + 8]; + CHECK(sceUltMutexOptParamInitialize(nullptr, 0) == Null); + CHECK(sceUltMutexOptParamInitialize(reinterpret_cast(misaligned + 4), 0) == -2139029502); + UltMutexOptParam mutexOptions; + std::memset(&mutexOptions, 0xa5, sizeof(mutexOptions)); + CHECK(sceUltMutexOptParamInitialize(&mutexOptions, 0x1234) == Ok); + CHECK(mutexOptions.header[0] == 0x6C696275 && mutexOptions.header[1] == 0x6C746D61 && mutexOptions.header[2] == 0x67696300); + CHECK(mutexOptions.buildVersion == 0x1234 && mutexOptions.attribute == 0); + for (const std::uint32_t word : mutexOptions.reserved) CHECK(word == 0); + UltUlthreadRuntimeOptParam runtimeOptions; + std::memset(&runtimeOptions, 0xa5, sizeof(runtimeOptions)); + CHECK(sceUltUlthreadRuntimeOptParamInitialize(nullptr, 0) == Null); + CHECK(sceUltUlthreadRuntimeOptParamInitialize(&runtimeOptions, 0) == Ok); + for (const std::uint8_t byte : runtimeOptions.bytes) CHECK(byte == 0); +} + void OptParams() { using Initializer = decltype(&sceUltSemaphoreOptParamInitialize); for (const Initializer initialize : {sceUltSemaphoreOptParamInitialize, sceUltQueueOptParamInitialize, sceUltQueueDataResourcePoolOptParamInitialize, @@ -677,7 +694,10 @@ int main(int argc, char** argv) { if (argc != 2) return 2; CHECK(sceUltInitialize() == Ok); if (std::strcmp(argv[1], "mutex-trylock") == 0) MutexTryLock(); - else if (std::strcmp(argv[1], "opt-params") == 0) OptParams(); + else if (std::strcmp(argv[1], "opt-params") == 0) { + OptParams(); + MutexAndRuntimeOptParams(); + } else if (std::strcmp(argv[1], "rwlock-validation") == 0) ReaderWriterValidation(); else if (std::strcmp(argv[1], "rwlock-readers") == 0) ReaderWriterReaders(); else if (std::strcmp(argv[1], "rwlock-writer") == 0) ReaderWriterWriter(); diff --git a/core/libs/tests/GuestUsbStorage.cpp b/core/libs/tests/GuestUsbStorage.cpp index 4013c5af5f..8732ce9e14 100644 --- a/core/libs/tests/GuestUsbStorage.cpp +++ b/core/libs/tests/GuestUsbStorage.cpp @@ -279,6 +279,8 @@ void TestRegisterCallbacks() { "register a registered callback again did not throw"); Require(sceUsbStorageUnregisterCallback(1, nullptr) == UsbStorage::InvalidArgument, "unregister a null callback"); Require(sceUsbStorageUnregisterCallback(0, Callback()) == UsbStorage::InvalidArgument, "unregister callback of event type 0"); + Require(sceUsbStorageUnregisterCallback(3, Callback()) == UsbStorage::InvalidArgument, "unregister callback of event type 3"); + Require(sceUsbStorageUnregisterCallback(-1, Callback()) == UsbStorage::InvalidArgument, "unregister callback of a negative event type"); Require(sceUsbStorageUnregisterCallback(1, Callback()) == 0, "unregister the mount callback"); Require(sceUsbStorageUnregisterCallback(2, Callback()) == 0, "unregister the unmount callback"); Require(ThrowsWith([] { sceUsbStorageUnregisterCallback(1, Callback()); }, "sceUsbStorageUnregisterCallback not implemented"), @@ -309,6 +311,7 @@ void TestNoDevice() { Require(devices[0] == 0x5a5a5a5a && devices[3] == 0x5a5a5a5a, "the device list was written"); Require(sceUsbStorageGetDeviceList(nullptr, &count) == UsbStorage::InvalidArgument, "get the device list without a device array"); Require(sceUsbStorageGetDeviceList(devices, nullptr) == UsbStorage::InvalidArgument, "get the device list without a count"); + Require(sceUsbStorageGetDeviceList(nullptr, nullptr) == UsbStorage::InvalidArgument, "get device list without buffers"); UsbStorageDeviceInfo info; std::memset(&info, 0x5a, sizeof(info)); @@ -355,6 +358,7 @@ void TestGetdents() { Require(fd == -1 && bufferSize == 0x5a5a5a5a5a5a5a5aull, "the getdents open wrote its outputs"); Require(sceUsbStorageGetdentsOpen(0, nullptr, &bufferSize) == UsbStorage::InvalidArgument, "open a getdents scan without a descriptor"); Require(sceUsbStorageGetdentsOpen(0, &fd, nullptr) == UsbStorage::InvalidArgument, "open a getdents scan without a buffer size"); + Require(sceUsbStorageGetdentsOpen(0, nullptr, nullptr) == UsbStorage::InvalidArgument, "open getdents scan without descriptor and buffer size"); std::uint8_t buffer[64]; std::uint64_t bytesRead = 0x5a5a5a5a5a5a5a5aull; @@ -364,6 +368,7 @@ void TestGetdents() { Require(buffer[0] == 0x5a, "the getdents read wrote the buffer"); Require(sceUsbStorageGetdentsRead(1, nullptr, sizeof(buffer), &bytesRead) == UsbStorage::InvalidArgument, "read a getdents scan without a buffer"); Require(sceUsbStorageGetdentsRead(1, buffer, sizeof(buffer), nullptr) == UsbStorage::InvalidArgument, "read a getdents scan without a byte count"); + Require(sceUsbStorageGetdentsRead(1, nullptr, sizeof(buffer), nullptr) == UsbStorage::InvalidArgument, "read getdents scan without buffer and byte count"); Require(ThrowsWith([] { sceUsbStorageGetdentsClose(1); }, "sceUsbStorageGetdentsClose not implemented"), "close a getdents scan that was never opened did not throw its marker"); diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index eb6cc9af44..0e8f5ecee6 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -241,8 +241,38 @@ void Device() { } +void CheckTransferFillers() { + auto* transfer = sceUsbdAllocTransfer(3); + Require(transfer != nullptr, "transfer allocation failed"); + std::uint8_t data[64] = {}; + int device = 0; + int user = 0; + sceUsbdFillBulkTransfer(transfer, &device, 0x02, data, 64, &MarkDone, &user, 500); + Require(transfer->deviceHandle == &device && transfer->endpoint == 0x02 && transfer->type == 2 && transfer->timeout == 500, "bulk transfer target not filled"); + Require(transfer->buffer == data && transfer->length == 64 && transfer->userData == &user && transfer->callback == &MarkDone, "bulk transfer buffer or callback not filled"); + sceUsbdFillIsoTransfer(transfer, &device, 0x83, data, 48, 3, &MarkDone, &user, 0); + Require(transfer->endpoint == 0x83 && transfer->type == 1 && transfer->length == 48 && transfer->numIsoPackets == 3, "iso transfer not filled"); + sceUsbdSetIsoPacketLengths(transfer, 16); + for (int packet = 0; packet < 3; ++packet) Require(transfer->isoPacketDescriptors[packet].length == 16, "iso packet length not set"); + Require(sceUsbdGetIsoPacketBuffer(transfer, 0) == data && sceUsbdGetIsoPacketBuffer(transfer, 1) == data + 16 && sceUsbdGetIsoPacketBuffer(transfer, 2) == data + 32, "iso packet buffers do not follow the packet lengths"); + Require(sceUsbdGetIsoPacketBuffer(transfer, 3) == nullptr, "a packet past the last one has a buffer"); + std::uint8_t control[12] = {}; + sceUsbdFillControlSetup(control, 0x80, 6, 0x0100, 0x0409, 4); + Require(control[0] == 0x80 && control[1] == 6 && control[2] == 0x00 && control[3] == 0x01 && control[4] == 0x09 && control[5] == 0x04 && control[6] == 4 && control[7] == 0, "control setup not packed little-endian"); + sceUsbdFillControlTransfer(transfer, &device, control, &MarkDone, &user, 100); + Require(transfer->type == 0 && transfer->endpoint == 0 && transfer->length == 12 && transfer->buffer == control && transfer->timeout == 100, "control transfer not filled from its setup"); + Require(sceUsbdControlTransferGetSetup(transfer) == control && sceUsbdControlTransferGetData(transfer) == control + 8, "control setup or data pointer wrong"); + sceUsbdFillControlTransfer(transfer, &device, nullptr, &MarkDone, &user, 100); + Require(transfer->length == 0 && sceUsbdControlTransferGetData(transfer) == nullptr, "a control transfer without a buffer has data"); + Require(Throws([] { sceUsbdFillBulkTransfer(nullptr, nullptr, 0, nullptr, 0, nullptr, nullptr, 0); }), "filling a null bulk transfer did not throw"); + Require(Throws([] { sceUsbdFillControlSetup(nullptr, 0, 0, 0, 0, 0); }), "filling a null control setup did not throw"); + Require(Throws([] { sceUsbdGetIsoPacketBuffer(nullptr, 0); }), "the packet buffer of a null transfer did not throw"); + sceUsbdFreeTransfer(transfer); +} + int main(int argc, char** argv) { Require(sceUsbdInit() == 0, "initialization failed"); + CheckTransferFillers(); void** list = nullptr; const std::int64_t count = sceUsbdGetDeviceList(&list); Require(count >= 0 && list != nullptr && list[count] == nullptr, "device list is not a null-terminated array of the reported length"); diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 9b46dffc13..9b8ce9d612 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -33,6 +33,10 @@ int main() { Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID, &privacy) == USER_SERVICE_OK); Require(privacy == 0); + privacy = -1; + Require(sceUserServiceGetPlatformPrivacyWs1(123, &privacy) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(privacy == -1); + Require(sceUserServiceGetPlatformPrivacyWs1(123, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); int foreground = -1; Require(sceUserServiceGetForegroundUser(&foreground) == USER_SERVICE_OK); @@ -107,6 +111,10 @@ int main() { Require(sceUserServiceGetAgeLevel(user_id, &age_level) == USER_SERVICE_ERROR_NOT_LOGGED_IN); Require(age_level == 0x5a5a5a5au); Require(sceUserServiceGetAgeLevel(user_id, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + privacy = 0x5a5a; + Require(sceUserServiceGetPlatformPrivacyWs1(user_id, &privacy) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(privacy == 0x5a5a); + Require(sceUserServiceGetPlatformPrivacyWs1(user_id, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); } Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID, &age_level) == USER_SERVICE_OK); Require(age_level == 0xffffffffu); @@ -181,6 +189,10 @@ int main() { Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_ERROR_NOT_LOGGED_IN); Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + privacy = 0x5a5a; + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID + 1, &privacy) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(privacy == 0x5a5a); + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); for (const Setting& setting : settings) { std::int32_t value = 0x5a5a5a5a; Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, &value) == USER_SERVICE_ERROR_NOT_LOGGED_IN); @@ -197,6 +209,10 @@ int main() { } Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_OK); Require(age_level == 0xffffffffu); + privacy = 0x5a5a; + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID + 1, &privacy) == USER_SERVICE_OK); + Require(privacy == 0); for (const Setting& setting : settings) { std::int32_t value = 0x5a5a5a5a; Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, &value) == USER_SERVICE_OK); @@ -211,6 +227,10 @@ int main() { } Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_ERROR_NOT_LOGGED_IN); Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + privacy = 0x5a5a; + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID + 1, &privacy) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(privacy == 0x5a5a); + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); for (const Setting& setting : settings) { std::int32_t value = 0x5a5a5a5a; Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, &value) == USER_SERVICE_ERROR_NOT_LOGGED_IN); diff --git a/core/libs/tests/GuestVectorAlgorithms.cpp b/core/libs/tests/GuestVectorAlgorithms.cpp index d9c21878c3..a09e46da55 100644 --- a/core/libs/tests/GuestVectorAlgorithms.cpp +++ b/core/libs/tests/GuestVectorAlgorithms.cpp @@ -82,6 +82,44 @@ static void CheckSizedHelpers() { Require(__std_max_element_8_nid_postfix(wides, wides, false) == wides); } +static void CheckSignedMin64Value() { + const std::int64_t edges[] = {INT64_MIN, INT64_MIN + 1, -0x100000001ll, -0x100000000ll, -1, 0, 1, 0x7fffffff, 0xffffffff, 0x100000000, INT64_MAX - 1, INT64_MAX}; + for (const auto& left : edges) { + Require(__std_min_8i_nid_postfix(&left, &left + 1) == left); + for (const auto& right : edges) { + const std::int64_t pair[] = {left, right}; + Require(__std_min_8i_nid_postfix(pair, pair + 2) == std::min(left, right)); + } + } + std::uint64_t state = 0xf3c82d51479ab6e1; + for (const std::size_t count : {1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 257}) { + std::vector values(count); + for (auto& value : values) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + std::memcpy(&value, &state, sizeof(value)); + } + const auto expected = *std::min_element(values.begin(), values.end()); + for (std::size_t offset = 0; offset < 32; ++offset) { + std::vector storage(count * sizeof(std::int64_t) + 64, 0xff); + std::memcpy(storage.data() + offset, values.data(), count * sizeof(std::int64_t)); + const auto original = storage; + Require(__std_min_8i_nid_postfix(storage.data() + offset, storage.data() + offset + count * sizeof(std::int64_t)) == expected); + Require(storage == original); + } + std::fill(values.begin(), values.end(), INT64_MAX); + Require(__std_min_8i_nid_postfix(values.data(), values.data() + count) == INT64_MAX); + for (std::size_t position = 0; position < count; ++position) { + values[position] = -0x100000005ll; + Require(__std_min_8i_nid_postfix(values.data(), values.data() + count) == -0x100000005ll); + values[position] = INT64_MAX; + } + } + const std::int64_t repeated[] = {-9, -9, -9, -9}; + Require(__std_min_8i_nid_postfix(repeated, repeated + 4) == -9); +} + 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); @@ -95,6 +133,44 @@ static void CheckFindAndCount() { Require(__std_find_trivial_unsized_4_nid_postfix(words + 8, 42) == words + 12); } +static void CheckSignedMax64Value() { + const std::int64_t edges[] = {INT64_MIN, INT64_MIN + 1, -0x100000001ll, -0x100000000ll, -1, 0, 1, 0x7fffffff, 0xffffffff, 0x100000000, INT64_MAX - 1, INT64_MAX}; + for (const auto& left : edges) { + Require(__std_max_8i_nid_postfix(&left, &left + 1) == left); + for (const auto& right : edges) { + const std::int64_t pair[] = {left, right}; + Require(__std_max_8i_nid_postfix(pair, pair + 2) == std::max(left, right)); + } + } + std::uint64_t state = 0x2e791fa84d6c530b; + for (const std::size_t count : {1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 257}) { + std::vector values(count); + for (auto& value : values) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + std::memcpy(&value, &state, sizeof(value)); + } + const auto expected = *std::max_element(values.begin(), values.end()); + for (std::size_t offset = 0; offset < 32; ++offset) { + std::vector storage(count * sizeof(std::int64_t) + 64, 0xff); + std::memcpy(storage.data() + offset, values.data(), count * sizeof(std::int64_t)); + const auto original = storage; + Require(__std_max_8i_nid_postfix(storage.data() + offset, storage.data() + offset + count * sizeof(std::int64_t)) == expected); + Require(storage == original); + } + std::fill(values.begin(), values.end(), INT64_MIN); + Require(__std_max_8i_nid_postfix(values.data(), values.data() + count) == INT64_MIN); + for (std::size_t position = 0; position < count; ++position) { + values[position] = -0x100000005ll; + Require(__std_max_8i_nid_postfix(values.data(), values.data() + count) == -0x100000005ll); + values[position] = INT64_MIN; + } + } + const std::int64_t repeated[] = {-9, -9, -9, -9}; + Require(__std_max_8i_nid_postfix(repeated, repeated + 4) == -9); +} + static void CheckUnsizedRescan() { alignas(32) unsigned char bytes[96] = {}; bytes[3] = 'x'; @@ -137,6 +213,44 @@ static void CheckUnsizedRescan() { Require(__std_find_trivial_unsized_8_nid_postfix(wides + 1, 99) == wides + 6); } +static void CheckUnsignedMax32Value() { + const std::uint32_t edges[] = {0, 1, 0xffff, 0x10000, 0x7fffffff, 0x80000000, 0xfffffffe, 0xffffffff}; + for (const auto& left : edges) { + Require(__std_max_4u_nid_postfix(&left, &left + 1) == left); + for (const auto& right : edges) { + const std::uint32_t pair[] = {left, right}; + Require(__std_max_4u_nid_postfix(pair, pair + 2) == std::max(left, right)); + } + } + std::uint32_t state = 0x8e47396b; + for (const std::size_t count : {1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 257}) { + std::vector values(count); + for (auto& value : values) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + value = state; + } + const auto expected = *std::max_element(values.begin(), values.end()); + for (std::size_t offset = 0; offset < 32; ++offset) { + std::vector storage(count * sizeof(std::uint32_t) + 64, 0xff); + std::memcpy(storage.data() + offset, values.data(), count * sizeof(std::uint32_t)); + const auto original = storage; + Require(__std_max_4u_nid_postfix(storage.data() + offset, storage.data() + offset + count * sizeof(std::uint32_t)) == expected); + Require(storage == original); + } + std::fill(values.begin(), values.end(), 0); + Require(__std_max_4u_nid_postfix(values.data(), values.data() + count) == 0); + for (std::size_t position = 0; position < count; ++position) { + values[position] = 0x10005; + Require(__std_max_4u_nid_postfix(values.data(), values.data() + count) == 0x10005); + values[position] = 0; + } + } + const std::uint32_t repeated[] = {9, 9, 9, 9}; + Require(__std_max_4u_nid_postfix(repeated, repeated + 4) == 9); +} + 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); @@ -155,6 +269,80 @@ static void CheckMinMax() { 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); + + const std::int32_t edges[] = {INT32_MIN, INT32_MIN + 1, -0x10001, -0x10000, -1, 0, 1, 0xffff, 0x10000, INT32_MAX - 1, INT32_MAX}; + for (const auto& left : edges) { + Require(__std_min_4i_nid_postfix(&left, &left + 1) == left); + for (const auto& right : edges) { + const std::int32_t pair[] = {left, right}; + Require(__std_min_4i_nid_postfix(pair, pair + 2) == std::min(left, right)); + } + } + std::uint32_t state = 0xa9f28537; + for (const std::size_t count : {1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 257}) { + std::vector values(count); + for (auto& value : values) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + std::memcpy(&value, &state, sizeof(value)); + } + const auto expected = *std::min_element(values.begin(), values.end()); + for (std::size_t offset = 0; offset < 32; ++offset) { + std::vector storage(count * sizeof(std::int32_t) + 64, 0xff); + std::memcpy(storage.data() + offset, values.data(), count * sizeof(std::int32_t)); + const auto original = storage; + Require(__std_min_4i_nid_postfix(storage.data() + offset, storage.data() + offset + count * sizeof(std::int32_t)) == expected); + Require(storage == original); + } + std::fill(values.begin(), values.end(), INT32_MAX); + Require(__std_min_4i_nid_postfix(values.data(), values.data() + count) == INT32_MAX); + for (std::size_t position = 0; position < count; ++position) { + values[position] = -0x10005; + Require(__std_min_4i_nid_postfix(values.data(), values.data() + count) == -0x10005); + values[position] = INT32_MAX; + } + } + const std::int32_t repeated[] = {-9, -9, -9, -9}; + Require(__std_min_4i_nid_postfix(repeated, repeated + 4) == -9); +} + +static void CheckUnsignedMin32Value() { + const std::uint32_t edges[] = {0, 1, 0xffff, 0x10000, 0x7fffffff, 0x80000000, 0xfffffffe, 0xffffffff}; + for (const auto& left : edges) { + Require(__std_min_4u_nid_postfix(&left, &left + 1) == left); + for (const auto& right : edges) { + const std::uint32_t pair[] = {left, right}; + Require(__std_min_4u_nid_postfix(pair, pair + 2) == std::min(left, right)); + } + } + std::uint32_t state = 0x8e47396b; + for (const std::size_t count : {1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 257}) { + std::vector values(count); + for (auto& value : values) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + value = state; + } + const auto expected = *std::min_element(values.begin(), values.end()); + for (std::size_t offset = 0; offset < 32; ++offset) { + std::vector storage(count * sizeof(std::uint32_t) + 64, 0xff); + std::memcpy(storage.data() + offset, values.data(), count * sizeof(std::uint32_t)); + const auto original = storage; + Require(__std_min_4u_nid_postfix(storage.data() + offset, storage.data() + offset + count * sizeof(std::uint32_t)) == expected); + Require(storage == original); + } + std::fill(values.begin(), values.end(), UINT32_MAX); + Require(__std_min_4u_nid_postfix(values.data(), values.data() + count) == UINT32_MAX); + for (std::size_t position = 0; position < count; ++position) { + values[position] = 0x10005; + Require(__std_min_4u_nid_postfix(values.data(), values.data() + count) == 0x10005); + values[position] = UINT32_MAX; + } + } + const std::uint32_t repeated[] = {9, 9, 9, 9}; + Require(__std_min_4u_nid_postfix(repeated, repeated + 4) == 9); } static void CheckReverseAndSwap() { @@ -177,12 +365,48 @@ static void CheckReverseAndSwap() { for (int i = 0; i < 37; ++i) Require(left[i] == 100 + i && right[i] == i); } +static void CheckMaxValue() { + const std::uint64_t edges[] = {0, 1, 0xffffffff, 0x100000000, 0x7fffffffffffffff, 0x8000000000000000, 0xfffffffffffffffe, 0xffffffffffffffff}; + for (const auto& left : edges) { + Require(__std_max_8u_nid_postfix(&left, &left + 1) == left); + for (const auto& right : edges) { + const std::uint64_t pair[] = {left, right}; + Require(__std_max_8u_nid_postfix(pair, pair + 2) == std::max(left, right)); + } + } + std::uint64_t state = 0x7698f1b33a25c04d; + for (const std::size_t count : {1, 2, 3, 4, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 257}) { + std::vector values(count); + for (auto& value : values) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + value = state; + } + const auto expected = *std::max_element(values.begin(), values.end()); + for (std::size_t offset = 0; offset < 16; ++offset) { + std::vector storage(count * sizeof(std::uint64_t) + 32, 0xff); + std::memcpy(storage.data() + offset, values.data(), count * sizeof(std::uint64_t)); + const auto original = storage; + Require(__std_max_8u_nid_postfix(storage.data() + offset, storage.data() + offset + count * sizeof(std::uint64_t)) == expected); + Require(storage == original); + } + } + const std::uint64_t repeated[] = {9, 9, 9, 9}; + Require(__std_max_8u_nid_postfix(repeated, repeated + 4) == 9); +} + int main() { CheckMinValue(); CheckSizedHelpers(); + CheckUnsignedMax32Value(); CheckFindAndCount(); + CheckUnsignedMin32Value(); CheckUnsizedRescan(); + CheckSignedMax64Value(); CheckMinMax(); + CheckMaxValue(); CheckReverseAndSwap(); + CheckSignedMin64Value(); return 0; } diff --git a/core/libs/tests/GuestVideoOutEventId.cpp b/core/libs/tests/GuestVideoOutEventId.cpp new file mode 100644 index 0000000000..73a9c3672b --- /dev/null +++ b/core/libs/tests/GuestVideoOutEventId.cpp @@ -0,0 +1,88 @@ +#include "prx/libSceVideoOut/include/Event.hpp" +#include "prx/libkernel/Equeue/Equeue.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int InvalidEvent = static_cast(0x8029000Du); +constexpr int FlipEvent = 0; +constexpr std::int16_t GraphicsCoreFilter = -14; +constexpr std::int64_t Unwritten = 0x5a5a; + +bool Returns(const std::string& what, const std::function& call, int expected) { + try { + const int result = call(); + if (result == expected) return true; + std::fprintf(stderr, "%s returned 0x%08x, expected 0x%08x\n", what.c_str(), static_cast(result), static_cast(expected)); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s threw: %s\n", what.c_str(), error.what()); + } + return false; +} + +bool Throws(const std::string& what, const std::function& call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + std::fprintf(stderr, "%s returned instead of throwing\n", what.c_str()); + return false; +} + +bool RejectsEventOfAnotherFilter(const std::string& what, const KernelEvent& event) { + std::int64_t data = Unwritten; + bool passed = Returns("sceVideoOutGetEventId of " + what, [&] { return sceVideoOutGetEventId(&event); }, InvalidEvent); + passed = Returns("sceVideoOutGetEventData of " + what, [&] { return sceVideoOutGetEventData(&event, &data); }, InvalidEvent) && passed; + passed = Returns("sceVideoOutGetEventCount of " + what, [&] { return sceVideoOutGetEventCount(&event); }, InvalidEvent) && passed; + if (data == Unwritten) return passed; + std::fprintf(stderr, "sceVideoOutGetEventData of %s wrote 0x%llx\n", what.c_str(), static_cast(data)); + return false; +} + +} + +int main() { + KernelEvent user{}; + user.filter = EVFILT_USER; + KernelEvent graphics{}; + graphics.filter = GraphicsCoreFilter; + graphics.data = 1; + KernelEvent unknown{}; + unknown.ident = 0x77; + unknown.filter = EVFILT_VIDEO_OUT; + KernelEvent wide{}; + wide.ident = 0x100000000; + wide.filter = EVFILT_VIDEO_OUT; + KernelEvent flip{}; + flip.ident = FlipEvent; + flip.filter = EVFILT_VIDEO_OUT; + flip.data = 0x12343000; + + bool passed = RejectsEventOfAnotherFilter("a user event", user); + passed = RejectsEventOfAnotherFilter("a graphics core event", graphics) && passed; + passed = Returns("sceVideoOutGetEventId of a VideoOut event with an unknown id", [&] { return sceVideoOutGetEventId(&unknown); }, InvalidEvent) && passed; + passed = Returns("sceVideoOutGetEventId of a VideoOut event with an id above 32 bits", [&] { return sceVideoOutGetEventId(&wide); }, InvalidEvent) && passed; + + std::int64_t data = Unwritten; + passed = Returns("sceVideoOutGetEventId of a flip event", [&] { return sceVideoOutGetEventId(&flip); }, FlipEvent) && passed; + passed = Returns("sceVideoOutGetEventCount of a flip event", [&] { return sceVideoOutGetEventCount(&flip); }, 3) && passed; + passed = Returns("sceVideoOutGetEventData of a flip event", [&] { return sceVideoOutGetEventData(&flip, &data); }, 0) && passed; + if (data != 0x1234) { + std::fprintf(stderr, "sceVideoOutGetEventData of a flip event wrote 0x%llx, expected 0x1234\n", static_cast(data)); + passed = false; + } + + passed = Throws("sceVideoOutGetEventId of a null event", [] { return sceVideoOutGetEventId(nullptr); }) && passed; + passed = Throws("sceVideoOutGetEventData of a null event", [&] { return sceVideoOutGetEventData(nullptr, &data); }) && passed; + passed = Throws("sceVideoOutGetEventData with a null data pointer", [&] { return sceVideoOutGetEventData(&flip, nullptr); }) && passed; + passed = Throws("sceVideoOutGetEventCount of a null event", [] { return sceVideoOutGetEventCount(nullptr); }) && passed; + if (!passed) return 1; + std::printf("guest VideoOut event getter tests passed\n"); + return 0; +} diff --git a/core/libs/tests/GuestVrServiceDialog.cpp b/core/libs/tests/GuestVrServiceDialog.cpp index 43664c8263..d37c208519 100644 --- a/core/libs/tests/GuestVrServiceDialog.cpp +++ b/core/libs/tests/GuestVrServiceDialog.cpp @@ -66,7 +66,10 @@ int main() { Require(sceVrServiceDialogTerminate() == 0); Require(sceVrServiceDialogTerminate() == kErrNotInitialized); Require(sceVrServiceDialogOpen(nullptr) == kErrNotInitialized); + Require(sceVrServiceDialogOpen(¶m) == kErrNotInitialized); + Require(sceVrServiceDialogClose() == kErrNotInitialized); Require(sceVrServiceDialogGetResult(nullptr) == kErrNotInitialized); + Require(sceVrServiceDialogGetResult(result) == kErrNotInitialized); Require(sceVrServiceDialogGetStatus() == kStatusNone); Require(sceVrServiceDialogUpdateStatus() == kStatusNone); } diff --git a/core/libs/tests/GuestVrSetupDialog.cpp b/core/libs/tests/GuestVrSetupDialog.cpp index d08cedc1dd..4f106e22b9 100644 --- a/core/libs/tests/GuestVrSetupDialog.cpp +++ b/core/libs/tests/GuestVrSetupDialog.cpp @@ -62,6 +62,13 @@ int main() { Require(result[0] == kResultUserCanceled); Require(result[1] == -1); + Require(sceVrSetupDialogOpen(nullptr) == kErrArgNull); + param.size = 55; + Require(sceVrSetupDialogOpen(¶m) == kErrArgNull); + param.size = sizeof(param); + Require(sceVrSetupDialogOpen(¶m) == 0); + Require(sceVrSetupDialogGetStatus() == kStatusFinished); + Require(sceVrSetupDialogClose() == 0); Require(sceVrSetupDialogTerminate() == 0); Require(sceVrSetupDialogTerminate() == kErrNotInitialized); diff --git a/core/libs/tests/GuestWebBrowserDialog.cpp b/core/libs/tests/GuestWebBrowserDialog.cpp index 53ce0d3955..a103ce8ea7 100644 --- a/core/libs/tests/GuestWebBrowserDialog.cpp +++ b/core/libs/tests/GuestWebBrowserDialog.cpp @@ -127,5 +127,12 @@ int main() { Require(result.padding == 0x5a5a5a5a && result.extended == &extended); Require(extended.type == 0x5a5a5a5a && extended.content[2047] == 'x'); Require(sceWebBrowserDialogTerminate() == 0); + Require(sceWebBrowserDialogOpen(nullptr) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogOpen(param) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogClose() == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogGetResult(&result) == COMMON_DIALOG_ERROR_NOT_INITIALIZED); + Require(sceWebBrowserDialogGetResult(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); + Require(sceWebBrowserDialogGetStatus() == COMMON_DIALOG_STATUS_NONE); + Require(sceWebBrowserDialogUpdateStatus() == COMMON_DIALOG_STATUS_NONE); return 0; } diff --git a/core/libs/tests/GuestWebBrowserDialogCookies.cpp b/core/libs/tests/GuestWebBrowserDialogCookies.cpp index 32deb3db57..abaa142bc3 100644 --- a/core/libs/tests/GuestWebBrowserDialogCookies.cpp +++ b/core/libs/tests/GuestWebBrowserDialogCookies.cpp @@ -120,6 +120,12 @@ int main() { CheckResetCookieParameters(); CheckOpenDialog(); Require(sceWebBrowserDialogTerminate() == 0, "final Terminate"); + Require(sceWebBrowserDialogTerminate() == NOT_INITIALIZED, "Terminate when uninitialized"); + Require(sceWebBrowserDialogSetCookie(&cookie) == NOT_INITIALIZED, "SetCookie after Terminate"); + Require(sceWebBrowserDialogSetCookie(nullptr) == NOT_INITIALIZED, "SetCookie null after Terminate"); + Require(sceWebBrowserDialogResetCookie(&reset) == NOT_INITIALIZED, "ResetCookie after Terminate"); + Require(sceWebBrowserDialogResetCookie(nullptr) == NOT_INITIALIZED, "ResetCookie null after Terminate"); + Require(sceWebBrowserDialogGetStatus() == 0, "GetStatus after Terminate"); std::puts("guest web browser dialog cookies: PASS"); return 0; } diff --git a/core/libs/tests/GuestWideStrings.cpp b/core/libs/tests/GuestWideStrings.cpp index e31bb8ed9b..df1e7c36ee 100644 --- a/core/libs/tests/GuestWideStrings.cpp +++ b/core/libs/tests/GuestWideStrings.cpp @@ -343,7 +343,7 @@ int main() { require(wcstold_nid_postfix(half, &end) == -1.5L && end == half + 4); const char16_t* wideNan = u" -nan(0x123)é"; require(std::bit_cast(wcstod_nid_postfix(wideNan, &end)) == 0xfff8000000000123u && end == wideNan + 12); - require(std::bit_cast(wcstof_nid_postfix(wideNan, &end)) == 0xffc00000u && end == wideNan + 12); + require(std::bit_cast(wcstof_nid_postfix(wideNan, &end)) == 0xffc00123u && end == wideNan + 12); const long double wideExtended = wcstold_nid_postfix(wideNan, &end); std::uint64_t significand; std::memcpy(&significand, &wideExtended, sizeof(significand)); @@ -360,8 +360,8 @@ int main() { wmemset_nid_postfix(transformed, 0xaaaa, 8); require(wcsxfrm_nid_postfix(transformed, u"much too long", 4) == 13); - if (!same(transformed, u"muc", 4)) std::fputs("wcsxfrm did not copy and terminate its truncated result\n", stderr); - require(same(transformed, u"muc", 4) && transformed[4] == 0xaaaa); + if (!same(transformed, u"much", 4)) std::fputs("wcsxfrm did not copy the first count units of its truncated result\n", stderr); + require(same(transformed, u"much", 4) && transformed[4] == 0xaaaa); require(wcsxfrm_nid_postfix(nullptr, u"abc", 0) == 3); struct TransformCase { const char16_t* source; @@ -372,13 +372,13 @@ int main() { }; for (const TransformCase test : { TransformCase{u"wide", 0, 4, u"", 0}, - TransformCase{u"wide", 1, 4, u"", 1}, - TransformCase{u"wide", 4, 4, u"wid", 4}, + TransformCase{u"wide", 1, 4, u"w", 1}, + TransformCase{u"wide", 4, 4, u"wide", 4}, TransformCase{u"wide", 5, 4, u"wide", 5}, TransformCase{u"", 0, 0, u"", 0}, TransformCase{u"", 1, 0, u"", 1}, TransformCase{u"", 8, 0, u"", 1}, - TransformCase{u"\x7fff\x8000\xffff\xd83d\xde00", 4, 5, u"\x7fff\x8000\xffff", 4}, + TransformCase{u"\x7fff\x8000\xffff\xd83d\xde00", 4, 5, u"\x7fff\x8000\xffff\xd83d", 4}, TransformCase{u"\x7fff\x8000\xffff\xd83d\xde00", 6, 5, u"\x7fff\x8000\xffff\xd83d\xde00", 6} }) { wmemset_nid_postfix(transformed, 0xaaaa, 8); @@ -397,6 +397,10 @@ int main() { char* narrowEnd = nullptr; const char text[] = "0x1ffffffffffffffffg"; require(strtouq_nid_postfix(text, &narrowEnd, 16) == UINT64_MAX && narrowEnd == text + 19); + const char signedText[] = " -0x7fffffffffffffff9"; + require(strtoq_nid_postfix(signedText, &narrowEnd, 0) == INT64_MIN && narrowEnd == signedText + 22); + const char positiveText[] = "+42z"; + require(strtoq_nid_postfix(positiveText, &narrowEnd, 10) == 42 && narrowEnd == positiveText + 3); const char path[] = "a/b/c"; require(rindex_nid_postfix(path, '/') == path + 3 && rindex_nid_postfix(path, 'x') == nullptr); require(rindex_nid_postfix(path, 0) == path + 5); diff --git a/core/libs/tests/GuestZlib.cpp b/core/libs/tests/GuestZlib.cpp index 15ad0686d0..81586998dd 100644 --- a/core/libs/tests/GuestZlib.cpp +++ b/core/libs/tests/GuestZlib.cpp @@ -34,6 +34,9 @@ int main() { std::int32_t status = 0; Require(sceZlibFinalize() == static_cast(0x81120032)); Require(sceZlibInflate(nullptr, 0, nullptr, 0, nullptr) == static_cast(0x81120032)); + Require(sceZlibWaitForDone(nullptr, nullptr) == static_cast(0x81120032)); + std::uint64_t earlyId = 0; + Require(sceZlibWaitForDone(&earlyId, nullptr) == static_cast(0x81120032)); Require(sceZlibInitialize(nullptr, 1) == static_cast(0x81120016)); Require(sceZlibInitialize(output, 0) == static_cast(0x81120016)); Require(sceZlibInitialize(nullptr, 0) == 0); @@ -145,4 +148,7 @@ int main() { Require(sceZlibInitialize(nullptr, 0) == 0); Require(sceZlibGetResult(id, &produced, &status) == static_cast(0x81120002)); Require(sceZlibFinalize() == 0); + Require(sceZlibFinalize() == static_cast(0x81120032)); + Require(sceZlibInflate(nullptr, 0, nullptr, 0, nullptr) == static_cast(0x81120032)); + Require(sceZlibWaitForDone(nullptr, nullptr) == static_cast(0x81120032)); } diff --git a/core/libs/tests/Http2TeardownFailure.cpp b/core/libs/tests/Http2TeardownFailure.cpp new file mode 100644 index 0000000000..00b2ecc827 --- /dev/null +++ b/core/libs/tests/Http2TeardownFailure.cpp @@ -0,0 +1,170 @@ +#include "prx/libSceHttp2/include/Http2.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static thread_local int allocationBudget = -1; +static thread_local unsigned allocationAttempts = 0; + +void* operator new(std::size_t bytes) { + if (allocationBudget >= 0) { + ++allocationAttempts; + if (allocationBudget == 0) { + allocationBudget = -1; + throw std::bad_alloc(); + } + --allocationBudget; + } + if (void* memory = std::malloc(bytes ? bytes : 1)) return memory; + throw std::bad_alloc(); +} + +void operator delete(void* memory) noexcept { std::free(memory); } +void operator delete(void* memory, std::size_t) noexcept { ::operator delete(memory); } + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +constexpr int InvalidId = static_cast(0x817B1100); + +enum class Operation { None, Request, Template, Context }; + +static const char* Name(Operation operation) { + switch (operation) { + case Operation::None: break; + case Operation::Request: return "DeleteRequest"; + case Operation::Template: return "DeleteTemplate"; + case Operation::Context: return "Term"; + } + std::abort(); +} + +struct Tree { + int context; + std::array templates; + std::array requests; + + Tree() : context(sceHttp2Init(1, 1, 0x10000, 4)) { + Require(context > 0, "context creation failed"); + for (auto& id : templates) { + id = sceHttp2CreateTemplate(context, "agent", 3, 0); + Require(id > 0, "template creation failed"); + } + for (std::size_t index = 0; index < requests.size(); ++index) { + requests[index] = sceHttp2CreateRequestWithURL(templates[index / 3], "GET", "https://example.invalid/", 0); + Require(requests[index] > 0, "request creation failed"); + } + } + + int Delete(Operation operation) const { + switch (operation) { + case Operation::None: break; + case Operation::Request: return sceHttp2DeleteRequest(requests[0]); + case Operation::Template: return sceHttp2DeleteTemplate(templates[0]); + case Operation::Context: return sceHttp2Term(context); + } + std::abort(); + } +}; + +static bool ContextExists(int context) { + const int probe = sceHttp2CreateTemplate(context, "probe", 3, 0); + if (probe == InvalidId) return false; + Require(probe > 0, "context probe failed"); + Require(sceHttp2DeleteTemplate(probe) == 0, "context probe cleanup failed"); + return true; +} + +static bool CheckTree(const Tree& tree, Operation operation) { + bool correct = true; + for (std::size_t index = 0; index < tree.requests.size(); ++index) { + const bool removed = operation == Operation::Context || (operation == Operation::Template ? index < 3 : operation == Operation::Request && index == 0); + const int status = sceHttp2SslEnableOption(tree.requests[index], 0); + if (status != (removed ? InvalidId : 0)) correct = false; + } + if (ContextExists(tree.context) != (operation != Operation::Context)) correct = false; + for (std::size_t index = 0; index < tree.templates.size(); ++index) { + const bool removed = operation == Operation::Context || (operation == Operation::Template && index == 0); + if (sceHttp2SslEnableOption(tree.templates[index], 0) != (removed ? InvalidId : 0)) correct = false; + } + return correct; +} + +static unsigned AllocationFailureCases(Operation operation) { + unsigned failures = 0; + for (int budget = 0; budget < 16; ++budget) { + Tree tree; + Tree independent; + const int cookieBox = sceHttp2CreateCookieBox(0x10000); + Require(cookieBox > 0, "cookie box creation failed"); + allocationAttempts = 0; + allocationBudget = budget; + bool threw = false; + int result = -1; + try { result = tree.Delete(operation); } + catch (const std::bad_alloc&) { threw = true; } + allocationBudget = -1; + const bool correct = CheckTree(tree, operation); + if (threw || result != 0 || !correct || allocationAttempts != 0) { + std::fprintf(stderr, "%s budget=%d threw=%d result=%d ownership=%d allocation attempts=%u\n", + Name(operation), budget, threw, result, correct, allocationAttempts); + ++failures; + } + Require(CheckTree(independent, Operation::None), "independent context changed"); + Http2CookieStats stats{}; + Require(sceHttp2GetCookieStats(cookieBox, &stats) == 0, "unrelated cookie box was deleted"); + Require(sceHttp2DeleteCookieBox(cookieBox) == 0, "cookie box cleanup failed"); + Require(sceHttp2Term(independent.context) == 0, "independent context cleanup failed"); + if (ContextExists(tree.context)) + Require(sceHttp2Term(tree.context) == 0, "context cleanup failed"); + } + return failures; +} + +static void ConcurrentCreationCases(Operation operation) { + for (int iteration = 0; iteration < 64; ++iteration) { + Tree tree; + std::array created{}; + std::atomic start{false}; + std::thread producer([&] { + while (!start.load(std::memory_order_acquire)) std::this_thread::yield(); + for (auto& request : created) { + request = sceHttp2CreateRequestWithURL(tree.templates[0], "GET", "https://example.invalid/", 0); + Require(request > 0 || request == InvalidId, "concurrent creation returned an unexpected result"); + } + }); + start.store(true, std::memory_order_release); + Require(tree.Delete(operation) == 0, "concurrent deletion failed"); + producer.join(); + Require(CheckTree(tree, operation), "concurrent deletion changed object ownership"); + for (const int request : created) { + if (request == InvalidId) { + Require(operation != Operation::Request, "request deletion removed its parent template"); + continue; + } + const int expected = operation == Operation::Request ? 0 : InvalidId; + Require(sceHttp2SslEnableOption(request, 0) == expected, "concurrent creation escaped its parent's teardown"); + } + Require(tree.Delete(operation) == InvalidId, "repeated deletion succeeded"); + if (operation != Operation::Context) Require(sceHttp2Term(tree.context) == 0, "concurrent fixture cleanup failed"); + } +} + +int main() { + unsigned failures = 0; + for (const auto operation : {Operation::Request, Operation::Template, Operation::Context}) { + failures += AllocationFailureCases(operation); + ConcurrentCreationCases(operation); + } + std::printf("48 allocation-pressure lifecycles, 192 concurrent lifecycles, %u failures\n", failures); + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/tests/LibcFloatingPointContraction.cmake b/core/libs/tests/LibcFloatingPointContraction.cmake index 56867679a7..132956d655 100644 --- a/core/libs/tests/LibcFloatingPointContraction.cmake +++ b/core/libs/tests/LibcFloatingPointContraction.cmake @@ -4,8 +4,12 @@ file(MAKE_DIRECTORY "${WORK}") file(WRITE "${source}" "float MultiplyAdd(float a, float b, float c) { return a * b + c; }\n") separate_arguments(options NATIVE_COMMAND "${OPTIONS}") list(FILTER options INCLUDE REGEX "^-f") +set(target) +foreach(architecture IN LISTS ARCHITECTURES) + list(APPEND target -arch ${architecture}) +endforeach() foreach(flags IN ITEMS "" ";${options}") - execute_process(COMMAND "${COMPILER}" -std=gnu++20 -O2 -mfma ${flags} -S "${source}" -o "${assembly}" + execute_process(COMMAND "${COMPILER}" ${target} -std=gnu++20 -O2 -mfma ${flags} -S "${source}" -o "${assembly}" RESULT_VARIABLE result ERROR_VARIABLE error) if(NOT result EQUAL 0) message(FATAL_ERROR "compiling the contraction probe failed: ${error}") diff --git a/core/libs/tests/RudpPollWaitException.cpp b/core/libs/tests/RudpPollWaitException.cpp new file mode 100644 index 0000000000..f9cf2f4d4f --- /dev/null +++ b/core/libs/tests/RudpPollWaitException.cpp @@ -0,0 +1,99 @@ +#include "prx/libSceRudp/include/Api.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" +#include "SceTypes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +thread_local bool failAllocations = false; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +} + +void* operator new(std::size_t bytes) { + if (failAllocations) throw std::bad_alloc(); + if (void* memory = std::malloc(bytes == 0 ? 1 : bytes)) return memory; + throw std::bad_alloc(); +} + +void* operator new[](std::size_t bytes) { + return ::operator new(bytes); +} + +void operator delete(void* memory) noexcept { + std::free(memory); +} + +void operator delete(void* memory, std::size_t) noexcept { + std::free(memory); +} + +void operator delete[](void* memory) noexcept { + std::free(memory); +} + +void operator delete[](void* memory, std::size_t) noexcept { + std::free(memory); +} + +int main() { + if (TimedWait::Coarse()) return 77; + constexpr int cancelled = static_cast(0x80770014u); + constexpr std::uint64_t forever = std::numeric_limits::max(); + alignas(16) static std::array pool{}; + Require(sceRudpInit_nid_postfix(pool.data(), static_cast(pool.size())) == 0, "sceRudpInit"); + const int poll = sceRudpPollCreate(1); + Require(poll > 0, "sceRudpPollCreate"); + for (const std::uint64_t timeout : {forever, std::uint64_t{10000000}}) { + std::atomic threw{false}; + std::thread waiter([&] { + std::array events{}; + failAllocations = true; + try { + sceRudpPollWait(poll, events.data(), static_cast(events.size()), timeout); + } catch (const std::bad_alloc&) { + threw = true; + } + failAllocations = false; + }); + waiter.join(); + Require(threw, "the first wait on a fresh thread fails to allocate its wait state"); + std::atomic cancelReturned{false}; + std::thread canceller([&] { + Require(sceRudpPollCancel(poll) == 0, "sceRudpPollCancel"); + cancelReturned = true; + }); + const auto start = std::chrono::steady_clock::now(); + while (!cancelReturned && std::chrono::steady_clock::now() - start < std::chrono::seconds(2)) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + if (!cancelReturned) { + std::fprintf(stderr, "failed: a wait that threw left its registration behind and the cancel did not return\n"); + Require(sceRudpPollDestroy(poll) == 0, "sceRudpPollDestroy releases the cancel"); + canceller.join(); + return 1; + } + canceller.join(); + std::array events{}; + Require(sceRudpPollWait(poll, events.data(), static_cast(events.size()), 0) == cancelled, + "the cancel with no registered waiter is kept for the next wait"); + Require(sceRudpPollWait(poll, events.data(), static_cast(events.size()), 0) == 0, + "only one cancellation is kept"); + } + Require(sceRudpPollDestroy(poll) == 0, "sceRudpPollDestroy"); + Require(sceRudpEnd() == 0, "sceRudpEnd"); + return 0; +} diff --git a/core/libs/tests/SaveDataWriteReplacement.cpp b/core/libs/tests/SaveDataWriteReplacement.cpp index ef57bfe136..369f27477e 100644 --- a/core/libs/tests/SaveDataWriteReplacement.cpp +++ b/core/libs/tests/SaveDataWriteReplacement.cpp @@ -13,13 +13,6 @@ #include #include -extern "C" { - -#ifndef SAVEDATA_NATIVE_BACKEND - -#endif -} - static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Save-data replacement check failed at line %d\n", line); @@ -37,12 +30,7 @@ int main() { std::filesystem::current_path(root); constexpr std::int32_t userId = 7531; -#ifdef SAVEDATA_NATIVE_BACKEND const auto savePath = std::filesystem::path("_sd_mem") / ("u" + std::to_string(userId)) / "slot0.bin"; -#else - const auto savePath = std::filesystem::path("_sd") / "sce_sdmemory" / - std::to_string(userId) / "memory.dat"; -#endif std::filesystem::create_directories(savePath.parent_path()); const std::vector oldData{'o', 'l', 'd'}; { @@ -57,18 +45,7 @@ int main() { setup.user_id = userId; setup.memory_size = oldData.size() + 3; SaveDataMemorySetupResult result{}; - int status = sceSaveDataSetupSaveDataMemory2(&setup, &result); - -#ifndef SAVEDATA_NATIVE_BACKEND - struct MemorySyncParam { - std::int32_t user_id; - std::uint32_t slot_id; - std::uint32_t option; - } sync{userId, 0, 0}; - if (status == 0) { - status = sceSaveDataSyncSaveDataMemory(&sync); - } -#endif + const int status = sceSaveDataSetupSaveDataMemory2(&setup, &result); Require(result.existed_memory_size == oldData.size()); Require(status == 0); @@ -82,9 +59,7 @@ int main() { Require(std::all_of(savedData.begin() + static_cast(oldData.size()), savedData.end(), [](char byte) { return byte == 0; })); -#ifdef SAVEDATA_NATIVE_BACKEND Require(sceSaveDataTerminate() == 0); -#endif std::filesystem::current_path(originalDirectory); std::filesystem::remove_all(root); } diff --git a/core/libs/tests/WriteWatch.cpp b/core/libs/tests/WriteWatch.cpp index 0588e469d0..0949643a2f 100644 --- a/core/libs/tests/WriteWatch.cpp +++ b/core/libs/tests/WriteWatch.cpp @@ -229,6 +229,18 @@ void CheckProtection() { region.base[2 * region.page] = 1; region.base[4 * region.page] = 1; Require(!GuestWriteWatchCovers_nid_postfix(region.Address(2), region.page), "an unregistered range is covered"); + + GuestWriteWatchRegister_nid_postfix(region.base, region.bytes); + static_cast(region.Collect()); + Require(GuestWriteWatchProtect_nid_postfix(region.base, region.page, PROT_READ) == 0, "cannot protect a watched page"); + Require(KernelProtection(region.base) == PROT_READ, "a protect does not change the host protection"); + Require(GuestWriteWatchProtection_nid_postfix(region.Address(0), region.Address(6), &runEnd) == PROT_READ && runEnd == region.Address(1), "a protect is not recorded"); + Require(region.Collect() == std::set({0}), "a protect is not reported as a write"); + Require(GuestWriteWatchProtect_nid_postfix(region.base, region.page, PROT_READ | PROT_WRITE) == 0, "cannot restore a watched page"); + static_cast(region.Collect()); + region.base[0] = 1; + Require(region.Collect() == std::set({0}), "a write after a protect is not reported"); + Require(GuestWriteWatchProtect_nid_postfix(region.base + 1, region.page, PROT_READ) == EINVAL, "a misaligned protect does not fail with EINVAL"); } void CheckOverlaps() { diff --git a/core/libs/tests/floating_prefix/CMakeLists.txt b/core/libs/tests/floating_prefix/CMakeLists.txt new file mode 100644 index 0000000000..32c8435992 --- /dev/null +++ b/core/libs/tests/floating_prefix/CMakeLists.txt @@ -0,0 +1,19 @@ +add_test_executable(guest_floating_prefix_tests ${LIBS_INCLUDE_DIR}/tests/GuestFloatingPrefix.cpp + ${LIBS_INCLUDE_DIR}/nid/src/NidCompute.cpp ${LIBS_INCLUDE_DIR}/nid/src/Sha1.cpp) +target_include_directories(guest_floating_prefix_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include) +target_link_libraries(guest_floating_prefix_tests PRIVATE ${CMAKE_DL_LIBS}) +configure_windows_unwind(guest_floating_prefix_tests) +if(MINGW) + target_link_libraries(guest_floating_prefix_tests PRIVATE libc) +endif() +add_custom_target(guest_floating_prefix_libraries DEPENDS ${LIBC_PATCHED}) +add_dependencies(guest_floating_prefix_tests guest_floating_prefix_libraries) +foreach(width IN ITEMS narrow wide) + add_test(NAME guest_floating_prefix_${width} COMMAND guest_floating_prefix_tests $ unpatched ${width}) + add_test(NAME guest_floating_prefix_${width}_patched COMMAND guest_floating_prefix_tests ${PATCHED_DIR} patched ${width}) + set_tests_properties(guest_floating_prefix_${width} guest_floating_prefix_${width}_patched PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_floating_prefix_${width} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(guest_floating_prefix_${width}_patched PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${PATCHED_DIR}") + endif() +endforeach() diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 2267be074f..fa2d17897a 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -56,6 +56,7 @@ add_library(relinker_core STATIC ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/MaskedStoreLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/AndnLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/RelrRelocations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CodeInstructionCollector.cpp @@ -130,6 +131,16 @@ if(BUILD_TESTING) set_tests_properties(call_registry_writer PROPERTIES TIMEOUT 20) endif() +if(ANYPS5_BUILD_FUZZERS) + add_test_executable(relinker_conversion_fuzzer ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/ConversionFuzzer.cpp) + target_link_libraries(relinker_conversion_fuzzer PRIVATE relinker_core) + target_link_options(relinker_conversion_fuzzer PRIVATE -fsanitize=fuzzer) + if(BUILD_TESTING) + add_test(NAME relinker_conversion_fuzzer COMMAND relinker_conversion_fuzzer -runs=20000 -seed=1) + set_tests_properties(relinker_conversion_fuzzer PROPERTIES TIMEOUT 120) + endif() +endif() + if(ANYPS5_BUILD_WASM) target_compile_options(relinker_core PRIVATE -fexceptions) add_subdirectory(wasm) @@ -152,6 +163,15 @@ target_link_libraries(relinker PRIVATE relinker_core) target_include_directories(relinker PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include ${CMAKE_CURRENT_SOURCE_DIR}/../libs) set_target_properties(relinker PROPERTIES CXX_EXTENSIONS OFF) +find_package(Git QUIET) +set(relinkerRevisionHeader ${CMAKE_CURRENT_BINARY_DIR}/RelinkerRevision.hpp) +add_custom_target(relinker_revision + COMMAND ${CMAKE_COMMAND} -DGIT=${GIT_EXECUTABLE} -DSOURCE=${CMAKE_SOURCE_DIR} -DOUTPUT=${relinkerRevisionHeader} -P ${CMAKE_CURRENT_SOURCE_DIR}/revision.cmake + BYPRODUCTS ${relinkerRevisionHeader} + VERBATIM) +add_dependencies(relinker relinker_revision) +target_include_directories(relinker PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + if(BUILD_TESTING AND WIN32) add_test_executable(windows_guest_export_alias_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/WindowsExportAliasTests.cpp) target_link_libraries(windows_guest_export_alias_tests PRIVATE relinker_core) @@ -242,6 +262,11 @@ if(BUILD_TESTING) add_test(NAME elf_reader_bounds COMMAND elf_reader_bounds_tests) set_tests_properties(elf_reader_bounds PROPERTIES TIMEOUT 20) + add_test_executable(relr_relocations_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/RelrRelocationTests.cpp) + target_link_libraries(relr_relocations_tests PRIVATE relinker_core) + add_test(NAME relr_relocations COMMAND relr_relocations_tests) + set_tests_properties(relr_relocations PROPERTIES TIMEOUT 20) + add_test_executable(call_site_index_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/CallSiteIndexTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp @@ -383,6 +408,21 @@ if(BUILD_TESTING) add_test(NAME param_json_utf8 COMMAND param_json_utf8_tests) set_tests_properties(param_json_utf8 PROPERTIES TIMEOUT 20) + if(WIN32) + add_test_executable(windows_file_writer_tests + ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/WindowsFileWriterTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/NativePath.cpp + ) + target_include_directories(windows_file_writer_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/domain/include + ${CMAKE_CURRENT_SOURCE_DIR}/io/include + ) + add_test(NAME windows_file_writer_binary COMMAND windows_file_writer_tests binary) + add_test(NAME windows_file_writer_text COMMAND windows_file_writer_tests text) + set_tests_properties(windows_file_writer_binary windows_file_writer_text PROPERTIES TIMEOUT 20) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_test_executable(file_writer_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/FileWriterTests.cpp @@ -560,6 +600,11 @@ if(BUILD_TESTING) ${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 relr_conversion COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_relr_relocations.py $) + add_test(NAME guest_relr_conversion COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_relr_relocations.py $ guest) + set_tests_properties(relr_conversion guest_relr_conversion PROPERTIES TIMEOUT 20) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) add_test(NAME windows_tls_zero_fill COMMAND ${Python3_EXECUTABLE} @@ -611,6 +656,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executable_tls_relocations.py $) add_test(NAME executable_export_bounds COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executable_export_bounds.py $) + add_test(NAME executable_export_attributes COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executable_export_attributes.py $) add_test(NAME macos_empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_empty_tls.py $) set_tests_properties(macos_empty_tls PROPERTIES TIMEOUT 60) @@ -671,6 +718,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executables.py $) add_test(NAME game_patches COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_game_patches.py $) + add_test(NAME annotate_guest_module COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_annotate_guest_module.py $) if(Python3_VERSION VERSION_GREATER_EQUAL 3.11) add_test(NAME patch_advisor_unit COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_suggest_patches.py) @@ -823,6 +872,11 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_file.py $) add_test(NAME null_symbol_relocations COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_null_symbol_relocations.py $) + foreach(symbolInput IN ITEMS executable guest) + add_test(NAME restricted_undefined_symbols_${symbolInput} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_restricted_undefined_symbols.py $ ${symbolInput}) + set_tests_properties(restricted_undefined_symbols_${symbolInput} PROPERTIES TIMEOUT 20) + endforeach() add_test(NAME input_nonascii COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_nonascii.py $) add_test(NAME long_paths COMMAND ${Python3_EXECUTABLE} @@ -931,6 +985,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionDecoder.cpp ) target_sources(strict_nid_filter_tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/RelrRelocations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index 2625b44aac..71ee3f85d4 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -11,6 +11,7 @@ namespace Cli { struct Args { bool showHelp = false; + bool showVersion = false; bool skipSyscallCheck = false; bool trapSdkSyscallStubs = false; bool skipSceModule = false; diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 9990cfb485..b74c8ac036 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -13,7 +13,7 @@ namespace Cli { const char* Usage() { - return "Usage: relinker [--help] [--windows | --macos] [--windows-diagnostics] [--windows-gui] [--trace-imports] [--windows-fixed-base
] [--linux-fixed-base
] [--macos-fixed-base
] [--skip-syscall-check | --trap-sdk-syscall-stubs] [--skip-sce-module | --sce-module-path ] [--exclude-sce-module ]... [--module-dir ]... [--executable ]... [--to-intel] [--to-rosetta] [--frame-red-zone] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] [--patches | --no-patches] \n" + return "Usage: relinker [--help] [--version] [--windows | --macos] [--windows-diagnostics] [--windows-gui] [--trace-imports] [--windows-fixed-base
] [--linux-fixed-base
] [--macos-fixed-base
] [--skip-syscall-check | --trap-sdk-syscall-stubs] [--skip-sce-module | --sce-module-path ] [--exclude-sce-module ]... [--module-dir ]... [--executable ]... [--to-intel] [--to-rosetta] [--frame-red-zone] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] [--patches | --no-patches] \n" " relinker --verify-patches [--verify-output] [--skip-sce-module | --sce-module-path ] [--exclude-sce-module ]... [--module-dir ]... [--executable ]... \n" "Example: relinker input.elf output.elf"; } @@ -60,6 +60,8 @@ Args ParseArgs(int argc, char* argv[]) { else if (arg.rfind("unused-filter=", 0) == 0) givenOptions.push_back("unused-filter="); if (arg == "--help") { args.showHelp = true; + } else if (arg == "--version") { + args.showVersion = true; } else if (arg == "--windows-fixed-base") { if (args.windowsFixedBase) throw std::runtime_error("--windows-fixed-base cannot be repeated"); if (i + 1 >= argc || std::string(argv[i + 1]).empty() || std::string(argv[i + 1]).starts_with("--")) @@ -213,7 +215,7 @@ Args ParseArgs(int argc, char* argv[]) { if (args.macosFixedBase && !args.toMacos) throw std::runtime_error("--macos-fixed-base requires --macos"); - if (!args.showHelp && (args.inputPath.empty() || (!verifying && args.outputPath.empty()))) + if (!args.showHelp && !args.showVersion && (args.inputPath.empty() || (!verifying && args.outputPath.empty()))) throw std::runtime_error(Usage()); diff --git a/core/relinker/cli/tests/test_help.py b/core/relinker/cli/tests/test_help.py index 8c9659affc..4c8386180a 100644 --- a/core/relinker/cli/tests/test_help.py +++ b/core/relinker/cli/tests/test_help.py @@ -26,6 +26,18 @@ def main(): assert output.read_bytes() == b"preserve this output" assert sorted(path.name for path in work.iterdir()) == [output.name] + result = run("--version") + assert result.returncode == 0, result.stderr + assert result.stdout.startswith("relinker "), result.stdout + assert len(result.stdout.split()) == 2, result.stdout + assert result.stderr == "", result.stderr + assert output.read_bytes() == b"preserve this output" + assert sorted(path.name for path in work.iterdir()) == [output.name] + + result = run("--version", str(source), str(output)) + assert result.returncode == 0 and result.stdout.startswith("relinker "), result.stderr + assert output.read_bytes() == b"preserve this output" + result = run() assert result.returncode == 1 and "Usage: relinker" in result.stderr for args in [("--help", "--unknown"), ("--unknown", "--help")]: diff --git a/core/relinker/codegen/src/Amd64OnlyConverter.cpp b/core/relinker/codegen/src/Amd64OnlyConverter.cpp index 8264fb4db1..d8d1be1731 100644 --- a/core/relinker/codegen/src/Amd64OnlyConverter.cpp +++ b/core/relinker/codegen/src/Amd64OnlyConverter.cpp @@ -278,15 +278,17 @@ std::set Amd64OnlyConverter::_collectBranchTargets( std::set targets; for (const auto& match : matches) { const auto info = decoder.DecodeInstruction(seg.data() + match.Offset, match.Length); - const auto next = static_cast(ph.MappedAddress + match.Offset + info.Length); + const auto next = ph.MappedAddress + match.Offset + info.Length; if (info.HasBranchTarget && !info.HasRipRelativeDisp) { - const auto target = next + info.BranchDisp; - if (target >= 0 && !pastPrefixes(static_cast(target))) - targets.insert(static_cast(target)); + const auto displacement = static_cast(info.BranchDisp); + const auto target = next + displacement; + const auto wrapped = info.BranchDisp < 0 ? target > next : target < next; + if (!wrapped && !pastPrefixes(target)) + targets.insert(target); } else if (info.HasRipRelativeDisp && info.Opcode == kLea) { std::int32_t displacement; std::memcpy(&displacement, seg.data() + match.Offset + info.RipRelativeDispOffset, sizeof(displacement)); - const auto table = static_cast(next + displacement); + const auto table = next + static_cast(displacement); for (const auto& data : segments) { if (data.Type != kLoadSegment || table < data.MappedAddress || table - data.MappedAddress >= data.FileSize) continue; diff --git a/core/relinker/codegen/src/x86/RedZoneFramer.cpp b/core/relinker/codegen/src/x86/RedZoneFramer.cpp index d975984199..1a2f27bb93 100644 --- a/core/relinker/codegen/src/x86/RedZoneFramer.cpp +++ b/core/relinker/codegen/src/x86/RedZoneFramer.cpp @@ -134,7 +134,15 @@ bool vexGeneralUsesRsp(const std::uint8_t* code, const Insn& insn, const std::ui MemoryUse memoryUse(const std::uint8_t* code, const Insn& insn) { MemoryUse use; const auto& info = insn.Info; - if (!info.HasModRm || info.ModRmOffset == 0 || info.ModRmOffset >= insn.Length) return use; + if (!info.HasModRm) { + const auto op = info.Opcode; + if ((info.RexPrefix & 1) == 0 + && ((!info.IsTwoByteOpcode && (op == 0x54 || op == 0x5c || op == 0x94 || op == 0xbc || (op == 0xb4 && info.RexPrefix != 0))) + || (info.IsTwoByteOpcode && op == 0xcc))) + use.UsesRsp = use.RegisterRsp = true; + return use; + } + if (info.ModRmOffset == 0 || info.ModRmOffset >= insn.Length) return use; bool r = false; bool x = false; bool b = false; diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 6c4d676b52..a47606f00a 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -536,6 +536,20 @@ void converterSegment() { requireFailure([&] { (void)converter->Convert(file, {segmentHeader(0x200)}); }, "Segment exceeding the file was accepted"); } +void converterSegmentMappedAtInt64Max() { + const auto converter = Codegen::MakeAmd64OnlyConverter(); + const auto file = segmentFixture(); + const Domain::ProgramHeader mappedAtInt64Max{1, 5, 0x200, 0x8000000000000000, 0, 20, 20, 16}; + const auto converted = converter->Convert(file, {mappedAtInt64Max}); + require(converted.ReplacedCount == 1 && converted.Trampolines.size() == 1, "Converter misclassified a segment mapped at 2^63"); + auto branchInto = file; + branchInto[0x207] = 0x02; + requireFailure([&] { (void)converter->Convert(branchInto, {mappedAtInt64Max}); }, "A branch into an AMD-only instruction was accepted in a segment mapped at 2^63"); + branchInto[0x207] = 0x80; + const auto backward = converter->Convert(branchInto, {mappedAtInt64Max}); + require(backward.ReplacedCount == 1 && backward.Trampolines.size() == 1, "A backward branch from a segment mapped at 2^63 changed the conversion"); +} + void converterRipRelativeFollower() { const auto converter = Codegen::MakeAmd64OnlyConverter(); const auto withFollower = [](const Bytes& following) { @@ -2327,6 +2341,7 @@ int main() { rdpruExecution(); mcommitExecution(); converterSegment(); + converterSegmentMappedAtInt64Max(); converterRipRelativeFollower(); converterSha256(); converterSha1(); diff --git a/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp b/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp index 5517011feb..d2acf6215f 100644 --- a/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp +++ b/core/relinker/codegen/tests/MaskedStoreLoweringTests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -336,7 +337,13 @@ void execution() { require(stack[-8 - 128 + static_cast(at)] == kSentinel, "Masked store stub wrote into the red zone"); require(std::memcmp(machine.Ymm, input.Ymm, sizeof(input.Ymm)) == 0, "Masked store stub changed a vector register"); require(std::memcmp(machine.Gpr, input.Gpr, sizeof(input.Gpr)) == 0, "Masked store stub changed a general register"); - require((machine.Flags & 0x8D5) == (input.Flags & 0x8D5), "Masked store stub changed the flags"); + if ((machine.Flags & 0x8D5) != (input.Flags & 0x8D5)) { + char detail[256]; + std::snprintf(detail, sizeof(detail), "Masked store stub changed the flags: %s %s, addressing %zu, offset %d, mask pattern 0x%02X, %d fault(s), flags 0x%03llX in, 0x%03llX out", + form.Name, wide ? "256-bit" : "128-bit", mode, offset, static_cast(pattern), static_cast(faults), + static_cast(input.Flags & 0x8D5), static_cast(machine.Flags & 0x8D5)); + throw std::runtime_error(detail); + } require(machine.Stack == input.Stack, "Masked store stub did not restore RSP"); } } diff --git a/core/relinker/codegen/tests/RedZoneFramerTests.cpp b/core/relinker/codegen/tests/RedZoneFramerTests.cpp index c19cd3131f..53e0ded76d 100644 --- a/core/relinker/codegen/tests/RedZoneFramerTests.cpp +++ b/core/relinker/codegen/tests/RedZoneFramerTests.cpp @@ -461,6 +461,46 @@ void framesFramePointerLeavesThatPushInTheirBody() { require(sites[1].Body == stubBody({0x48, 0x01, 0xd0, 0x48, 0x8d, 0xa4, 0x24, 0x80, 0x00, 0x00, 0x00, 0x5d, 0xc3}), "the add before pop rbp was not copied ahead of raising rsp"); } +void skipsOpcodeEncodedRspUses() { + const std::vector> uses{ + {0x48, 0x0f, 0xcc, 0x48, 0x0f, 0xcc}, + {0x0f, 0xcc, 0x0f, 0xcc}, + {0x4c, 0x0f, 0xcc, 0x4c, 0x0f, 0xcc}, + {0x54, 0x58}, + {0x66, 0x54, 0x66, 0x58}, + {0x40, 0xb4, 0x10}, + {0x66, 0xbc, 0x00, 0x10}, + {0x54, 0x5c}, + {0x48, 0x94, 0x48, 0x94}, + {0xbc, 0x00, 0x10, 0x00, 0x00}, + {0x48, 0xbc, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }; + for (const auto& use : uses) { + auto body = StoreBelowRsp; + append(body, use); + const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(sites.empty() && displacements.empty() && report.Skipped.at("other use of rsp") == 1, + "an opcode-encoded rsp use was framed"); + } +} + +void framesOpcodeEncodedNonStackRegisters() { + for (const auto& use : std::vector>{ + {0x49, 0x0f, 0xcc, 0x49, 0x0f, 0xcc}, {0x41, 0x0f, 0xcc, 0x41, 0x0f, 0xcc}, + {0x49, 0x94, 0x49, 0x94}, {0x41, 0xbc, 0x00, 0x10, 0x00, 0x00}, {0xb4, 0x10}}) { + auto body = StoreBelowRsp; + append(body, use); + const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)}); + Codegen::RedZoneFrameReport report; + std::vector displacements; + const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements); + require(report.Framed == 1 && sites.size() == 2, "an opcode-encoded non-stack register prevented framing"); + } +} + void skipsLeavesThatCopyRsp() { std::vector withCmov = StoreBelowRsp; append(withCmov, {0x48, 0x0f, 0x45, 0xc4}); @@ -849,6 +889,8 @@ int main() { widensExitsOverVexOperandsAtRsp(); framesFramePointerLeavesThatPushInTheirBody(); skipsLeavesThatCopyRsp(); + skipsOpcodeEncodedRspUses(); + framesOpcodeEncodedNonStackRegisters(); framesEachFunctionFromItsOwnFde(); treatsATailCallPastTheFdeEndAsLeaving(); framesALeafAfterAJumpWithoutInt3FromItsOwnFde(); diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index b8b6b853b6..a3defa240e 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -152,6 +152,7 @@ inline constexpr std::uint32_t R_X86_64_IRELATIVE = 37; inline constexpr std::uint8_t STB_GLOBAL = 1; inline constexpr std::uint8_t STB_WEAK = 2; inline constexpr std::uint8_t STT_NOTYPE = 0; +inline constexpr std::uint8_t STT_OBJECT = 1; inline constexpr std::uint8_t STT_FUNC = 2; inline constexpr std::uint8_t STT_TLS = 6; inline constexpr std::uint8_t STV_DEFAULT = 0; diff --git a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp index fb493db7f0..e2a1ceb67f 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp @@ -407,8 +407,7 @@ std::vector LinuxElfPatcher::Patch( std::vector buf = std::move(sourceElf); const std::uint32_t type = _fixedBase ? ET_EXEC : ET_DYN; - buf[kEhdrOsAbiOffset] = 0; - buf[kEhdrAbiVersionOffset] = 0; + std::fill_n(buf.begin() + static_cast(kEhdrOsAbiOffset), 9, std::uint8_t{0}); buf[kEhdrTypeOffset] = static_cast(type & 0xFF); buf[kEhdrTypeOffset + 1] = static_cast((type >> 8) & 0xFF); _byteWriter->WriteU64(buf, kEhdrShOffOffset, 0); diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index d539683a1e..b2545510c1 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -224,6 +224,7 @@ std::pair Locate(const std::vector& segmen const auto& segment = segments[index]; if (segment.MemorySize == 0 || rva < segment.Rva || rva - segment.Rva >= segment.MemorySize || size > segment.MemorySize - (rva - segment.Rva)) continue; if (index > 15) throw Domain::RelinkerException("Too many Mach-O segments for dyld opcodes", rva); + if ((segment.Protection & VmProtWrite) == 0) throw Domain::RelinkerException("Mach-O fixup target is not in a writable segment", rva); return {static_cast(index), rva - segment.Rva}; } throw Domain::RelinkerException("Mach-O fixup target is outside the image", rva); @@ -891,7 +892,8 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { std::vector file; constexpr std::size_t HeaderSize = 32; - const auto stubOffset = AlignUp(HeaderSize + commands.size(), 16); + constexpr std::size_t LoadCommandReserve = 0x400; + const auto stubOffset = AlignUp(HeaderSize + commands.size() + LoadCommandReserve, 16); const auto traceJump = input.Executable && input.TraceImports; const auto entryOffset = stubOffset + (traceJump ? 5 : 0); if (entryOffset + (input.Executable ? sizeof(EntryStub) : 0) > LoadRva) diff --git a/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp b/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp index 3cb70284e7..81833c0e17 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp @@ -59,7 +59,8 @@ MacOsMachOPatcher::MacOsMachOPatcher(const std::optional fixedBas std::vector MacOsMachOPatcher::Patch(std::vector sourceElf, const std::vector& originalHeaders, const Domain::SysVDynamicSection& dynamicSection, const std::uint64_t originalPltGotVaddr, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector& trampolines) { if (dependencyDiagnostics) throw Domain::RelinkerException("macOS target does not support --windows-diagnostics"); - static_cast(lazyBinding); + if (lazyBinding) + throw Domain::RelinkerException("macOS target does not support --lazy-binding"); Windows::WindowsLoadImage image(sourceElf, originalHeaders, true, fixedBase ? *fixedBase - Windows::LoadRva : Windows::ImageBase); if (originalPltGotVaddr != 0) @@ -81,11 +82,7 @@ std::vector MacOsMachOPatcher::Patch(std::vector sou const auto info = symbols[offset + 4]; const auto visibility = symbols[offset + 5] & 3; if (section == SHN_UNDEF || (info >> 4) == 0 || visibility == 1 || visibility == 2) continue; - if (((info >> 4) != STB_GLOBAL && (info >> 4) != STB_WEAK) || - ((info & 15) != STT_NOTYPE && (info & 15) != 1 && (info & 15) != STT_FUNC && (info & 15) != STT_TLS)) - throw Domain::RelinkerException("Unsupported macOS executable export attributes"); const auto name = "_" + Windows::ReadString(dynamicSection.DynStrData, Io::ReadU32(symbols, offset)); - if (name.size() == 1) throw Domain::RelinkerException("Empty macOS executable export"); const auto value = Io::ReadU64(symbols, offset + 8); const auto flags = (info >> 4) == STB_WEAK ? ExportWeakDefinition : std::uint64_t{0}; if ((info & 15) == STT_TLS) { diff --git a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp index 123bc83ef7..af0f69328b 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -72,6 +72,8 @@ std::vector WindowsPeWriter::Write(const std::vector& s } if (!foundEntry) throw Domain::RelinkerException("PE entry point is not inside executable section data", entryRva); + if (endRva > 0x77000000) + throw Domain::RelinkerException("PE image exceeds the Windows image size limit", endRva); if (fixedBase && imageBase > 0x7fffffff0000 - static_cast(endRva)) throw Domain::RelinkerException("Fixed Windows image exceeds the user address space"); Io::WriteU32(result, optionalOffset + 4, codeSize); diff --git a/core/relinker/fuzz/ConversionFuzzer.cpp b/core/relinker/fuzz/ConversionFuzzer.cpp new file mode 100644 index 0000000000..234d14857b --- /dev/null +++ b/core/relinker/fuzz/ConversionFuzzer.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include + +namespace { + +class SilentProgress final : public Domain::IProgress { +public: + void Report(const Domain::ProgressEvent&) override {} + void Message(std::string_view) override {} +}; + +} + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) { + if (size < 1) return 0; + const std::uint8_t selector = data[0]; + Relinker::ConversionInput input; + input.InputPath = Io::NativePath("/input/eboot.bin"); + input.OutputPath = Io::NativePath("/output/eboot"); + input.ModuleRoot = "/input"; + input.Executable.assign(data + 1, data + size); + Relinker::ConversionOptions options; + switch (selector % 3) { + case 0: options.Target = Relinker::TargetSystem::Linux; break; + case 1: options.Target = Relinker::TargetSystem::Windows; break; + default: options.Target = Relinker::TargetSystem::MacOs; break; + } + options.ToIntel = (selector & 0x04) != 0; + options.UnusedFilterLevel = (selector >> 3) % 3; + SilentProgress progress; + try { + Relinker::Conversion(progress).Convert(std::move(input), options); + } catch (const std::exception&) { + } + return 0; +} diff --git a/core/relinker/io/tests/WindowsFileWriterTests.cpp b/core/relinker/io/tests/WindowsFileWriterTests.cpp new file mode 100644 index 0000000000..c42f909388 --- /dev/null +++ b/core/relinker/io/tests/WindowsFileWriterTests.cpp @@ -0,0 +1,119 @@ +#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); +} + +std::string readFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + require(file.is_open(), "Cannot read test output"); + return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; +} + +class ReadOnlyFile { + std::filesystem::path path; + DWORD attributes; + +public: + explicit ReadOnlyFile(const std::filesystem::path& file) : path(file), attributes(GetFileAttributesW(path.c_str())) { + require(attributes != INVALID_FILE_ATTRIBUTES, "Cannot read test file attributes"); + require(SetFileAttributesW(path.c_str(), attributes | FILE_ATTRIBUTE_READONLY) != 0, "Cannot protect test output"); + } + ~ReadOnlyFile() { SetFileAttributesW(path.c_str(), attributes); } + ReadOnlyFile(const ReadOnlyFile&) = delete; + ReadOnlyFile& operator=(const ReadOnlyFile&) = delete; +}; + +template +void requireOpenFailure(const std::filesystem::path& path, const TValue& data) { + const auto name = Io::Utf8Path(path); + try { + Io::FileWriter().Write(name, data); + } catch (const Domain::RelinkerException& error) { + require(error.what() == "Cannot open output file: " + name, "Unexpected output error: " + std::string(error.what())); + return; + } + throw std::runtime_error("Writing an unavailable output reported success"); +} + +template +void run(const std::filesystem::path& directory, const TValue& data, const std::string& expected) { + Io::FileWriter writer; + const auto output = directory / std::filesystem::path(u8"Gr\u00fc\u00dfe-\u65e5\u672c.bin"); + const auto name = Io::Utf8Path(output); + writer.Write(name, data); + require(readFile(output) == expected, "A new output has incorrect bytes"); + + const std::string previous(4096, 'x'); + std::ofstream(output, std::ios::binary) << previous; + writer.Write(name, data); + require(readFile(output) == expected, "Replacing an output left old bytes or changed the data"); + writer.Write(name, TValue{}); + require(readFile(output).empty(), "An empty output retained old bytes"); + + std::ofstream(output, std::ios::binary) << previous; + { + const auto raw = CreateFileW(output.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + require(raw != INVALID_HANDLE_VALUE, "Cannot lock test output"); + const std::unique_ptr locked(raw, CloseHandle); + requireOpenFailure(output, data); + require(readFile(output) == previous, "A sharing violation changed the previous output"); + } + { + const ReadOnlyFile readOnly(output); + requireOpenFailure(output, data); + require(readFile(output) == previous, "A read-only output was changed"); + } + writer.Write(name, data); + require(readFile(output) == expected, "Writing after releasing output protection failed"); + + const auto missing = directory / "missing" / "output.bin"; + requireOpenFailure(missing, data); + require(!std::filesystem::exists(missing.parent_path()), "A failed write created a parent directory"); + requireOpenFailure(directory, data); + require(std::filesystem::is_directory(directory), "A failed write replaced a directory"); +} + +} + +int main(int argc, char** argv) { + const auto directory = std::filesystem::temp_directory_path() / + ("anyps5-windows-file-writer-" + std::to_string(GetCurrentProcessId()) + "-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())) / + std::filesystem::path(u8"\u00dcbertragung-\u65e5\u672c"); + try { + require(argc == 2, "Expected binary or text"); + std::filesystem::create_directories(directory); + const std::string mode(argv[1]); + if (mode == "binary") { + const std::vector data{0x00, 0x0a, 0x0d, 0x1a, 0x80, 0xff}; + run(directory, data, std::string(data.begin(), data.end())); + } else if (mode == "text") { + const std::string data("first\n\0last\n", 12); + run(directory, data, std::string("first\r\n\0last\r\n", 14)); + } else { + throw std::runtime_error("Expected binary or text"); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + std::error_code ignored; + std::filesystem::remove_all(directory.parent_path(), ignored); + return 1; + } + std::filesystem::remove_all(directory.parent_path()); + return 0; +} diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 1c2b12b489..9ec903f5a3 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -25,6 +25,8 @@ #include #endif +#include "RelinkerRevision.hpp" + namespace { struct ConvertedExecutable { @@ -264,6 +266,11 @@ int Run(const int argc, char* argv[]) { return 0; } + if (args.showVersion) { + std::cout << "relinker " << ANYPS5_RELINKER_REVISION << '\n'; + return 0; + } + Cli::ConsoleProgress progress(std::cout); try { if (!args.verifyPatchesPath.empty()) return RunVerifyPatches(args); diff --git a/core/relinker/relinker/include/relinker/parsing/RelrRelocations.hpp b/core/relinker/relinker/include/relinker/parsing/RelrRelocations.hpp new file mode 100644 index 0000000000..c65d433b33 --- /dev/null +++ b/core/relinker/relinker/include/relinker/parsing/RelrRelocations.hpp @@ -0,0 +1,17 @@ +#ifndef RELINKER_PARSING_RELRRELOCATIONS_HPP +#define RELINKER_PARSING_RELRRELOCATIONS_HPP + +#include +#include +#include + +namespace Relinker { + +std::map ReadRelrRelocations( + std::span bytes, + std::span headers, + std::span tags); + +} + +#endif diff --git a/core/relinker/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/relinker/src/analysis/SyscallScanner.cpp index 786e065c5e..3a828796f5 100644 --- a/core/relinker/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/relinker/src/analysis/SyscallScanner.cpp @@ -200,6 +200,32 @@ static std::optional ConstantSyscallNumber(const std::vector& code, const std::size_t opcode) { + ActivePrefixes prefixes{false, 0, 0}; + std::size_t start = opcode; + if (start > 0 && (code[start - 1] & 0xF0) == 0x40) { + prefixes.Rex = code[start - 1]; + --start; + } + while (start > 0) { + const std::uint8_t prefix = code[start - 1]; + if (prefix == 0x66) + prefixes.OperandSize = true; + else if (prefix != 0x67 && prefix != 0xF0 && prefix != 0xF2 && prefix != 0xF3 && + prefix != 0x26 && prefix != 0x2E && prefix != 0x36 && prefix != 0x3E && prefix != 0x64 && prefix != 0x65) + break; + --start; + } + prefixes.Count = opcode - start; + return prefixes; +} + static std::optional ConstantRaxSetEndingAt(const std::vector& code, const std::size_t end) { if (end >= 10 && code[end - 10] == 0x48 && code[end - 9] == 0xB8) { std::uint64_t value = 0; @@ -212,11 +238,19 @@ static std::optional ConstantRaxSetEndingAt(const std::vector(static_cast(static_cast(immediate))); } if (end >= 5 && code[end - 5] == 0xB8) { - std::memcpy(&immediate, code.data() + end - 4, sizeof(immediate)); - return immediate; + const ActivePrefixes prefixes = ActivePrefixesBefore(code, end - 5); + if (!prefixes.OperandSize && (prefixes.Rex & 0x09) == 0) { + std::memcpy(&immediate, code.data() + end - 4, sizeof(immediate)); + return immediate; + } + } + if (end >= 2 && (code[end - 2] == 0x31 || code[end - 2] == 0x33) && code[end - 1] == 0xC0) { + const ActivePrefixes prefixes = ActivePrefixesBefore(code, end - 2); + const bool wordSized = prefixes.OperandSize && (prefixes.Rex & 0x08) == 0; + const bool extendedOperand = (prefixes.Rex & 0x05) != 0; + if (!wordSized && !extendedOperand) + return 0; } - if (end >= 2 && (code[end - 2] == 0x31 || code[end - 2] == 0x33) && code[end - 1] == 0xC0) - return 0; return std::nullopt; } @@ -227,10 +261,16 @@ struct LowRaxWrite { }; static std::optional ImmediateLowRaxWriteEndingAt(const std::vector& code, const std::size_t end) { - if (end >= 2 && code[end - 2] == 0xB0) - return LowRaxWrite{2, 0xFF, code[end - 1]}; - if (end >= 4 && code[end - 4] == 0x66 && code[end - 3] == 0xB8) - return LowRaxWrite{4, 0xFFFF, static_cast(code[end - 2]) | static_cast(code[end - 1]) << 8}; + if (end >= 2 && code[end - 2] == 0xB0) { + const ActivePrefixes prefixes = ActivePrefixesBefore(code, end - 2); + if ((prefixes.Rex & 0x01) == 0) + return LowRaxWrite{prefixes.Count + 2, 0xFF, code[end - 1]}; + return LowRaxWrite{prefixes.Count + 2, 0, 0}; + } + if (end >= 4 && code[end - 4] == 0x66 && code[end - 3] == 0xB8) { + const std::size_t length = end >= 5 && (code[end - 5] & 0xF0) == 0x40 ? 5 : 4; + return LowRaxWrite{length, 0xFFFF, static_cast(code[end - 2]) | static_cast(code[end - 1]) << 8}; + } return std::nullopt; } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp index 9108445515..ad534c1ddc 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -155,7 +156,8 @@ std::unique_ptr BuildRelativeRelocationIndex( sceTableBase = read64(elfBytes, phPos + 8); } } - for (const auto& header : ElfReader(elfBytes).ReadProgramHeaders()) { + const auto headers = ElfReader(elfBytes).ReadProgramHeaders(); + for (const auto& header : headers) { if (header.Type != PT_LOAD) continue; loads.push_back({header.MappedAddress, header.Offset, header.FileSize}); } @@ -189,6 +191,7 @@ std::unique_ptr BuildRelativeRelocationIndex( bool hasSysvPltrelsz = false; bool hasOsPltrelsz = false; bool terminated = false; + std::vector relrTags; for (std::uint64_t off = 0; off <= segSz && segSz - off >= 16; off += 16) { std::size_t pos = static_cast(segOff + off); @@ -199,6 +202,7 @@ std::unique_ptr BuildRelativeRelocationIndex( terminated = true; break; } + if (tag == DT_RELR || tag == DT_RELRSZ || tag == DT_RELRENT) relrTags.push_back({tag, val}); 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"); @@ -265,6 +269,13 @@ std::unique_ptr BuildRelativeRelocationIndex( collectRelativeEntries(elfBytes, loads, *relaVa, *relaSz, hasOsRela, sceTableBase, targets); if (jmprelVa.has_value()) collectRelativeEntries(elfBytes, loads, *jmprelVa, *pltrelsz, hasOsJmprel, sceTableBase, targets); + for (const auto& [target, addend] : ReadRelrRelocations(elfBytes, headers, relrTags)) { + if (addend > static_cast(std::numeric_limits::max())) + throw RelinkerException("Relative relocation target address is negative", target); + const auto [found, inserted] = targets.emplace(target, addend); + if (!inserted && found->second != addend) + throw RelinkerException("Conflicting relative relocation targets for slot", target); + } } return std::make_unique(std::move(targets)); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 75d0da66f9..b63d3c329c 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -85,7 +85,8 @@ public: throw RelinkerException("Strict filter: relative relocation has a symbol", slot); } const auto target = read(*rela + offset + 16); - if (input.ImportSlots.contains(slot) || !input.Pointers.emplace(slot, target).second) + const auto [recorded, inserted] = input.Pointers.emplace(slot, target); + if (input.ImportSlots.contains(slot) || (!inserted && recorded->second != target)) throw RelinkerException("Strict filter: overlapping pointer relocations", slot); } diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index 9e058f2b6e..03287bb37f 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -83,7 +84,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector } } if (!terminated) fail("Unterminated dynamic segment"); - for (const auto unsupported : {15u, 16u, 17u, 18u, 19u, 22u, 35u, 36u, 37u}) { + for (const auto unsupported : {15u, 16u, 17u, 18u, 19u, 22u}) { if (tags.contains(unsupported)) fail("Unsupported dynamic tag " + std::to_string(unsupported)); } if (tags.contains(30) && (tags.at(30) & ~(2ull | 8ull)) != 0) fail("Unsupported guest dynamic flags"); @@ -233,6 +234,17 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector && relaOffset % 24 != pltOffset % 24) fail("Unaligned overlapping relocation tables"); std::map relocationTargets; + std::vector relrTags; + for (const auto& [tag, value] : tags) + if (tag >= 35 && tag <= 37) relrTags.push_back({static_cast(tag), value}); + for (const auto& [target, addend] : ReadRelrRelocations(bytes, image.Headers, relrTags)) { + mapped(target, 8, 2); + mapped(addend, 0, 0); + relocationTargets.emplace(target, target + 8); + Io::AppendU64(image.Dynamic.RelaData, target); + Io::AppendU64(image.Dynamic.RelaData, 8); + Io::AppendU64(image.Dynamic.RelaData, addend); + } const auto copyRelocations = [&](std::uint64_t offset, std::uint64_t size, std::vector& output, bool plt) { output.reserve(size); for (std::uint64_t index = 0; index < size; index += 24) { @@ -241,6 +253,9 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const auto relocation = static_cast(info); if (relocation == 0) continue; if ((info >> 32) >= image.Symbols.size() || (plt && relocation != 7) || (relocation != 1 && relocation != 6 && relocation != 7 && relocation != 8 && relocation != 16 && relocation != 17 && relocation != 18)) fail("Unsupported relocation or symbol index"); + const auto& referencedSymbol = image.Symbols[info >> 32]; + if (relocation != 8 && referencedSymbol.Section == 0 && (referencedSymbol.Info >> 4) != 2 && (referencedSymbol.Visibility & 3) != 0) + fail("Undefined non-weak symbol has non-default visibility: " + referencedSymbol.Name); const auto target = Io::ReadU64(bytes, offset + index); const auto addend = Io::ReadU64(bytes, offset + index + 16); mapped(target, 8, 2); diff --git a/core/relinker/relinker/src/parsing/RelrRelocations.cpp b/core/relinker/relinker/src/parsing/RelrRelocations.cpp new file mode 100644 index 0000000000..d739f4304d --- /dev/null +++ b/core/relinker/relinker/src/parsing/RelrRelocations.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include + +namespace Relinker { + +std::map ReadRelrRelocations( + const std::span bytes, + const std::span headers, + const std::span tags) { + using namespace Elfpatcher; + const auto value = [&](const std::int64_t kind, const char* name) { + std::optional result; + for (const auto& tag : tags) { + if (tag.Tag != kind) continue; + if (result && *result != tag.Value) + throw Domain::RelinkerException(std::string("Conflicting duplicate ") + name + " tags"); + result = tag.Value; + } + return result; + }; + const auto address = value(DT_RELR, "DT_RELR"); + const auto size = value(DT_RELRSZ, "DT_RELRSZ"); + const auto stride = value(DT_RELRENT, "DT_RELRENT"); + if (!address && !size && !stride) return {}; + if (!address || !size || !stride) + throw Domain::RelinkerException("Incomplete DT_RELR metadata"); + if (*stride != 8) + throw Domain::RelinkerException("Invalid DT_RELRENT: expected 8 bytes"); + if (*size % 8 != 0) + throw Domain::RelinkerException("Invalid DT_RELRSZ: expected a multiple of 8 bytes"); + if (*size == 0) return {}; + + for (const auto& header : headers) { + if (header.Type != PT_LOAD) continue; + if (header.FileSize > header.MemorySize || header.Offset > bytes.size() || header.FileSize > bytes.size() - header.Offset + || header.MemorySize > std::numeric_limits::max() - header.MappedAddress) + throw Domain::RelinkerException("Invalid RELR load segment", header.Offset); + } + const auto loadAt = [&](const std::uint64_t target, const std::uint64_t extent, const bool file) -> const Domain::ProgramHeader& { + const Domain::ProgramHeader* found = nullptr; + for (const auto& header : headers) { + const auto limit = file ? header.FileSize : header.MemorySize; + if (header.Type != PT_LOAD || target < header.MappedAddress || target - header.MappedAddress > limit + || extent > limit - (target - header.MappedAddress)) continue; + if (found) throw Domain::RelinkerException("Ambiguous RELR load mapping", target); + found = &header; + } + if (!found) throw Domain::RelinkerException(file ? "RELR table exceeds file-backed load segment" : "RELR target exceeds load segment", target); + return *found; + }; + const auto& table = loadAt(*address, *size, true); + const auto tableOffset = table.Offset + (*address - table.MappedAddress); + const auto readWord = [&](const std::uint64_t offset) { + std::uint64_t result = 0; + for (std::size_t index = 0; index < 8; ++index) + result |= static_cast(bytes[offset + index]) << (index * 8); + return result; + }; + + std::map result; + const auto append = [&](const std::uint64_t target) { + const auto& load = loadAt(target, 8, false); + const auto next = result.lower_bound(target); + if ((next != result.end() && next->first - target < 8) + || (next != result.begin() && target - std::prev(next)->first < 8)) + throw Domain::RelinkerException("Overlapping RELR targets", target); + const auto relative = target - load.MappedAddress; + std::uint64_t addend = 0; + for (std::size_t index = 0; index < 8 && relative + index < load.FileSize; ++index) + addend |= static_cast(bytes[load.Offset + relative + index]) << (index * 8); + result.emplace_hint(next, target, addend); + }; + std::optional cursor; + for (std::uint64_t offset = 0; offset < *size; offset += 8) { + const auto entry = readWord(tableOffset + offset); + if ((entry & 1) == 0) { + append(entry); + cursor = entry + 8; + } else { + if (!cursor) throw Domain::RelinkerException("RELR bitmap precedes address entry", *address + offset); + if (*cursor > std::numeric_limits::max() - 63 * 8) + throw Domain::RelinkerException("RELR bitmap address overflow", *cursor); + for (std::size_t bit = 1; bit < 64; ++bit) + if ((entry >> bit) & 1) append(*cursor + (bit - 1) * 8); + *cursor += 63 * 8; + } + } + return result; +} + +} diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index ceda75412d..00a39894f0 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -5,13 +5,16 @@ #include #include #include +#include #include #include #include +#include #include #include #include +#include namespace Relinker { @@ -44,6 +47,7 @@ RelinkerPipeline::RelinkerPipeline(std::shared_ptr elfReader, std::s std::string RelinkerPipeline::relocationTypeName(std::uint32_t type) { switch (type) { case 1: return "R_X86_64_64"; + case 2: return "R_X86_64_PC32"; case 6: return "R_X86_64_GLOB_DAT"; case 7: return "R_X86_64_JUMP_SLOT"; case 10: return "R_X86_64_32"; @@ -66,9 +70,11 @@ RelinkResult RelinkerPipeline::Relink() { throw RelinkerException("ET_NONE inputs do not specify an ELF object type"); if (inputType == kEtCore) throw RelinkerException("ET_CORE core dumps are not executable images"); - if (inputType != ET_EXEC && inputType != ET_DYN && inputType != kEtSceDynExec && inputType != kEtSceDynamic) { + if (inputType == ET_EXEC) + throw RelinkerException("ET_EXEC inputs are linked at fixed addresses; conversion requires a position-independent image", 0x10); + if (inputType != ET_DYN && inputType != kEtSceDynExec && inputType != kEtSceDynamic) { std::ostringstream message; - message << "Unsupported ELF object type 0x" << std::hex << inputType << ": expected ET_EXEC, ET_DYN, 0xfe10 or 0xfe18"; + message << "Unsupported ELF object type 0x" << std::hex << inputType << ": expected ET_DYN, 0xfe10 or 0xfe18"; throw RelinkerException(message.str(), 0x10); } @@ -231,8 +237,6 @@ RelinkResult RelinkerPipeline::Relink() { 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); @@ -260,6 +264,7 @@ RelinkResult RelinkerPipeline::Relink() { std::vector neededLibraries; const std::vector& raw = elfReader->GetRawBytes(); + const auto relrRelocations = ReadRelrRelocations(raw, programHeaders, dynTags); const std::optional dynSymTabSize = hasTag(DT_OS_SYMTABSZ) ? std::optional(getTagValue(DT_OS_SYMTABSZ)) : std::nullopt; @@ -280,6 +285,8 @@ RelinkResult RelinkerPipeline::Relink() { auto readCStr = [&](FileByteOffset strOff) -> std::string { if (strOff >= dynStrTabSize) throw RelinkerException("Dynamic string offset is outside DT_STRSZ", strOff); + if (strOff == 0 && raw[dynStrTabOffset] != 0) + throw RelinkerException("Dynamic string table does not begin with a NUL byte", dynStrTabOffset); std::string result; FileByteOffset pos = dynStrTabOffset + strOff; const FileByteOffset end = dynStrTabOffset + dynStrTabSize; @@ -373,6 +380,13 @@ RelinkResult RelinkerPipeline::Relink() { const std::uint32_t symIdx = static_cast(rInfo >> 32); const std::uint32_t relType = static_cast(rInfo & 0xffffffff); + if (relType != R_X86_64_NONE && !relrRelocations.empty()) { + const auto nextRelr = relrRelocations.lower_bound(rOffset); + if ((nextRelr != relrRelocations.end() && nextRelr->first - rOffset < 8) + || (nextRelr != relrRelocations.begin() && rOffset - std::prev(nextRelr)->first < 8)) + throw RelinkerException("RELR target overlaps RELA relocation", rOffset); + } + validationPolicy->ValidateRelocationTypeSupported(relType, pos); if (relType == R_X86_64_NONE) @@ -396,6 +410,10 @@ RelinkResult RelinkerPipeline::Relink() { std::memcpy(&nameOff, raw.data() + symOff, 4); const auto name = readCStr(nameOff); + std::uint16_t section = 0; + std::memcpy(§ion, raw.data() + symOff + 6, sizeof(section)); + if (section == SHN_UNDEF && (raw[symOff + 4] >> 4) != STB_WEAK && (raw[symOff + 5] & 3) != 0) + throw RelinkerException("Undefined non-weak symbol has non-default visibility: " + name, symOff); if (name.empty() && (raw.size() - symOff < symEntSize || std::any_of(raw.begin() + static_cast(symOff), raw.begin() + static_cast(symOff + symEntSize), [](const std::uint8_t byte) { return byte != 0; }))) @@ -456,6 +474,7 @@ RelinkResult RelinkerPipeline::Relink() { const auto symbolBytes = getTagValue(DT_OS_SYMTABSZ); if (symbolBytes % symEntSize != 0 || dynSymTabOffset > raw.size() || symbolBytes > raw.size() - dynSymTabOffset) throw RelinkerException("Invalid executable dynamic symbol table size"); + std::map exportEntries; for (ByteCount off = symEntSize; off < symbolBytes; off += symEntSize) { const auto* symbol = raw.data() + dynSymTabOffset + off; std::uint16_t section = 0; @@ -467,6 +486,12 @@ RelinkResult RelinkerPipeline::Relink() { name.resize(name.find('#') == std::string::npos ? name.size() : name.find('#')); const FileByteOffset entryOffset = dynSymTabOffset + off; const auto type = symbol[4] & 15; + if (name.empty()) + throw RelinkerException("Empty executable export name", entryOffset); + if ((symbol[4] >> 4) > STB_WEAK || (symbol[5] & ~3) != 0 || (type != STT_NOTYPE && type != STT_OBJECT && type != STT_FUNC && type != STT_TLS)) + throw RelinkerException("Unsupported executable export attributes: " + name, entryOffset); + if (const auto [previous, inserted] = exportEntries.emplace(name, symbol); !inserted && std::memcmp(previous->second + 4, symbol + 4, symEntSize - 4) != 0) + throw RelinkerException("Conflicting executable exports after stripping #: " + name, entryOffset); std::uint64_t value = 0; std::uint64_t size = 0; std::memcpy(&value, symbol + 8, sizeof(value)); @@ -616,6 +641,8 @@ RelinkResult RelinkerPipeline::Relink() { extractLocal(dynRelaOffset, dynRelaSize, false); extractLocal(dynJmpRelOffset, dynJmpRelSize, true); + for (const auto& [target, addend] : relrRelocations) + appendRela(dynSection.RelaData, target, R_X86_64_RELATIVE, static_cast(addend)); std::vector entries; entries.reserve(nidRefs.size()); diff --git a/core/relinker/relinker/tests/CallRegistryWriterTests.cpp b/core/relinker/relinker/tests/CallRegistryWriterTests.cpp index 542f386b46..3113311b37 100644 --- a/core/relinker/relinker/tests/CallRegistryWriterTests.cpp +++ b/core/relinker/relinker/tests/CallRegistryWriterTests.cpp @@ -69,11 +69,143 @@ void RegistryOffsets() { } } +void RequireUtf8Failure(const std::string& input, const std::string& expectedMessage) { + Relinker::CallRegistryWriter writer; + try { + writer.WriteNeededLibraries({input}); + } catch (const Relinker::RelinkerException& error) { + Require(std::string(error.what()).find(expectedMessage) != std::string::npos, + "Expected message '" + expectedMessage + "', got: " + error.what()); + return; + } + throw std::runtime_error("Expected failure for input: " + input); +} + +void Utf8ValidationAndJsonEscaping() { + Relinker::CallRegistryWriter writer; + + for (const std::string& invalid : { + std::string("\x80", 1), + std::string("\xBF", 1), + std::string("\xC0", 1), + std::string("\xC1", 1), + std::string("\xF5", 1), + std::string("\xFF", 1), + std::string("prefix\x80postfix", 14), + std::string("lib\xC0.prx", 8) + }) { + RequireUtf8Failure(invalid, "Invalid UTF-8 lead byte in call registry string"); + } + + for (const std::string& truncated : { + std::string("\xC2", 1), + std::string("\xDF", 1), + std::string("\xE0", 1), + std::string("\xE2", 1), + std::string("\xE2\x82", 2), + std::string("\xF0", 1), + std::string("\xF0\x9F", 2), + std::string("\xF0\x9F\x98", 3), + std::string("hello\xC2", 6), + std::string("test\xE2\x82", 6) + }) { + RequireUtf8Failure(truncated, "Truncated UTF-8 sequence in call registry string"); + } + + for (const std::string& invalidContinuation : { + std::string("\xC2\x00", 2), + std::string("\xC2\x7F", 2), + std::string("\xC2\xC0", 2), + std::string("\xC2\x20", 2), + std::string("\xE2\x00\x80", 3), + std::string("\xE2\x82\x00", 3), + std::string("\xE2\x82\x7F", 3), + std::string("\xE2\x82\xC0", 3), + std::string("\xF0\x00\x80\x80", 4), + std::string("\xF0\x9F\x00\x80", 4), + std::string("\xF0\x9F\x98\x00", 4), + std::string("\xF0\x9F\x98\x7F", 4) + }) { + RequireUtf8Failure(invalidContinuation, "Invalid UTF-8 continuation byte in call registry string"); + } + + for (const std::string& invalidCodepoint : { + std::string("\xE0\x80\xAF", 3), + std::string("\xE0\x9F\xBF", 3), + std::string("\xF0\x80\x80\x80", 4), + std::string("\xF0\x8F\xBF\xBF", 4), + std::string("\xED\xA0\x80", 3), + std::string("\xED\xAD\xBF", 3), + std::string("\xED\xAE\x80", 3), + std::string("\xED\xBF\xBF", 3), + std::string("\xF4\x90\x80\x80", 4) + }) { + RequireUtf8Failure(invalidCodepoint, "Invalid UTF-8 codepoint in call registry string"); + } + + Relinker::CallRegistryEntry invalidEntry{}; + invalidEntry.Nid = std::string("\xFF", 1); + invalidEntry.CallSitesResolved = false; + try { + writer.WriteCallRegistry({invalidEntry}); + throw std::runtime_error("Expected failure in WriteCallRegistry for invalid UTF-8"); + } catch (const Relinker::RelinkerException& error) { + Require(std::string(error.what()).find("Invalid UTF-8 lead byte in call registry string") != std::string::npos, + "Expected lead byte error in WriteCallRegistry"); + } + + try { + writer.WriteModuleImports({invalidEntry}); + throw std::runtime_error("Expected failure in WriteModuleImports for invalid UTF-8"); + } catch (const Relinker::RelinkerException& error) { + Require(std::string(error.what()).find("Invalid UTF-8 lead byte in call registry string") != std::string::npos, + "Expected lead byte error in WriteModuleImports"); + } + + const std::string specialChars = "\"\\\n\r\t"; + const std::string specialJson = writer.WriteNeededLibraries({specialChars}); + Require(specialJson.find("\"\\\"\\\\\\n\\r\\t\"") != std::string::npos, "special characters are not escaped properly in JSON"); + + const std::string controlChars = std::string("\x00\x01\x07\x08\x0B\x0C\x1B\x1F", 8); + const std::string controlJson = writer.WriteNeededLibraries({controlChars}); + Require(controlJson.find("\"\\u0000\\u0001\\u0007\\u0008\\u000b\\u000c\\u001b\\u001f\"") != std::string::npos, + "control characters < 0x20 are not escaped to \\u00XX in JSON"); + + const std::string mixedEscapes = std::string("lib \"test\\v1\"\n\r\t\x07.prx", 21); + const std::string mixedJson = writer.WriteNeededLibraries({mixedEscapes}); + Require(mixedJson.find("\"lib \\\"test\\\\v1\\\"\\n\\r\\t\\u0007.prx\"") != std::string::npos, + "mixed escaping failed in JSON"); + + const std::string valid2Byte = "\xC2\xA2"; + const std::string valid3Byte = "\xE2\x82\xAC"; + const std::string valid4Byte = "\xF0\x9F\x98\x80"; + const std::string validCombined = "lib_\xC2\xA2_\xE2\x82\xAC_\xF0\x9F\x98\x80.prx"; + + const std::string validJson = writer.WriteNeededLibraries({valid2Byte, valid3Byte, valid4Byte, validCombined}); + Require(validJson.find(" \"" + valid2Byte + "\"") != std::string::npos, "valid 2-byte UTF-8 sequence was not preserved"); + Require(validJson.find(" \"" + valid3Byte + "\"") != std::string::npos, "valid 3-byte UTF-8 sequence was not preserved"); + Require(validJson.find(" \"" + valid4Byte + "\"") != std::string::npos, "valid 4-byte UTF-8 sequence was not preserved"); + Require(validJson.find(" \"" + validCombined + "\"") != std::string::npos, "combined valid UTF-8 sequences were not preserved"); + + Relinker::CallRegistryEntry validEntry{}; + validEntry.Nid = validCombined; + validEntry.CallSitesResolved = true; + validEntry.Library = valid2Byte; + validEntry.RelocationTypeString = valid3Byte; + validEntry.TargetSection = valid4Byte; + const std::string validRegistry = writer.WriteCallRegistry({validEntry}); + Require(validRegistry.find("\"nid\": \"" + validCombined + "\"") != std::string::npos, "NID UTF-8 not preserved in call registry"); + Require(validRegistry.find("\"library\": \"" + valid2Byte + "\"") != std::string::npos, "Library UTF-8 not preserved in call registry"); + Require(validRegistry.find("\"relocationType\": \"" + valid3Byte + "\"") != std::string::npos, "RelocationType UTF-8 not preserved in call registry"); + Require(validRegistry.find("\"targetSection\": \"" + valid4Byte + "\"") != std::string::npos, "TargetSection UTF-8 not preserved in call registry"); +} + } int main() { try { RegistryOffsets(); + Utf8ValidationAndJsonEscaping(); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; diff --git a/core/relinker/relinker/tests/RelrRelocationTests.cpp b/core/relinker/relinker/tests/RelrRelocationTests.cpp new file mode 100644 index 0000000000..993b71e4a1 --- /dev/null +++ b/core/relinker/relinker/tests/RelrRelocationTests.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(const bool condition) { + if (!condition) throw std::runtime_error("RELR validation failed"); +} + +struct Fixture { + std::vector Bytes = std::vector(0x2000); + std::vector Headers{{1, 7, 0, 0, 0, 0x2000, 0x3000, 0x1000}}; + std::vector Tags{{36, 0x1000}, {35, 8}, {37, 8}}; + + Fixture() { + Bytes[0] = 0x7f; Bytes[1] = 'E'; Bytes[2] = 'L'; Bytes[3] = 'F'; + Bytes[4] = 2; Bytes[5] = 1; Bytes[6] = 1; + Io::WriteU16(Bytes, 16, 3); Io::WriteU16(Bytes, 18, 62); Io::WriteU32(Bytes, 20, 1); + Io::WriteU64(Bytes, 32, 64); Io::WriteU16(Bytes, 52, 64); + Io::WriteU16(Bytes, 54, 56); Io::WriteU16(Bytes, 56, 2); + Io::WriteU32(Bytes, 64, 1); Io::WriteU32(Bytes, 68, 7); + Io::WriteU64(Bytes, 96, 0x2000); Io::WriteU64(Bytes, 104, 0x3000); Io::WriteU64(Bytes, 112, 0x1000); + Io::WriteU32(Bytes, 120, 2); Io::WriteU64(Bytes, 128, 0x100); Io::WriteU64(Bytes, 136, 0x100); + Io::WriteU64(Bytes, 152, 64); Io::WriteU64(Bytes, 160, 64); + for (std::size_t index = 0; index < Tags.size(); ++index) { + Io::WriteU64(Bytes, 0x100 + index * 16, Tags[index].Tag); + Io::WriteU64(Bytes, 0x108 + index * 16, Tags[index].Value); + } + for (std::size_t offset = 0x200; offset < 0x900; offset += 8) Io::WriteU64(Bytes, offset, offset + 0x900); + SetWords({0x200}); + } + + void SetWords(const std::initializer_list words) { + Tags[1].Value = words.size() * 8; + Io::WriteU64(Bytes, 0x118, words.size() * 8); + std::size_t offset = 0x1000; + for (const auto word : words) { Io::WriteU64(Bytes, offset, word); offset += 8; } + } + + auto Read() const { return Relinker::ReadRelrRelocations(Bytes, Headers, Tags); } +}; + +void Reject(const std::function& change, const std::string_view diagnostic) { + Fixture fixture; + change(fixture); + try { fixture.Read(); } + catch (const Domain::RelinkerException& error) { + Require(std::string_view(error.what()).find(diagnostic) != std::string_view::npos); + return; + } + throw std::runtime_error("Malformed RELR input accepted"); +} + +} + +int main() try { + Fixture fixture; + Require(fixture.Read() == std::map{{0x200, 0xb00}}); + for (unsigned bit = 1; bit < 64; ++bit) { + fixture.SetWords({0x200, 1ull | (1ull << bit), 1ull | (1ull << 63)}); + const auto result = fixture.Read(); + const auto target = 0x208 + (bit - 1) * 8; + Require(result.size() == 3 && result.at(0x200) == 0xb00 && result.at(target) == target + 0x900); + Require(result.at(0x5f0) == 0xef0); + const auto index = Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(fixture.Bytes); + Require(index->TargetOfSlot(target) == target + 0x900 && index->TargetOfSlot(0x5f0) == 0xef0); + } + fixture.SetWords({0x200, std::numeric_limits::max()}); + Require(fixture.Read().size() == 64); + fixture.SetWords({0x200, 1, 3}); + Require(fixture.Read().size() == 2 && fixture.Read().at(0x400) == 0xd00); + fixture.SetWords({0x800, 0x202}); + Require(fixture.Read().size() == 2 && fixture.Read().at(0x202) == 0x0b08000000000000ull); + fixture.SetWords({0x2000}); + Require(fixture.Read().at(0x2000) == 0); + fixture.SetWords({0x1ffc}); + Io::WriteU32(fixture.Bytes, 0x1ffc, 0x12345678); + Require(fixture.Read().at(0x1ffc) == 0x12345678); + fixture.SetWords({}); + Require(fixture.Read().empty()); + fixture.Tags.clear(); + Require(fixture.Read().empty()); + + for (unsigned mask = 1; mask < 7; ++mask) { + Reject([&](auto& input) { + const auto tags = input.Tags; + input.Tags.clear(); + for (unsigned index = 0; index < 3; ++index) if (mask & (1u << index)) input.Tags.push_back(tags[index]); + }, "Incomplete DT_RELR metadata"); + } + Reject([](auto& input) { input.Tags[2].Value = 4; }, "DT_RELRENT"); + Reject([](auto& input) { input.Tags[1].Value = 9; }, "DT_RELRSZ"); + for (std::size_t index = 0; index < 3; ++index) { + Reject([&](auto& input) { auto tag = input.Tags[index]; ++tag.Value; input.Tags.push_back(tag); }, "Conflicting duplicate"); + } + Reject([](auto& input) { input.SetWords({3}); }, "bitmap precedes address"); + Reject([](auto& input) { input.SetWords({0x200, 0x200}); }, "Overlapping RELR targets"); + Reject([](auto& input) { input.SetWords({0x200, 0x206}); }, "Overlapping RELR targets"); + Reject([](auto& input) { input.SetWords({0x3000}); }, "target exceeds load"); + Reject([](auto& input) { input.SetWords({0x2ffc}); }, "target exceeds load"); + Reject([](auto& input) { input.Tags[0].Value = 0x2000; }, "table exceeds file-backed"); + Reject([](auto& input) { input.Tags[0].Value = 0x1ffc; }, "table exceeds file-backed"); + Reject([](auto& input) { input.Tags[1].Value = 0xfffffffffffffff8ull; }, "table exceeds file-backed"); + Reject([](auto& input) { input.Headers[0].Offset = 0xffffffffffffffffull; }, "Invalid RELR load"); + Reject([](auto& input) { input.Headers[0].MemorySize = 0x1000; }, "Invalid RELR load"); + Reject([](auto& input) { input.Headers.push_back(input.Headers.front()); }, "Ambiguous RELR load"); + Reject([](auto& input) { + const auto address = std::numeric_limits::max() - 0x3f; + input.Headers.push_back({1, 6, 0, address, 0, 0, 0x38, 1}); + input.SetWords({address, 1}); + }, "bitmap address overflow"); + std::cout << "RELR address, bitmap, bounds, addend and reachability tests passed\n"; +} catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; +} diff --git a/core/relinker/relinker/tests/StrictNidFilterTests.cpp b/core/relinker/relinker/tests/StrictNidFilterTests.cpp index 349da2d7a0..76a811be45 100644 --- a/core/relinker/relinker/tests/StrictNidFilterTests.cpp +++ b/core/relinker/relinker/tests/StrictNidFilterTests.cpp @@ -672,6 +672,50 @@ void sceDynlibDataOffsets() { "Strict filter accepted a RELATIVE relocation symbol outside the symbol table"); } +void duplicatePointerRelocations() { + auto input = fixture(); + emit(input, 0, {0xC3}); + importThunk(input, 16, 0x2000); + auto bytes = elfFixture(input); + bytes.resize(0xA00); + write(bytes, 56, 4); + write(bytes, 176, 2); + write(bytes, 180, 6); + write(bytes, 184, 0x300); + write(bytes, 192, 0x2000); + write(bytes, 208, 0x40); + write(bytes, 216, 0x40); + write(bytes, 224, 8); + write(bytes, 232, 0x61000000); + write(bytes, 236, 4); + write(bytes, 240, 0x500); + write(bytes, 264, 0x500); + write(bytes, 272, 0x500); + write(bytes, 280, 0x10); + write(bytes, 0x300, 0x6100002F); + write(bytes, 0x308, 0x400); + write(bytes, 0x310, 0x61000031); + write(bytes, 0x318, 48); + write(bytes, 0x320, 0x61000033); + write(bytes, 0x328, 24); + write(bytes, 0x900, 0x2040); + write(bytes, 0x908, 8); + write(bytes, 0x910, 0x1010); + write(bytes, 0x918, 0x2040); + write(bytes, 0x920, 8); + write(bytes, 0x928, 0x1010); + const std::vector references = {{"live", {}, 6, 0x900, 0x2000, 0}}; + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr).size() == 1, + "Strict filter rejected an identical duplicate pointer relocation"); + write(bytes, 0x928, 0x1020); + requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr); }, + "Strict filter accepted a conflicting duplicate pointer relocation"); + write(bytes, 0x928, 0x1010); + write(bytes, 0x918, 0x2000); + requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr); }, + "Strict filter accepted a pointer relocation overlapping an import slot"); +} + } int main() { @@ -696,6 +740,7 @@ int main() { filterAndPltCompaction(); relativeRelocationTableBounds(); sceDynlibDataOffsets(); + duplicatePointerRelocations(); std::cout << "Strict NID filter tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/relinker/relinker/tests/test_annotate_guest_module.py b/core/relinker/relinker/tests/test_annotate_guest_module.py new file mode 100644 index 0000000000..03f39beed0 --- /dev/null +++ b/core/relinker/relinker/tests/test_annotate_guest_module.py @@ -0,0 +1,53 @@ +import subprocess +import sys +import tempfile +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[4] / "tools")) +import annotate_module + +from test_guest_intel_trampolines import main_fixture +from test_guest_none_relocations import JUMP_SLOT, module + + +def convert(root, rela, plt): + (root / "in" / "sce_module").mkdir(parents=True) + (root / "in" / "sce_module" / "module.prx").write_bytes(bytes(module(rela, plt))) + (root / "in" / "eboot.bin").write_bytes(bytes(main_fixture())) + (root / "out").mkdir() + result = subprocess.run([sys.argv[1], str(root / "in" / "eboot.bin"), str(root / "out" / "app.elf")], + capture_output=True, text=True, timeout=120) + assert result.returncode == 0, result.stderr + artifacts = sorted((root / "out").rglob("*.guest.prx")) + assert len(artifacts) == 1, artifacts + return artifacts[0] + + +def parsed(path): + return annotate_module.Module(path.read_bytes()) + + +def main(): + with tempfile.TemporaryDirectory(prefix="anyps5-annotate-guest-") as directory: + root = Path(directory) + + imported = parsed(convert(root / "imports", [], [JUMP_SLOT])) + assert imported.symbol_count >= 2, imported.symbol_count + slots = imported.imports() + assert len(slots) == 1, slots + assert "shared" in list(slots.values())[0], slots + exports, thread_local = imported.exports() + assert len(exports) == 1 and not thread_local, (exports, thread_local) + assert "shared" in list(exports.values())[0], exports + assert imported.code_segments(), "no code segments" + + plain = parsed(convert(root / "plain", [], [])) + assert plain.imports() == {}, plain.imports() + exports, thread_local = plain.exports() + assert len(exports) == 1 and not thread_local, (exports, thread_local) + assert plain.code_segments(), "no code segments" + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/core/relinker/relinker/tests/test_call_registry.py b/core/relinker/relinker/tests/test_call_registry.py index 5b9a61ac30..f1042be198 100644 --- a/core/relinker/relinker/tests/test_call_registry.py +++ b/core/relinker/relinker/tests/test_call_registry.py @@ -99,6 +99,22 @@ def main(): needed_json = output_needed_bad.with_suffix(".needed.json") if result_needed_bad.returncode != 2 or b"UTF-8" not in result_needed_bad.stderr or needed_json.exists(): raise AssertionError((result_needed_bad.returncode, result_needed_bad.stdout, result_needed_bad.stderr)) + for relocation_type, type_name in ((1, "R_X86_64_64"), (2, "R_X86_64_PC32"), + (6, "R_X86_64_GLOB_DAT"), (10, "R_X86_64_32"), + (16, "R_X86_64_DTPMOD64"), (17, "R_X86_64_DTPOFF64"), + (18, "R_X86_64_TPOFF64")): + image = make_image(b"\0lib.so\0symbol\0") + struct.pack_into("